Merge pull request #4046 from outfoxxed/main-macro-executor

executor: add executor selection to #[embassy_executor::main]
This commit is contained in:
Dario Nieuwenhuis 2025-04-07 13:08:33 +00:00 committed by GitHub
commit 1eec964637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 5 deletions

View File

@ -173,3 +173,27 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
}
/// Creates a new `executor` instance and declares an application entry point for an unspecified architecture, spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// A user-defined entry macro and executor type must be provided via the `entry` and `executor` arguments of the `main` macro.
///
/// ## Examples
/// Spawning a task:
/// ``` rust
/// #[embassy_executor::main(entry = "your_hal::entry", executor = "your_hal::Executor")]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into()
}

View File

@ -16,42 +16,57 @@ enum Flavor {
pub(crate) struct Arch {
default_entry: Option<&'static str>,
flavor: Flavor,
executor_required: bool,
}
pub static ARCH_AVR: Arch = Arch {
default_entry: Some("avr_device::entry"),
flavor: Flavor::Standard,
executor_required: false,
};
pub static ARCH_RISCV: Arch = Arch {
default_entry: Some("riscv_rt::entry"),
flavor: Flavor::Standard,
executor_required: false,
};
pub static ARCH_CORTEX_M: Arch = Arch {
default_entry: Some("cortex_m_rt::entry"),
flavor: Flavor::Standard,
executor_required: false,
};
pub static ARCH_SPIN: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
executor_required: false,
};
pub static ARCH_STD: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
executor_required: false,
};
pub static ARCH_WASM: Arch = Arch {
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
flavor: Flavor::Wasm,
executor_required: false,
};
pub static ARCH_UNSPECIFIED: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
executor_required: true,
};
#[derive(Debug, FromMeta, Default)]
struct Args {
#[darling(default)]
entry: Option<String>,
#[darling(default)]
executor: Option<String>,
}
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
@ -112,9 +127,10 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
}
let entry = match args.entry.as_deref().or(arch.default_entry) {
None => TokenStream::new(),
Some(x) => match TokenStream::from_str(x) {
let entry = match (args.entry.as_deref(), arch.default_entry.as_deref()) {
(None, None) => TokenStream::new(),
(Some(x), _) | (None, Some(x)) if x == "" => TokenStream::new(),
(Some(x), _) | (None, Some(x)) => match TokenStream::from_str(x) {
Ok(x) => quote!(#[#x]),
Err(e) => {
error(&mut errors, &f.sig, e);
@ -123,6 +139,28 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
},
};
let executor = match (args.executor.as_deref(), arch.executor_required) {
(None, true) => {
error(
&mut errors,
&f.sig,
"\
No architecture selected for embassy-executor. Make sure you've enabled one of the `arch-*` features in your Cargo.toml.
Alternatively, if you would like to use a custom executor implementation, specify it with the `executor` argument.
For example: `#[embassy_executor::main(entry = ..., executor = \"some_crate::Executor\")]",
);
""
}
(Some(x), _) => x,
(None, _) => "::embassy_executor::Executor",
};
let executor = TokenStream::from_str(executor).unwrap_or_else(|e| {
error(&mut errors, &f.sig, e);
TokenStream::new()
});
let f_body = f.body;
let out = &f.sig.output;
@ -134,7 +172,7 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
::core::mem::transmute(t)
}
let mut executor = ::embassy_executor::Executor::new();
let mut executor = #executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
@ -144,7 +182,7 @@ pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
Flavor::Wasm => (
quote!(Result<(), wasm_bindgen::JsValue>),
quote! {
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(#executor::new()));
executor.start(|spawner| {
spawner.must_spawn(__embassy_main(spawner));

View File

@ -44,6 +44,8 @@ mod arch;
#[cfg(feature = "_arch")]
#[allow(unused_imports)] // don't warn if the module is empty.
pub use arch::*;
#[cfg(not(feature = "_arch"))]
pub use embassy_executor_macros::main_unspecified as main;
pub mod raw;