From ef8d168df6fa1d8020d7490e4469d196a35077ce Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Fri, 4 Apr 2025 01:33:51 -0700 Subject: [PATCH] executor: add executor selection to #[embassy_executor::main] --- embassy-executor-macros/src/lib.rs | 24 +++++++++++ embassy-executor-macros/src/macros/main.rs | 48 +++++++++++++++++++--- embassy-executor/src/lib.rs | 2 + 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 5f2182f10..8e737db6a 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -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() +} diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 24f61f30b..95722b19b 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -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, + #[darling(default)] + executor: Option, } 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)); diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index d6bd63665..d6fd3d651 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -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;