diff --git a/hil-test/tests/embassy_interrupt_executor.rs b/hil-test/tests/embassy_interrupt_executor.rs index 5bbd393ecec..b44e30992dc 100644 --- a/hil-test/tests/embassy_interrupt_executor.rs +++ b/hil-test/tests/embassy_interrupt_executor.rs @@ -3,6 +3,12 @@ //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 //% FEATURES: unstable embassy +//% ENV(single_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = single-integrated +//% ENV(multiple_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = multiple-integrated +//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = generic +//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE = 16 +//% ENV(default_with_waiti): ESP_HAL_EMBASSY_CONFIG_LOW_POWER_WAIT = true +//% ENV(default_no_waiti): ESP_HAL_EMBASSY_CONFIG_LOW_POWER_WAIT = false #![no_std] #![no_main] @@ -50,7 +56,7 @@ struct Context { } #[cfg(test)] -#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] +#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())] mod test { use super::*; diff --git a/hil-test/tests/embassy_interrupt_spi_dma.rs b/hil-test/tests/embassy_interrupt_spi_dma.rs index a751fe5581f..ea52f33a544 100644 --- a/hil-test/tests/embassy_interrupt_spi_dma.rs +++ b/hil-test/tests/embassy_interrupt_spi_dma.rs @@ -2,6 +2,10 @@ //% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2 //% FEATURES: unstable embassy +//% ENV(single_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = single-integrated +//% ENV(multiple_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = multiple-integrated +//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = generic +//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE = 16 #![no_std] #![no_main] diff --git a/hil-test/tests/gpio.rs b/hil-test/tests/gpio.rs index eed10de3e04..e6d9f69ca91 100644 --- a/hil-test/tests/gpio.rs +++ b/hil-test/tests/gpio.rs @@ -1,7 +1,7 @@ //! GPIO Test //% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: unstable embassy +//% FEATURES(unstable): unstable embassy //% FEATURES(stable): #![no_std] diff --git a/xtask/README.md b/xtask/README.md index 2e5182d3c69..2003df05fb2 100644 --- a/xtask/README.md +++ b/xtask/README.md @@ -25,3 +25,94 @@ Commands: Options: -h, --help Print help ``` + +## Test/example metadata use + +Each test and example can specify metadata. This metadata is read, interpreted and used by the +`xtask`. It affects how a particular test or example is built, but it does not otherwise modify +the user's system. + +Metadata lines come in the form of: + +- `//% METADATA_KEY: value` - applies to all configuration. +- `//% METADATA_KEY(config_name_1, config_name_2, ...): value` - applies to specific configurations. + +The following metadata keys can be used: + +### `//% CHIPS` + +A space-separated list of chip names. The test or example will be built for these chips. If the line +is missing, the file is built for all known chips. + +``` +//% CHIPS: esp32c6 esp32s3 +``` + +This key is a filter. If a named configuration contains a list of chips, the named list overwrites +the unnamed list for that configuration. If multiple lines specify the same configuration, the +latter one overwrites the earlier one. + +`CHIPS` can be used to set a specific feature for a specific chip, like this: + +``` +//% CHIPS: esp32c3 esp32c6 +//% CHIPS(xtensa): esp32s3 +//% FEATURES(riscv): +//% FEATURES(xtensa): psram +``` + +Here we need to specify an empty `FEATURES(riscv)` otherwise the xtask would only create the +`xtensa` configuration, ignoring the other chips. + +### `//% TAG` + +Used to sort examples, when running `run-example` without naming a specific example. + +This key is a filter. If a named configuration contains a tag, the named line overwrites +the unnamed line for that configuration. If multiple lines specify the same configuration, the +latter one overwrites the earlier one. In general, you probably don't want to use `//% TAG(config)`. + +### `//% FEATURES` + +A space-separated list of cargo features that will be enabled automatically when +building the test or example. If you need to specify a feature of a dependency, +you can use the `crate-name/feature-name` format. + +This key is additive. The unnamed list is added to named lists, and multiple lists with the +same name are merged. + +### `//% ENV` + +Environmental variables to be set, when building the test or example. This is +mainly intended, but not limited to setting esp-config configuration. + +One environment variable is specified in a single line. The name and value are separated by `=`. + +``` +//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = generic +//% ENV(generic_queue): ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE = 16 +``` + +This key is additive. The unnamed list is added to named lists, and multiple lists with the +same name are merged. + +### Working with multiple metadata configurations + +Processing a file will create one configuration, or however many names the xtask encounters. + +For example, the following list creates two configurations (`single_integrated` and `multiple_integrated`). + +``` +// This is a hypothetical "embassy_test.rs" +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 +//% FEATURES: unstable embassy +//% ENV(single_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = single-integrated +//% ENV(multiple_integrated): ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE = multiple-integrated +``` + +You can specifiy a test or example by file, or by configuration. If the +parameters match multiple files, they will be built or executed in sucession. + +For example, running `cargo xtask run-tests esp32 embassy_test` will run both +`embassy_test_single_integrated` and `embassy_test_multiple_integrated`, but you can also +run `cargo xtask run-tests esp32 embassy_test_multiple_integrated` to select only one. diff --git a/xtask/src/cargo.rs b/xtask/src/cargo.rs index 3bb7efda770..dac780331db 100644 --- a/xtask/src/cargo.rs +++ b/xtask/src/cargo.rs @@ -28,11 +28,6 @@ pub fn run(args: &[String], cwd: &Path) -> Result<()> { Ok(()) } -/// Execute cargo with the given arguments and from the specified directory. -pub fn run_and_capture(args: &[String], cwd: &Path) -> Result { - run_with_env::<[(&str, &str); 0], _, _>(args, cwd, [], true) -} - /// Execute cargo with the given arguments and from the specified directory. pub fn run_with_env(args: &[String], cwd: &Path, envs: I, capture: bool) -> Result where diff --git a/xtask/src/firmware.rs b/xtask/src/firmware.rs new file mode 100644 index 00000000000..97224bb9988 --- /dev/null +++ b/xtask/src/firmware.rs @@ -0,0 +1,316 @@ +use std::{ + collections::HashMap, + fs, + path::{Path, PathBuf}, +}; + +use anyhow::{bail, Context, Result}; +use clap::ValueEnum; +use esp_metadata::Chip; +use strum::IntoEnumIterator as _; + +use crate::windows_safe_path; + +/// A single, configured example (or test). + +#[derive(Debug, Clone)] +pub struct Metadata { + example_path: PathBuf, + chip: Chip, + configuration_name: String, + features: Vec, + tag: Option, + description: Option, + env_vars: HashMap, +} + +impl Metadata { + /// Absolute path to the example. + pub fn example_path(&self) -> &Path { + &self.example_path + } + + /// Name of the example. + pub fn binary_name(&self) -> String { + self.example_path() + .file_name() + .unwrap() + .to_string_lossy() + .replace(".rs", "") + } + + /// Name of the example, including the name of the configuration. + pub fn output_file_name(&self) -> String { + if self.configuration_name.is_empty() { + self.binary_name() + } else { + format!("{}_{}", self.binary_name(), self.configuration_name) + } + } + + /// The name of the configuration. + pub fn configuration(&self) -> &str { + &self.configuration_name + } + + /// Name of the example, including the name of the configuration. + pub fn name_with_configuration(&self) -> String { + if self.configuration_name.is_empty() { + self.binary_name() + } else { + format!("{} ({})", self.binary_name(), self.configuration_name) + } + } + + /// A list of all features required for building a given example. + pub fn feature_set(&self) -> &[String] { + &self.features + } + + /// A list of all env vars to build a given example. + pub fn env_vars(&self) -> &HashMap { + &self.env_vars + } + + /// If the specified chip is in the list of chips, then it is supported. + pub fn supports_chip(&self, chip: Chip) -> bool { + self.chip == chip + } + + /// Optional tag of the example. + pub fn tag(&self) -> Option { + self.tag.clone() + } + + /// Optional description of the example. + pub fn description(&self) -> Option { + self.description.clone() + } + + pub fn matches(&self, filter: &Option) -> bool { + let Some(filter) = filter.as_deref() else { + return false; + }; + + filter == self.binary_name() || filter == self.output_file_name() + } +} + +#[derive(Debug, Default, Clone)] +pub struct Configuration { + chips: Vec, + name: String, + features: Vec, + esp_config: HashMap, + tag: Option, +} + +struct ConfigurationCollector<'a> { + configurations: &'a mut HashMap, + all_configurations: &'a mut Configuration, + meta_line: &'a MetaLine, +} + +impl ConfigurationCollector<'_> { + fn apply(&mut self, callback: impl Fn(&mut Configuration)) { + if self.meta_line.config_names.is_empty() { + callback(self.all_configurations); + } else { + for config_name in &self.meta_line.config_names { + let meta = self + .configurations + .entry(config_name.clone()) + .or_insert_with(|| Configuration { + name: config_name.clone(), + ..Configuration::default() + }); + callback(meta); + } + } + } +} + +struct MetaLine { + key: String, + config_names: Vec, + value: String, +} + +/// Parse a metadata line from an example file. +/// +/// Metadata lines come in the form of: +/// +/// - `//% METADATA_KEY: value` or +/// - `//% METADATA_KEY(config_name_1, config_name_2, ...): value`. +fn parse_meta_line(line: &str) -> anyhow::Result { + let Some((key, value)) = line.trim_start_matches("//%").split_once(':') else { + bail!("Metadata line is missing ':': {}", line); + }; + + let (key, config_names) = if let Some((key, config_names)) = key.split_once('(') { + let config_names = config_names + .trim_end_matches(')') + .split(',') + .map(str::trim) + .map(ToString::to_string) + .collect(); + (key.trim(), config_names) + } else { + (key, Vec::new()) + }; + + let key = key.trim(); + let value = value.trim(); + + Ok(MetaLine { + key: key.to_string(), + config_names, + value: value.to_string(), + }) +} + +/// Load all examples at the given path, and parse their metadata. +pub fn load(path: &Path) -> Result> { + let mut examples = Vec::new(); + + for entry in fs::read_dir(path)? { + let path = windows_safe_path(&entry?.path()); + let text = fs::read_to_string(&path) + .with_context(|| format!("Could not read {}", path.display()))?; + + let mut description = None; + + // collect `//!` as description + for line in text.lines().filter(|line| line.starts_with("//!")) { + let line = line.trim_start_matches("//!"); + let mut descr: String = description.unwrap_or_default(); + descr.push_str(line); + descr.push('\n'); + description = Some(descr); + } + + // When the list of configuration names is missing, the metadata is applied to + // all configurations. Each configuration encountered will create a + // separate Metadata entry. Different metadata lines referring to the + // same configuration will be merged. + // + // If there are no named configurations, an unnamed default is created. + let mut all_configuration = Configuration { + chips: Chip::iter().collect::>(), + ..Configuration::default() + }; + + let mut configurations = HashMap::::new(); + + // Unless specified, an example is assumed to be valid for all chips. + for (line_no, line) in text + .lines() + .enumerate() + .filter(|(_, line)| line.starts_with("//%")) + { + let meta_line = parse_meta_line(line) + .with_context(|| format!("Failed to parse line {}", line_no + 1))?; + + let mut relevant_metadata = ConfigurationCollector { + configurations: &mut configurations, + all_configurations: &mut all_configuration, + meta_line: &meta_line, + }; + + match meta_line.key.as_str() { + // A list of chips that can run the example using the current configuration. + "CHIPS" => { + let chips = meta_line + .value + .split_ascii_whitespace() + .map(|s| Chip::from_str(s, false).unwrap()) + .collect::>(); + relevant_metadata.apply(|meta| meta.chips = chips.clone()); + } + // Cargo features to enable for the current configuration. + "FEATURES" => { + let mut values = meta_line + .value + .split_ascii_whitespace() + .map(ToString::to_string) + .collect::>(); + + // Sort the features so they are in a deterministic order: + values.sort(); + + relevant_metadata.apply(|meta| meta.features.extend_from_slice(&values)); + } + // esp-config env vars, one per line + "ENV" => { + let (env_var, value) = meta_line + .value + .split_once('=') + .with_context(|| "CONFIG metadata must be in the form 'CONFIG=VALUE'")?; + + let env_var = env_var.trim(); + let value = value.trim(); + + relevant_metadata.apply(|meta| { + meta.esp_config + .insert(env_var.to_string(), value.to_string()); + }); + } + // Tags by which the user can filter examples. + "TAG" => { + relevant_metadata.apply(|meta| meta.tag = Some(meta_line.value.to_string())); + } + key => log::warn!("Unrecognized metadata key '{key}', ignoring"), + } + } + + // Merge "all" into configurations + for meta in configurations.values_mut() { + // Chips is a filter, inherit if empty + if meta.chips.is_empty() { + meta.chips = all_configuration.chips.clone(); + } + + // Tag is an ID, inherit if empty + if meta.tag.is_none() { + meta.tag = all_configuration.tag.clone(); + } + + // Other values are merged + meta.features.extend_from_slice(&all_configuration.features); + meta.esp_config.extend(all_configuration.esp_config.clone()); + } + + // If no configurations are specified, fall back to the unnamed one. Otherwise + // ignore it, it has been merged into the others. + if configurations.is_empty() { + configurations.insert(String::new(), all_configuration); + } + + // Generate metadata + + for configuration in configurations.values_mut() { + // Sort the features so they are in a deterministic order: + configuration.features.sort(); + + for chip in &configuration.chips { + examples.push(Metadata { + // File properties + example_path: path.clone(), + description: description.clone(), + + // Configuration + chip: *chip, + configuration_name: configuration.name.clone(), + features: configuration.features.clone(), + tag: configuration.tag.clone(), + env_vars: configuration.esp_config.clone(), + }) + } + } + } + + // Sort by feature set, to prevent rebuilding packages if not necessary. + examples.sort_by_key(|e| e.feature_set().join(",")); + + Ok(examples) +} diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 95b3ae5d370..51393c9dee7 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -1,20 +1,21 @@ use std::{ - collections::{HashMap, VecDeque}, + collections::VecDeque, fs::{self, File}, io::Write as _, path::{Path, PathBuf}, process::Command, }; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use cargo::CargoAction; use clap::ValueEnum; use esp_metadata::{Chip, Config}; use strum::{Display, EnumIter, IntoEnumIterator as _}; -use self::cargo::CargoArgsBuilder; +use crate::{cargo::CargoArgsBuilder, firmware::Metadata}; pub mod cargo; +pub mod firmware; #[derive( Debug, @@ -54,70 +55,6 @@ pub enum Package { XtensaLxRt, } -#[derive(Debug, Clone)] -pub struct Metadata { - example_path: PathBuf, - chip: Chip, - feature_set_name: String, - feature_set: Vec, - tag: Option, - description: Option, -} - -impl Metadata { - pub fn new( - example_path: &Path, - chip: Chip, - feature_set_name: String, - feature_set: Vec, - tag: Option, - description: Option, - ) -> Self { - Self { - example_path: example_path.to_path_buf(), - chip, - feature_set_name, - feature_set, - tag, - description, - } - } - - /// Absolute path to the example. - pub fn example_path(&self) -> &Path { - &self.example_path - } - - /// Name of the example. - pub fn name(&self) -> String { - self.example_path() - .file_name() - .unwrap() - .to_string_lossy() - .replace(".rs", "") - } - - /// 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. - pub fn supports_chip(&self, chip: Chip) -> bool { - self.chip == chip - } - - /// Optional tag of the example. - pub fn tag(&self) -> Option { - self.tag.clone() - } - - /// Optional description of the example. - pub fn description(&self) -> Option { - self.description.clone() - } -} - #[derive(Debug, Clone, Copy, Display, ValueEnum)] #[strum(serialize_all = "lowercase")] pub enum Version { @@ -218,128 +155,6 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec { features } -/// Load all examples at the given path, and parse their metadata. -pub fn load_examples(path: &Path) -> Result> { - let mut examples = Vec::new(); - - for entry in fs::read_dir(path)? { - let path = windows_safe_path(&entry?.path()); - let text = fs::read_to_string(&path) - .with_context(|| format!("Could not read {}", path.display()))?; - - let mut chips = Chip::iter().collect::>(); - let mut feature_sets = Vec::new(); - let mut chip_features = HashMap::new(); - let mut tag = None; - let mut description = None; - - // collect `//!` as description - for line in text.lines().filter(|line| line.starts_with("//!")) { - let line = line.trim_start_matches("//!"); - let mut descr: String = description.unwrap_or_default(); - descr.push_str(line); - descr.push('\n'); - description = Some(descr); - } - - // We will indicate metadata lines using the `//%` prefix: - for line in text.lines().filter(|line| line.starts_with("//%")) { - let Some((key, value)) = line.trim_start_matches("//%").split_once(':') else { - bail!("Metadata line is missing ':': {}", line); - }; - - let key = key.trim(); - - if key == "CHIPS" { - chips = value - .split_ascii_whitespace() - .map(|s| Chip::from_str(s, false).unwrap()) - .collect::>(); - } else if let Some(feature_set_name) = key.strip_prefix("FEATURES") { - // Base feature set required to run the example. - // If multiple are specified, we compile the same example multiple times. - let mut values = value - .split_ascii_whitespace() - .map(ToString::to_string) - .collect::>(); - - // Sort the features so they are in a deterministic order: - values.sort(); - - let feature_set_name = feature_set_name.trim_matches(&['(', ')']).to_string(); - - if feature_sets - .iter() - .any(|(name, _)| name == &feature_set_name) - { - bail!( - "Duplicate feature set name '{}' in {}", - feature_set_name, - path.display() - ); - } - - feature_sets.push((feature_set_name, values)); - } else if key.starts_with("CHIP-FEATURES(") { - // Additional features required for specific chips. - // These are appended to the base feature set(s). - // If multiple are specified, the last entry wins. - let chips = key - .trim_start_matches("CHIP-FEATURES(") - .trim_end_matches(')'); - - let chips = chips - .split_ascii_whitespace() - .map(|s| Chip::from_str(s, false).unwrap()) - .collect::>(); - - let values = value - .split_ascii_whitespace() - .map(ToString::to_string) - .collect::>(); - - for chip in chips { - chip_features.insert(chip, values.clone()); - } - } else if key.starts_with("TAG") { - tag = Some(value.to_string()); - } else { - log::warn!("Unrecognized metadata key '{key}', ignoring"); - } - } - - if feature_sets.is_empty() { - feature_sets.push((String::new(), Vec::new())); - } - - for (feature_set_name, feature_set) in feature_sets { - for chip in &chips { - let mut feature_set = feature_set.clone(); - if let Some(chip_features) = chip_features.get(chip) { - feature_set.extend(chip_features.iter().cloned()); - - // Sort the features so they are in a deterministic order: - feature_set.sort(); - } - - examples.push(Metadata::new( - &path, - *chip, - feature_set_name.clone(), - feature_set.clone(), - tag.clone(), - description.clone(), - )); - } - } - } - - // Sort by feature set, to prevent rebuilding packages if not necessary. - examples.sort_by_key(|e| e.feature_set().join(",")); - - Ok(examples) -} - /// Run or build the specified test or example for the specified chip. pub fn execute_app( package_path: &Path, @@ -350,31 +165,34 @@ pub fn execute_app( repeat: usize, debug: bool, ) -> Result<()> { - log::info!( - "Building example '{}' for '{}'", - app.example_path().display(), - chip - ); + let package = app.example_path().strip_prefix(package_path)?; + log::info!("Building example '{}' for '{}'", package.display(), chip); + + if !app.configuration().is_empty() { + log::info!(" Configuration: {}", app.configuration()); + } let mut features = app.feature_set().to_vec(); if !features.is_empty() { - log::info!("Features: {}", features.join(",")); + log::info!(" Features: {}", features.join(", ")); } features.push(chip.to_string()); - let package = app.example_path().strip_prefix(package_path)?; - log::info!("Package: {}", package.display()); + let env_vars = app.env_vars(); + for (key, value) in env_vars { + log::info!(" esp-config: {} = {}", key, value); + } let mut builder = CargoArgsBuilder::default() .target(target) .features(&features); let bin_arg = if package.starts_with("src/bin") { - format!("--bin={}", app.name()) + format!("--bin={}", app.binary_name()) } else if package.starts_with("tests") { - format!("--test={}", app.name()) + format!("--test={}", app.binary_name()) } else { - format!("--example={}", app.name()) + format!("--example={}", app.binary_name()) }; builder.add_arg(bin_arg); @@ -405,27 +223,20 @@ pub fn execute_app( log::debug!("{args:#?}"); if let CargoAction::Build(out_dir) = action { - cargo::run(&args, package_path)?; + cargo::run_with_env(&args, package_path, env_vars, false)?; // Now that the build has succeeded and we printed the output, we can // rerun the build again quickly enough to capture JSON. We'll use this to // copy the binary to the output directory. builder.add_arg("--message-format=json"); let args = builder.build(); - let output = cargo::run_and_capture(&args, package_path)?; + let output = cargo::run_with_env(&args, package_path, env_vars, true)?; for line in output.lines() { if let Ok(artifact) = serde_json::from_str::(line) { let out_dir = out_dir.join(&chip.to_string()); std::fs::create_dir_all(&out_dir)?; - let basename = app.name(); - let name = if app.feature_set_name.is_empty() { - basename - } else { - format!("{}_{}", basename, app.feature_set_name) - }; - - let output_file = out_dir.join(name); + let output_file = out_dir.join(app.output_file_name()); std::fs::copy(artifact.executable, &output_file)?; log::info!("Output ready: {}", output_file.display()); } @@ -435,7 +246,7 @@ pub fn execute_app( if repeat != 1 { log::info!("Run {}/{}", i + 1, repeat); } - cargo::run(&args, package_path)?; + cargo::run_with_env(&args, package_path, env_vars.clone(), false)?; } } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 6d3a975ac63..f0af171f224 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -12,8 +12,8 @@ use minijinja::Value; use strum::IntoEnumIterator; use xtask::{ cargo::{CargoAction, CargoArgsBuilder}, + firmware::Metadata, target_triple, - Metadata, Package, Version, }; @@ -235,7 +235,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::firmware::load(&example_path)? .iter() .filter_map(|example| { if example.supports_chip(args.chip) { @@ -247,7 +247,7 @@ fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Res .collect::>(); // Sort all examples by name: - examples.sort_by_key(|a| a.name()); + examples.sort_by_key(|a| a.binary_name()); // Execute the specified action: match action { @@ -268,11 +268,11 @@ fn build_examples( if examples .iter() - .find(|ex| Some(ex.name()) == args.example) + .find(|ex| ex.matches(&args.example)) .is_some() { // Attempt to build only the specified example: - for example in examples.iter().filter(|ex| Some(ex.name()) == args.example) { + for example in examples.iter().filter(|ex| ex.matches(&args.example)) { xtask::execute_app( package_path, args.chip, @@ -310,7 +310,7 @@ 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: let mut found_one = false; - for example in examples.iter().filter(|ex| Some(ex.name()) == args.example) { + for example in examples.iter().filter(|ex| ex.matches(&args.example)) { found_one = true; xtask::execute_app( package_path, @@ -349,7 +349,7 @@ fn run_examples(args: ExampleArgs, examples: Vec, package_path: &Path) for example in examples { let mut skip = false; - log::info!("Running example '{}'", example.name()); + log::info!("Running example '{}'", example.output_file_name()); if let Some(description) = example.description() { log::info!( "\n\n{}\n\nPress ENTER to run example, `s` to skip", @@ -413,21 +413,17 @@ 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::firmware::load(&package_path.join("tests"))? .into_iter() .filter(|example| example.supports_chip(args.chip)) .collect::>(); // Sort all tests by name: - tests.sort_by_key(|a| a.name()); + tests.sort_by_key(|a| a.binary_name()); // Execute the specified action: - if tests - .iter() - .find(|test| Some(test.name()) == args.test) - .is_some() - { - for test in tests.iter().filter(|test| Some(test.name()) == args.test) { + if tests.iter().find(|test| test.matches(&args.test)).is_some() { + for test in tests.iter().filter(|test| test.matches(&args.test)) { xtask::execute_app( &package_path, args.chip, @@ -455,12 +451,12 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { ) .is_err() { - failed.push(test.name()); + failed.push(test.name_with_configuration()); } } if !failed.is_empty() { - bail!("Failed tests: {:?}", failed); + bail!("Failed tests: {:#?}", failed); } Ok(())