diff --git a/.github/actions/check-esp-hal/action.yml b/.github/actions/check-esp-hal/action.yml index d532c7e2acf..65991955d29 100644 --- a/.github/actions/check-esp-hal/action.yml +++ b/.github/actions/check-esp-hal/action.yml @@ -57,5 +57,7 @@ runs: --target=${{ inputs.target }} \ esp-hal - name: Build (examples) + env: + CI: 1 shell: bash - run: cargo +${{ inputs.toolchain }} xtask build-examples esp-hal ${{ inputs.device }} + run: cargo +${{ inputs.toolchain }} xtask build-examples esp-hal ${{ inputs.device }} --debug diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f9aa6789209..69103ed3ed8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,7 +139,7 @@ jobs: cargo xtask build-package --features=esp32c3,wifi,ble,async --target=riscv32imc-unknown-none-elf esp-wifi cargo xtask build-package --features=esp32c6,wifi,ble,async --target=riscv32imac-unknown-none-elf esp-wifi cargo xtask build-package --features=esp32h2,ble,async --target=riscv32imac-unknown-none-elf esp-wifi - + # Verify the MSRV for all Xtensa chips: - name: msrv Xtensa (esp-hal) run: | diff --git a/esp-storage/build.rs b/esp-storage/build.rs index ebb63536296..8a7a421f7e6 100644 --- a/esp-storage/build.rs +++ b/esp-storage/build.rs @@ -6,14 +6,14 @@ fn main() -> Result<(), String> { if cfg!(feature = "esp32") { match std::env::var("OPT_LEVEL") { - Ok(level) => { + Ok(level) if std::env::var("CI").is_err() => { if level != "2" && level != "3" { Err(format!("Building esp-storage for ESP32 needs optimization level 2 or 3 - yours is {}. See https://github.com/esp-rs/esp-storage", level)) } else { Ok(()) } } - Err(_err) => Ok(()), + _ => Ok(()), } } else { Ok(()) diff --git a/examples/build.rs b/examples/build.rs index ffde503da6d..77d3bb7d796 100644 --- a/examples/build.rs +++ b/examples/build.rs @@ -8,4 +8,12 @@ fn main() { if cfg!(feature = "esp-wifi") { println!("cargo::rustc-link-arg=-Trom_functions.x"); } + + // Allow building examples in CI in debug mode + println!("cargo:rustc-check-cfg=cfg(is_not_release)"); + println!("cargo:rerun-if-env-changed=CI"); + #[cfg(debug_assertions)] + if std::env::var("CI").is_err() { + println!("cargo::rustc-cfg=is_not_release"); + } } diff --git a/examples/src/bin/embassy_rmt_rx.rs b/examples/src/bin/embassy_rmt_rx.rs index 176e9b4a135..cd2a4040e84 100644 --- a/examples/src/bin/embassy_rmt_rx.rs +++ b/examples/src/bin/embassy_rmt_rx.rs @@ -22,7 +22,7 @@ use esp_println::{print, println}; const WIDTH: usize = 80; -#[cfg(debug_assertions)] +#[cfg(is_not_release)] compile_error!("Run this example in release mode"); #[embassy_executor::task] diff --git a/examples/src/bin/psram_octal.rs b/examples/src/bin/psram_octal.rs index 4664d0932c7..8a888ab7404 100644 --- a/examples/src/bin/psram_octal.rs +++ b/examples/src/bin/psram_octal.rs @@ -25,11 +25,11 @@ fn init_psram_heap() { } } +#[cfg(is_not_release)] +compile_error!("PSRAM example must be built in release mode!"); + #[entry] fn main() -> ! { - #[cfg(debug_assertions)] - compile_error!("This example MUST be built in release mode!"); - let peripherals = esp_hal::init(esp_hal::Config::default()); psram::init_psram(peripherals.PSRAM); diff --git a/examples/src/bin/psram_quad.rs b/examples/src/bin/psram_quad.rs index 6a70b436d84..6aec440bf97 100644 --- a/examples/src/bin/psram_quad.rs +++ b/examples/src/bin/psram_quad.rs @@ -1,6 +1,6 @@ //! This shows how to use PSRAM as heap-memory via esp-alloc //! -//! You need an ESP32, ESP32-S2, or ESP32-S3 with at least 2 MB of PSRAM memory. +//! You need an ESP32, ESP32-S2 or ESP32-S3 with at least 2 MB of PSRAM memory. //% CHIPS: esp32 esp32s2 esp32s3 //% FEATURES: psram-2m @@ -25,11 +25,11 @@ fn init_psram_heap() { } } +#[cfg(is_not_release)] +compile_error!("PSRAM example must be built in release mode!"); + #[entry] fn main() -> ! { - #[cfg(debug_assertions)] - compile_error!("PSRAM example must be built in release mode!"); - let peripherals = esp_hal::init(esp_hal::Config::default()); psram::init_psram(peripherals.PSRAM); diff --git a/xtask/src/cargo.rs b/xtask/src/cargo.rs index 8f1051faab3..3bc45930421 100644 --- a/xtask/src/cargo.rs +++ b/xtask/src/cargo.rs @@ -113,6 +113,14 @@ impl CargoArgsBuilder { self } + pub fn add_arg(&mut self, arg: S) -> &mut Self + where + S: Into, + { + self.args.push(arg.into()); + self + } + #[must_use] pub fn build(self) -> Vec { let mut args = vec![]; diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 6eb98fcbd89..97235d13535 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -57,11 +57,11 @@ pub enum Package { pub struct Metadata { example_path: PathBuf, chips: Vec, - feature_sets: Vec>, + feature_set: Vec, } impl Metadata { - pub fn new(example_path: &Path, chips: Vec, feature_sets: Vec>) -> Self { + pub fn new(example_path: &Path, chips: Vec, feature_set: Vec) -> Self { let chips = if chips.is_empty() { Chip::iter().collect() } else { @@ -71,7 +71,7 @@ impl Metadata { Self { example_path: example_path.to_path_buf(), chips, - feature_sets, + feature_set, } } @@ -89,9 +89,9 @@ impl Metadata { .replace(".rs", "") } - /// A list of all features required for building a given examples. - pub fn feature_sets(&self) -> &[Vec] { - &self.feature_sets + /// A list of all features required for building a given example. + pub fn feature_set(&self) -> &[String] { + &self.feature_set } /// If the specified chip is in the list of chips, then it is supported. @@ -154,7 +154,7 @@ pub fn build_documentation( } /// Load all examples at the given path, and parse their metadata. -pub fn load_examples(path: &Path) -> Result> { +pub fn load_examples(path: &Path, action: CargoAction) -> Result> { let mut examples = Vec::new(); for entry in fs::read_dir(path)? { @@ -172,7 +172,7 @@ pub fn load_examples(path: &Path) -> Result> { .trim() .split_ascii_whitespace() .map(|s| s.to_string()) - .collect::>(); + .collect::>(); if split.len() < 2 { bail!( @@ -182,7 +182,7 @@ pub fn load_examples(path: &Path) -> Result> { } // The trailing ':' on metadata keys is optional :) - let key = split.pop_front().unwrap(); + let key = split.swap_remove(0); let key = key.trim_end_matches(':'); if key == "CHIPS" { @@ -191,15 +191,31 @@ pub fn load_examples(path: &Path) -> Result> { .map(|s| Chip::from_str(s, false).unwrap()) .collect::>(); } else if key == "FEATURES" { - feature_sets.push(split.into()); + // Sort the features so they are in a deterministic order: + split.sort(); + feature_sets.push(split); } else { log::warn!("Unrecognized metadata key '{key}', ignoring"); } } - examples.push(Metadata::new(&path, chips, feature_sets)); + if feature_sets.is_empty() { + feature_sets.push(Vec::new()); + } + if action == CargoAction::Build { + // Only build the first feature set for each example. + // Rebuilding with a different feature set just wastes time because the latter + // one will overwrite the former one(s). + feature_sets.truncate(1); + } + for feature_set in feature_sets { + examples.push(Metadata::new(&path, chips.clone(), feature_set)); + } } + // Sort by feature set, to prevent rebuilding packages if not necessary. + examples.sort_by_key(|e| e.feature_set().join(",")); + Ok(examples) } @@ -210,7 +226,8 @@ pub fn execute_app( target: &str, app: &Metadata, action: CargoAction, - repeat: usize, + mut repeat: usize, + debug: bool, ) -> Result<()> { log::info!( "Building example '{}' for '{}'", @@ -218,30 +235,7 @@ pub fn execute_app( chip ); - let feature_sets = if app.feature_sets().is_empty() { - vec![vec![]] - } else { - app.feature_sets().to_vec() - }; - - for features in feature_sets { - execute_app_with_features(package_path, chip, target, app, action, repeat, features)?; - } - - Ok(()) -} - -/// Run or build the specified test or example for the specified chip, with the -/// specified features enabled. -pub fn execute_app_with_features( - package_path: &Path, - chip: Chip, - target: &str, - app: &Metadata, - action: CargoAction, - mut repeat: usize, - mut features: Vec, -) -> Result<()> { + let mut features = app.feature_set().to_vec(); if !features.is_empty() { log::info!("Features: {}", features.join(",")); } @@ -269,19 +263,22 @@ pub fn execute_app_with_features( let mut builder = CargoArgsBuilder::default() .subcommand(subcommand) - .arg("--release") .target(target) .features(&features) .arg(bin); + if !debug { + builder.add_arg("--release"); + } + if subcommand == "test" && chip == Chip::Esp32c2 { - builder = builder.arg("--").arg("--speed").arg("15000"); + builder.add_arg("--").add_arg("--speed").add_arg("15000"); } // If targeting an Xtensa device, we must use the '+esp' toolchain modifier: if target.starts_with("xtensa") { builder = builder.toolchain("esp"); - builder = builder.arg("-Zbuild-std=core,alloc") + builder.add_arg("-Zbuild-std=core,alloc"); } let args = builder.build(); diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 2ae6458f62a..3f9f223c27e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -58,6 +58,9 @@ struct ExampleArgs { chip: Chip, /// Optional example to act on (all examples used if omitted) example: Option, + /// Build examples in debug mode only + #[arg(long)] + debug: bool, } #[derive(Debug, Args)] @@ -202,7 +205,7 @@ fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Res }; // Load all examples which support the specified chip and parse their metadata: - let mut examples = xtask::load_examples(&example_path)? + let mut examples = xtask::load_examples(&example_path, action)? .iter() .filter_map(|example| { if example.supports_chip(args.chip) { @@ -227,16 +230,24 @@ fn build_examples(args: ExampleArgs, examples: Vec, package_path: &Pat // Determine the appropriate build target for the given package and chip: let target = target_triple(args.package, &args.chip)?; - if let Some(example) = examples.iter().find(|ex| Some(ex.name()) == args.example) { + if examples + .iter() + .find(|ex| Some(ex.name()) == args.example) + .is_some() + { // Attempt to build only the specified example: - xtask::execute_app( - package_path, - args.chip, - target, - example, - CargoAction::Build, - 1, - ) + for example in examples.iter().filter(|ex| Some(ex.name()) == args.example) { + xtask::execute_app( + package_path, + args.chip, + target, + example, + CargoAction::Build, + 1, + args.debug, + )?; + } + Ok(()) } else if args.example.is_some() { // An invalid argument was provided: bail!("Example not found or unsupported for the given chip") @@ -250,6 +261,7 @@ fn build_examples(args: ExampleArgs, examples: Vec, package_path: &Pat example, CargoAction::Build, 1, + args.debug, ) }) } @@ -261,7 +273,9 @@ fn run_example(args: ExampleArgs, examples: Vec, package_path: &Path) // Filter the examples down to only the binary we're interested in, assuming it // actually supports the specified chip: - if let Some(example) = examples.iter().find(|ex| Some(ex.name()) == args.example) { + let mut found_one = false; + for example in examples.iter().filter(|ex| Some(ex.name()) == args.example) { + found_one = true; xtask::execute_app( package_path, args.chip, @@ -269,10 +283,17 @@ fn run_example(args: ExampleArgs, examples: Vec, package_path: &Path) example, CargoAction::Run, 1, - ) - } else { - bail!("Example not found or unsupported for the given chip") + args.debug, + )?; } + + ensure!( + found_one, + "Example not found or unsupported for {}", + args.chip + ); + + Ok(()) } fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { @@ -283,7 +304,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { let target = target_triple(Package::HilTest, &args.chip)?; // Load all tests which support the specified chip and parse their metadata: - let mut tests = xtask::load_examples(&package_path.join("tests"))? + let mut tests = xtask::load_examples(&package_path.join("tests"), action)? .into_iter() .filter(|example| example.supports_chip(args.chip)) .collect::>(); @@ -292,15 +313,23 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { tests.sort_by_key(|a| a.name()); // Execute the specified action: - if let Some(test) = tests.iter().find(|test| Some(test.name()) == args.test) { - xtask::execute_app( - &package_path, - args.chip, - target, - test, - action, - args.repeat.unwrap_or(1), - ) + if tests + .iter() + .find(|test| Some(test.name()) == args.test) + .is_some() + { + for test in tests.iter().filter(|test| Some(test.name()) == args.test) { + xtask::execute_app( + &package_path, + args.chip, + target, + test, + action, + args.repeat.unwrap_or(1), + false, + )?; + } + Ok(()) } else if args.test.is_some() { bail!("Test not found or unsupported for the given chip") } else { @@ -313,6 +342,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { &test, action, args.repeat.unwrap_or(1), + false, ) .is_err() {