xtask: refactor feature selection and package validation (#3358)

* apply_feature_rules applies more things

* apply features in one place only, fix missing features and clippy warnings

* move various logic to package enum, re-add the ability to test packages with custom feature sets

* small cleanup

* simplify msrv check, fix CI

* review feedback

* try and fix msrv check

* rebase fixups

* use msrv toolchain in check
This commit is contained in:
Scott Mabin 2025-04-14 12:23:54 +01:00 committed by GitHub
parent f034f827b3
commit 0876bac6c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 259 additions and 362 deletions

View File

@ -133,39 +133,18 @@ jobs:
components: rust-src components: rust-src
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
# Verify the MSRV for all RISC-V chips. - name: Stable toolchain checks
run: rustc +stable --version --verbose
- name: esp toolchain checks
run: rustc +esp --version --verbose
# Verify the MSRV for all chips by running a lint session
- name: msrv RISCV (esp-hal) - name: msrv RISCV (esp-hal)
run: | run: |
cargo xtask build-package --features=esp32c2,ci --target=riscv32imc-unknown-none-elf esp-hal cargo +stable xtask lint-packages --chips esp32c2,esp32c3,esp32c6,esp32h2
cargo xtask build-package --features=esp32c3,ci --target=riscv32imc-unknown-none-elf esp-hal
cargo xtask build-package --features=esp32c6,ci --target=riscv32imac-unknown-none-elf esp-hal
cargo xtask build-package --features=esp32h2,ci --target=riscv32imac-unknown-none-elf esp-hal
- name: msrv RISCV (esp-wifi)
run: |
cargo xtask build-package --features=esp32c2,wifi,ble,esp-hal/unstable --target=riscv32imc-unknown-none-elf esp-wifi
cargo xtask build-package --features=esp32c3,wifi,ble,esp-hal/unstable --target=riscv32imc-unknown-none-elf esp-wifi
cargo xtask build-package --features=esp32c6,wifi,ble,esp-hal/unstable --target=riscv32imac-unknown-none-elf esp-wifi
cargo xtask build-package --features=esp32h2,ble,esp-hal/unstable --target=riscv32imac-unknown-none-elf esp-wifi
# Verify the MSRV for all Xtensa chips:
- name: msrv Xtensa (esp-hal) - name: msrv Xtensa (esp-hal)
run: | run: |
cargo xtask build-package --toolchain=esp --features=esp32,ci --target=xtensa-esp32-none-elf esp-hal cargo +esp xtask lint-packages --chips esp32,esp32s2,esp32s3
cargo xtask build-package --toolchain=esp --features=esp32s2,ci --target=xtensa-esp32s2-none-elf esp-hal
cargo xtask build-package --toolchain=esp --features=esp32s3,ci --target=xtensa-esp32s3-none-elf esp-hal
- name: msrv Xtensa (esp-wifi)
run: |
cargo xtask build-package --toolchain=esp --features=esp32,wifi,ble,esp-hal/unstable --target=xtensa-esp32-none-elf esp-wifi
cargo xtask build-package --toolchain=esp --features=esp32s2,wifi,esp-hal/unstable --target=xtensa-esp32s2-none-elf esp-wifi
cargo xtask build-package --toolchain=esp --features=esp32s3,wifi,ble,esp-hal/unstable --target=xtensa-esp32s3-none-elf esp-wifi
- name: msrv (esp-lp-hal)
run: |
cargo xtask build-package --features=esp32c6 --target=riscv32imac-unknown-none-elf esp-lp-hal
cargo xtask build-package --features=esp32s2 --target=riscv32imc-unknown-none-elf esp-lp-hal
cargo xtask build-package --features=esp32s3 --target=riscv32imc-unknown-none-elf esp-lp-hal
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Format # Format

View File

@ -90,7 +90,7 @@ pub fn generate_config(
continue; continue;
} }
markdown::write_doc_table_line(&mut doc_table, name, option); markdown::write_doc_table_line(&mut doc_table, name, option);
markdown::write_summary_table_line(&mut selected_config, &name, value); markdown::write_summary_table_line(&mut selected_config, name, value);
} }
write_out_file(format!("{file_name}_config_table.md"), doc_table); write_out_file(format!("{file_name}_config_table.md"), doc_table);

View File

@ -4,6 +4,8 @@ use serde::Serialize;
use super::{snake_case, value::Value, Error}; use super::{snake_case, value::Value, Error};
type CustomValidatorFn = Box<dyn Fn(&Value) -> Result<(), Error>>;
/// Configuration value validation functions. /// Configuration value validation functions.
#[derive(Serialize)] #[derive(Serialize)]
pub enum Validator { pub enum Validator {
@ -22,13 +24,10 @@ pub enum Validator {
/// type. /// type.
#[serde(serialize_with = "serialize_custom")] #[serde(serialize_with = "serialize_custom")]
#[serde(untagged)] #[serde(untagged)]
Custom(Box<dyn Fn(&Value) -> Result<(), Error>>), Custom(CustomValidatorFn),
} }
pub(crate) fn serialize_custom<S>( pub(crate) fn serialize_custom<S>(_: &CustomValidatorFn, serializer: S) -> Result<S::Ok, S::Error>
_: &Box<dyn Fn(&Value) -> Result<(), Error>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where where
S: serde::Serializer, S: serde::Serializer,
{ {
@ -75,25 +74,22 @@ impl Validator {
config_key: &str, config_key: &str,
actual_value: &Value, actual_value: &Value,
) { ) {
match self { if let Validator::Enumeration(values) = self {
Validator::Enumeration(values) => { for possible_value in values {
for possible_value in values {
writeln!(
stdout,
"cargo:rustc-check-cfg=cfg({config_key}_{})",
snake_case(possible_value)
)
.ok();
}
writeln!( writeln!(
stdout, stdout,
"cargo:rustc-cfg={config_key}_{}", "cargo:rustc-check-cfg=cfg({config_key}_{})",
snake_case(&actual_value.to_string()) snake_case(possible_value)
) )
.ok(); .ok();
} }
_ => (),
writeln!(
stdout,
"cargo:rustc-cfg={config_key}_{}",
snake_case(&actual_value.to_string())
)
.ok();
} }
} }
} }
@ -109,9 +105,9 @@ pub(crate) fn enumeration(values: &Vec<String>, value: &Value) -> Result<(), Err
Ok(()) Ok(())
} else { } else {
return Err(Error::parse( Err(Error::parse(
"Validator::Enumeration can only be used with string values", "Validator::Enumeration can only be used with string values",
)); ))
} }
} }

View File

@ -33,7 +33,7 @@ impl Value {
[b'0', b'x', ..] => i128::from_str_radix(&s[2..], 16), [b'0', b'x', ..] => i128::from_str_radix(&s[2..], 16),
[b'0', b'o', ..] => i128::from_str_radix(&s[2..], 8), [b'0', b'o', ..] => i128::from_str_radix(&s[2..], 8),
[b'0', b'b', ..] => i128::from_str_radix(&s[2..], 2), [b'0', b'b', ..] => i128::from_str_radix(&s[2..], 2),
_ => i128::from_str_radix(&s, 10), _ => s.parse(),
} }
.map_err(|_| Error::parse(format!("Expected valid intger value, found: '{s}'")))?; .map_err(|_| Error::parse(format!("Expected valid intger value, found: '{s}'")))?;

View File

@ -8,27 +8,24 @@
//! //!
//! - Placing statics and functions into RAM //! - Placing statics and functions into RAM
//! - Marking interrupt handlers //! - Marking interrupt handlers
//! - Automatically creating an `embassy` executor instance and spawning the //! - Blocking and Async `#[main]` macros
//! defined entry point
//! //!
//! These macros offer developers a convenient way to control memory placement //! These macros offer developers a convenient way to control memory placement
//! and define interrupt handlers in their embedded applications, allowing for //! and define interrupt handlers in their embedded applications, allowing for
//! optimized memory usage and precise handling of hardware interrupts. //! optimized memory usage and precise handling of hardware interrupts.
//! //!
//! Key Components: //! Key Components:
//! - [`interrupt`](attr.interrupt.html) - Attribute macro for marking //! - [`handler`](macro@handler) - Attribute macro for marking interrupt
//! interrupt handlers. Interrupt handlers are used to handle specific //! handlers. Interrupt handlers are used to handle specific hardware
//! hardware interrupts generated by peripherals. //! interrupts generated by peripherals.
//! //!
//! The macro allows users to specify the interrupt name explicitly or use //! - [`ram`](macro@ram) - Attribute macro for placing statics and functions
//! the function name to match the interrupt. //! into specific memory sections, such as SRAM or RTC RAM (slow or fast)
//! - [`main`](attr.main.html) - Creates a new `executor` instance and declares //! with different initialization options. See its documentation for details.
//! an application entry point spawning the corresponding function body as an //!
//! async task. //! - [`embassy::main`](macro@embassy_main) - Creates a new `executor` instance
//! - [`ram`](attr.ram.html) - Attribute macro for placing statics and //! and declares an application entry point spawning the corresponding
//! functions into specific memory sections, such as SRAM or RTC RAM (slow or //! function body as an async task.
//! fast) with different initialization options. See its documentation for
//! details.
//! //!
//! ## Examples //! ## Examples
//! //!
@ -49,7 +46,7 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
mod blocking_main; mod blocking;
mod builder; mod builder;
#[cfg(feature = "embassy")] #[cfg(feature = "embassy")]
mod embassy; mod embassy;
@ -209,7 +206,7 @@ pub fn embassy_main(args: TokenStream, item: TokenStream) -> TokenStream {
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn blocking_main(args: TokenStream, input: TokenStream) -> TokenStream { pub fn blocking_main(args: TokenStream, input: TokenStream) -> TokenStream {
blocking_main::main(args, input) blocking::main(args, input)
} }
/// Automatically implement the [Builder Lite] pattern for a struct. /// Automatically implement the [Builder Lite] pattern for a struct.

View File

@ -146,9 +146,6 @@ defmt = [
## Use externally connected PSRAM (`quad` by default, can be configured to `octal` via ESP_HAL_CONFIG_PSRAM_MODE) ## Use externally connected PSRAM (`quad` by default, can be configured to `octal` via ESP_HAL_CONFIG_PSRAM_MODE)
psram = [] psram = []
# This feature is intended for testing; you probably don't want to enable it:
ci = ["defmt", "bluetooth"]
#! ### Unstable APIs #! ### Unstable APIs
#! Unstable APIs are drivers and features that are not yet ready for general use. #! Unstable APIs are drivers and features that are not yet ready for general use.
#! They may be incomplete, have bugs, or be subject to change without notice. #! They may be incomplete, have bugs, or be subject to change without notice.

View File

@ -889,7 +889,7 @@ pub(crate) mod utils {
// Enable MOSI // Enable MOSI
spi.user().modify(|_, w| w.usr_mosi().clear_bit()); spi.user().modify(|_, w| w.usr_mosi().clear_bit());
// Load send buffer // Load send buffer
let len = (p_in_data.tx_data_bit_len + 31) / 32; let len = p_in_data.tx_data_bit_len.div_ceil(32);
if !p_tx_val.is_null() { if !p_tx_val.is_null() {
for i in 0..len { for i in 0..len {
spi.w(0) spi.w(0)

View File

@ -100,7 +100,7 @@ pub enum Chip {
} }
impl Chip { impl Chip {
pub fn target(&self) -> &str { pub fn target(&self) -> &'static str {
use Chip::*; use Chip::*;
match self { match self {
@ -118,7 +118,7 @@ impl Chip {
matches!(self, Esp32c6 | Esp32s2 | Esp32s3) matches!(self, Esp32c6 | Esp32s2 | Esp32s3)
} }
pub fn lp_target(&self) -> Result<&str> { pub fn lp_target(&self) -> Result<&'static str> {
use Chip::*; use Chip::*;
match self { match self {
@ -186,6 +186,20 @@ impl Config {
} }
} }
/// Create an empty configuration
pub fn empty() -> Self {
Self {
device: Device {
name: "".to_owned(),
arch: Arch::RiscV,
cores: Cores::Single,
peripherals: Vec::new(),
symbols: Vec::new(),
memory: Vec::new(),
},
}
}
/// The name of the device. /// The name of the device.
pub fn name(&self) -> String { pub fn name(&self) -> String {
self.device.name.clone() self.device.name.clone()

View File

@ -58,7 +58,7 @@ pub fn build_documentation(
_ => vec![Chip::Esp32c6], _ => vec![Chip::Esp32c6],
} }
} else { } else {
log::warn!("Package '{package}' does not have chip features, ignoring argument"); log::debug!("Package '{package}' does not have chip features, ignoring argument");
vec![] vec![]
}; };
@ -97,7 +97,7 @@ fn build_documentation_for_package(
// Ensure that the package/chip combination provided are valid: // Ensure that the package/chip combination provided are valid:
if let Some(chip) = chip { if let Some(chip) = chip {
if let Err(err) = crate::validate_package_chip(package, &chip) { if let Err(err) = package.validate_package_chip(&chip) {
log::warn!("{err}"); log::warn!("{err}");
return Ok(()); return Ok(());
} }
@ -192,15 +192,17 @@ fn cargo_doc(workspace: &Path, package: Package, chip: Option<Chip>) -> Result<P
// Determine the appropriate build target for the given package and chip, // Determine the appropriate build target for the given package and chip,
// if we're able to: // if we're able to:
let target = if let Some(ref chip) = chip { let target = if let Some(ref chip) = chip {
Some(crate::target_triple(package, chip)?) Some(package.target_triple(chip)?)
} else { } else {
None None
}; };
let mut features = vec![]; let mut features = vec![];
if let Some(chip) = chip { if let Some(chip) = &chip {
features.push(chip.to_string()); features.push(chip.to_string());
features.extend(apply_feature_rules(&package, Config::for_chip(&chip))); features.extend(package.feature_rules(Config::for_chip(&chip)));
} else {
features.extend(package.feature_rules(&Config::empty()));
} }
// Build up an array of command-line arguments to pass to `cargo`: // Build up an array of command-line arguments to pass to `cargo`:
@ -249,48 +251,6 @@ fn cargo_doc(workspace: &Path, package: Package, chip: Option<Chip>) -> Result<P
Ok(crate::windows_safe_path(&docs_path)) Ok(crate::windows_safe_path(&docs_path))
} }
fn apply_feature_rules(package: &Package, config: &Config) -> Vec<String> {
let chip_name = &config.name();
let mut features = vec![];
match package {
Package::EspBacktrace => features.push("defmt".to_owned()),
Package::EspConfig => features.push("build".to_owned()),
Package::EspHal => {
features.push("unstable".to_owned());
features.push("ci".to_owned());
match chip_name.as_str() {
"esp32" => features.push("psram".to_owned()),
"esp32s2" => features.push("psram".to_owned()),
"esp32s3" => features.push("psram".to_owned()),
_ => {}
};
}
Package::EspWifi => {
features.push("esp-hal/unstable".to_owned());
if config.contains("wifi") {
features.push("wifi".to_owned());
features.push("esp-now".to_owned());
features.push("sniffer".to_owned());
features.push("smoltcp/proto-ipv4".to_owned());
features.push("smoltcp/proto-ipv6".to_owned());
}
if config.contains("ble") {
features.push("ble".to_owned());
}
if config.contains("wifi") && config.contains("ble") {
features.push("coex".to_owned());
}
}
Package::EspHalEmbassy | Package::EspIeee802154 => {
features.push("esp-hal/unstable".to_owned());
},
_ => {}
}
features
}
fn patch_documentation_index_for_package( fn patch_documentation_index_for_package(
workspace: &Path, workspace: &Path,
package: &Package, package: &Package,
@ -454,7 +414,7 @@ fn generate_documentation_meta_for_package(
for chip in chips { for chip in chips {
// Ensure that the package/chip combination provided are valid: // Ensure that the package/chip combination provided are valid:
crate::validate_package_chip(&package, chip)?; package.validate_package_chip(chip)?;
// Build the context object required for rendering this particular build's // Build the context object required for rendering this particular build's
// information on the documentation index: // information on the documentation index:

View File

@ -6,10 +6,10 @@ use std::{
process::Command, process::Command,
}; };
use anyhow::{ensure, Context, Result}; use anyhow::{anyhow, Context, Result};
use cargo::CargoAction; use cargo::CargoAction;
use clap::ValueEnum; use clap::ValueEnum;
use esp_metadata::Chip; use esp_metadata::{Chip, Config};
use strum::{Display, EnumIter, IntoEnumIterator as _}; use strum::{Display, EnumIter, IntoEnumIterator as _};
use crate::{cargo::CargoArgsBuilder, firmware::Metadata}; use crate::{cargo::CargoArgsBuilder, firmware::Metadata};
@ -96,6 +96,135 @@ impl Package {
matches!(self, EspBuild | EspConfig | EspMetadata) matches!(self, EspBuild | EspConfig | EspMetadata)
} }
/// Given a device config, return the features which should be enabled for
/// this package.
pub fn feature_rules(&self, config: &Config) -> Vec<String> {
let mut features = vec![];
match self {
Package::EspBacktrace => features.push("defmt".to_owned()),
Package::EspConfig => features.push("build".to_owned()),
Package::EspHal => {
features.push("unstable".to_owned());
if config.contains("psram") {
// TODO this doesn't test octal psram (since `ESP_HAL_CONFIG_PSRAM_MODE`
// defaults to `quad`) as it would require a separate build
features.push("psram".to_owned())
}
if config.contains("usb0") {
features.push("usb-otg".to_owned());
}
if config.contains("bt") {
features.push("bluetooth".to_owned());
}
}
Package::EspWifi => {
features.push("esp-hal/unstable".to_owned());
features.push("defmt".to_owned());
if config.contains("wifi") {
features.push("wifi".to_owned());
features.push("esp-now".to_owned());
features.push("sniffer".to_owned());
features.push("smoltcp/proto-ipv4".to_owned());
features.push("smoltcp/proto-ipv6".to_owned());
}
if config.contains("ble") {
features.push("ble".to_owned());
}
if config.contains("coex") {
features.push("coex".to_owned());
}
}
Package::EspHalProcmacros => {
features.push("embassy".to_owned());
}
Package::EspHalEmbassy => {
features.push("esp-hal/unstable".to_owned());
features.push("defmt".to_owned());
features.push("executors".to_owned());
}
Package::EspIeee802154 => {
features.push("defmt".to_owned());
features.push("esp-hal/unstable".to_owned());
}
Package::EspLpHal => {
if config.contains("lp_core") {
features.push("embedded-io".to_owned());
}
}
Package::EspPrintln => {
features.push("auto".to_owned());
features.push("defmt-espflash".to_owned());
}
Package::EspStorage => {
features.push("storage".to_owned());
features.push("nor-flash".to_owned());
features.push("low-level".to_owned());
}
Package::EspBootloaderEspIdf => {
features.push("defmt".to_owned());
features.push("validation".to_owned());
}
Package::EspAlloc => {
features.push("defmt".to_owned());
}
_ => {}
}
features
}
/// Additional feature rules to test subsets of features for a package.
pub fn lint_feature_rules(&self, _config: &Config) -> Vec<Vec<String>> {
let mut cases = Vec::new();
match self {
Package::EspWifi => {
// minimal set of features that when enabled _should_ still compile
cases.push(vec![
"esp-hal/unstable".to_owned(),
"builtin-scheduler".to_owned(),
]);
}
_ => {}
}
cases
}
/// Return the target triple for a given package/chip pair.
pub fn target_triple(&self, chip: &Chip) -> Result<&'static str> {
if *self == Package::EspLpHal {
chip.lp_target()
} else {
Ok(chip.target())
}
}
/// Validate that the specified chip is valid for the specified package.
pub fn validate_package_chip(&self, chip: &Chip) -> Result<()> {
let device = Config::for_chip(chip);
let check = match self {
Package::EspIeee802154 => device.contains("ieee802154"),
Package::EspLpHal => chip.has_lp_core(),
Package::XtensaLx | Package::XtensaLxRt | Package::XtensaLxRtProcMacros => {
chip.is_xtensa()
}
Package::EspRiscvRt => chip.is_riscv(),
_ => true,
};
if check {
Ok(())
} else {
Err(anyhow!(
"Invalid chip provided for package '{}': '{}'",
self,
chip
))
}
}
} }
#[derive(Debug, Clone, Copy, Display, ValueEnum)] #[derive(Debug, Clone, Copy, Display, ValueEnum)]
@ -536,24 +665,3 @@ pub fn package_version(workspace: &Path, package: Package) -> Result<semver::Ver
pub fn windows_safe_path(path: &Path) -> PathBuf { pub fn windows_safe_path(path: &Path) -> PathBuf {
PathBuf::from(path.to_str().unwrap().to_string().replace("\\\\?\\", "")) PathBuf::from(path.to_str().unwrap().to_string().replace("\\\\?\\", ""))
} }
/// Return the target triple for a given package/chip pair.
pub fn target_triple(package: Package, chip: &Chip) -> Result<&str> {
if package == Package::EspLpHal {
chip.lp_target()
} else {
Ok(chip.target())
}
}
/// Validate that the specified chip is valid for the specified package.
pub fn validate_package_chip(package: &Package, chip: &Chip) -> Result<()> {
ensure!(
*package != Package::EspLpHal || chip.has_lp_core(),
"Invalid chip provided for package '{}': '{}'",
package,
chip
);
Ok(())
}

View File

@ -7,12 +7,11 @@ use std::{
use anyhow::{bail, ensure, Context as _, Result}; use anyhow::{bail, ensure, Context as _, Result};
use clap::{Args, Parser}; use clap::{Args, Parser};
use esp_metadata::{Arch, Chip, Config}; use esp_metadata::{Chip, Config};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use xtask::{ use xtask::{
cargo::{CargoAction, CargoArgsBuilder}, cargo::{CargoAction, CargoArgsBuilder},
firmware::Metadata, firmware::Metadata,
target_triple,
Package, Package,
Version, Version,
}; };
@ -165,7 +164,7 @@ struct LintPackagesArgs {
packages: Vec<Package>, packages: Vec<Package>,
/// Lint for a specific chip /// Lint for a specific chip
#[arg(long, value_enum, default_values_t = Chip::iter())] #[arg(long, value_enum, value_delimiter = ',', default_values_t = Chip::iter())]
chips: Vec<Chip>, chips: Vec<Chip>,
/// Automatically apply fixes /// Automatically apply fixes
@ -253,7 +252,7 @@ fn main() -> Result<()> {
fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Result<()> { fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Result<()> {
// Ensure that the package/chip combination provided are valid: // Ensure that the package/chip combination provided are valid:
xtask::validate_package_chip(&args.package, &args.chip)?; args.package.validate_package_chip(&args.chip)?;
// If the 'esp-hal' package is specified, what we *really* want is the // If the 'esp-hal' package is specified, what we *really* want is the
// 'examples' package instead: // 'examples' package instead:
@ -305,7 +304,7 @@ fn build_examples(
out_path: PathBuf, out_path: PathBuf,
) -> Result<()> { ) -> Result<()> {
// Determine the appropriate build target for the given package and chip: // Determine the appropriate build target for the given package and chip:
let target = target_triple(args.package, &args.chip)?; let target = args.package.target_triple(&args.chip)?;
if examples if examples
.iter() .iter()
@ -346,7 +345,7 @@ fn build_examples(
fn run_example(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path) -> Result<()> { fn run_example(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path) -> Result<()> {
// Determine the appropriate build target for the given package and chip: // Determine the appropriate build target for the given package and chip:
let target = target_triple(args.package, &args.chip)?; let target = args.package.target_triple(&args.chip)?;
// Filter the examples down to only the binary we're interested in, assuming it // Filter the examples down to only the binary we're interested in, assuming it
// actually supports the specified chip: // actually supports the specified chip:
@ -375,7 +374,7 @@ fn run_example(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path)
fn run_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path) -> Result<()> { fn run_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path) -> Result<()> {
// Determine the appropriate build target for the given package and chip: // Determine the appropriate build target for the given package and chip:
let target = target_triple(args.package, &args.chip)?; let target = args.package.target_triple(&args.chip)?;
// Filter the examples down to only the binaries we're interested in // Filter the examples down to only the binaries we're interested in
let mut examples: Vec<Metadata> = examples let mut examples: Vec<Metadata> = examples
@ -451,7 +450,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
let package_path = xtask::windows_safe_path(&workspace.join("hil-test")); let package_path = xtask::windows_safe_path(&workspace.join("hil-test"));
// Determine the appropriate build target for the given package and chip: // Determine the appropriate build target for the given package and chip:
let target = target_triple(Package::HilTest, &args.chip)?; let target = Package::HilTest.target_triple(&args.chip)?;
// Load all tests which support the specified chip and parse their metadata: // Load all tests which support the specified chip and parse their metadata:
let mut tests = xtask::firmware::load(&package_path.join("tests"))? let mut tests = xtask::firmware::load(&package_path.join("tests"))?
@ -636,188 +635,36 @@ fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> {
let mut packages = args.packages; let mut packages = args.packages;
packages.sort(); packages.sort();
for package in packages { for package in packages.iter().filter(|p| p.is_published()) {
let path = workspace.join(package.to_string());
// Unfortunately each package has its own unique requirements for // Unfortunately each package has its own unique requirements for
// building, so we need to handle each individually (though there // building, so we need to handle each individually (though there
// is *some* overlap) // is *some* overlap)
for chip in &args.chips { for chip in &args.chips {
let device = Config::for_chip(chip); let device = Config::for_chip(chip);
match package { if let Err(_) = package.validate_package_chip(chip) {
Package::EspBacktrace => { continue;
lint_package( }
chip,
&path, let feature_sets = [
&[ vec![package.feature_rules(device)], // initially test all features
"--no-default-features", package.lint_feature_rules(device), // add separate test cases
&format!("--target={}", chip.target()), ]
], .concat();
&[&format!("{chip},defmt")],
args.fix, for mut features in feature_sets {
package.build_on_host(), if package.has_chip_features() {
)?; features.push(device.name())
} }
Package::EspHal => { lint_package(
let mut features = format!("{chip},ci,unstable"); workspace,
*package,
// Cover all esp-hal features where a device is supported chip,
if device.contains("usb0") { &["--no-default-features"],
features.push_str(",usb-otg") &features,
} args.fix,
if device.contains("bt") { )?;
features.push_str(",bluetooth")
}
if device.contains("psram") {
// TODO this doesn't test octal psram (since `ESP_HAL_CONFIG_PSRAM_MODE`
// defaults to `quad`) as it would require a separate build
features.push_str(",psram")
}
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[&features],
args.fix,
package.build_on_host(),
)?;
}
Package::EspHalEmbassy => {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[&format!("{chip},executors,defmt,esp-hal/unstable")],
args.fix,
package.build_on_host(),
)?;
}
Package::EspIeee802154 => {
if device.contains("ieee802154") {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[&format!("{chip},defmt,esp-hal/unstable")],
args.fix,
package.build_on_host(),
)?;
}
}
Package::EspLpHal => {
if device.contains("lp_core") {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.lp_target().unwrap())],
&[&format!("{chip},embedded-io")],
args.fix,
package.build_on_host(),
)?;
}
}
Package::EspPrintln => {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[&format!("{chip},defmt-espflash")],
args.fix,
package.build_on_host(),
)?;
}
Package::EspRiscvRt => {
if matches!(device.arch(), Arch::RiscV) {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[""],
args.fix,
package.build_on_host(),
)?;
}
}
Package::EspStorage => {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[&format!("{chip},storage,nor-flash,low-level")],
args.fix,
package.build_on_host(),
)?;
}
Package::EspWifi => {
let minimal_features = format!("{chip},esp-hal/unstable,builtin-scheduler");
let mut all_features = minimal_features.clone();
all_features.push_str(",defmt");
if device.contains("wifi") {
all_features.push_str(",esp-now,sniffer")
}
if device.contains("bt") {
all_features.push_str(",ble")
}
if device.contains("coex") {
all_features.push_str(",coex")
}
lint_package(
chip,
&path,
&[
&format!("--target={}", chip.target()),
"--no-default-features",
],
&[&minimal_features, &all_features],
args.fix,
package.build_on_host(),
)?;
}
Package::XtensaLx => {
if matches!(device.arch(), Arch::Xtensa) {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[""],
args.fix,
package.build_on_host(),
)?
}
}
Package::XtensaLxRt => {
if matches!(device.arch(), Arch::Xtensa) {
lint_package(
chip,
&path,
&[&format!("--target={}", chip.target())],
&[&format!("{chip}")],
args.fix,
package.build_on_host(),
)?
}
}
// We will *not* check the following packages with `clippy`; this
// may or may not change in the future:
Package::Examples | Package::HilTest | Package::QaTest => {}
// By default, no `clippy` arguments are required:
_ => lint_package(chip, &path, &[], &[], args.fix, package.build_on_host())?,
} }
} }
} }
@ -826,54 +673,53 @@ fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> {
} }
fn lint_package( fn lint_package(
workspace: &Path,
package: Package,
chip: &Chip, chip: &Chip,
path: &Path,
args: &[&str], args: &[&str],
feature_sets: &[&str], features: &[String],
fix: bool, fix: bool,
build_on_host: bool,
) -> Result<()> { ) -> Result<()> {
for features in feature_sets { log::info!(
log::info!( "Linting package: {} ({}, features: {:?})",
"Linting package: {} ({}, features: {})", package,
path.display(), chip,
chip, features
features );
);
let builder = CargoArgsBuilder::default().subcommand("clippy"); let path = workspace.join(package.to_string());
let mut builder = if chip.is_xtensa() { let mut builder = CargoArgsBuilder::default().subcommand("clippy");
let builder = if build_on_host {
builder
} else {
builder.arg("-Zbuild-std=core,alloc")
};
let mut builder = if !package.build_on_host() {
if chip.is_xtensa() {
// We only overwrite Xtensas so that externally set nightly/stable toolchains // We only overwrite Xtensas so that externally set nightly/stable toolchains
// are not overwritten. // are not overwritten.
builder.toolchain("esp") builder = builder.arg("-Zbuild-std=core,alloc");
} else { builder = builder.toolchain("esp");
builder
};
for arg in args {
builder = builder.arg(arg.to_string());
} }
builder = builder.arg(format!("--features={features}")); builder.target(package.target_triple(chip)?)
} else {
builder
};
let builder = if fix { for arg in args {
builder.arg("--fix").arg("--lib").arg("--allow-dirty") builder = builder.arg(arg.to_string());
} else {
builder.arg("--").arg("-D").arg("warnings").arg("--no-deps")
};
let cargo_args = builder.build();
xtask::cargo::run_with_env(&cargo_args, path, [("CI", "1")], false)?;
} }
builder = builder.arg(format!("--features={}", features.join(",")));
let builder = if fix {
builder.arg("--fix").arg("--lib").arg("--allow-dirty")
} else {
builder.arg("--").arg("-D").arg("warnings").arg("--no-deps")
};
let cargo_args = builder.build();
xtask::cargo::run_with_env(&cargo_args, &path, [("CI", "1")], false)?;
Ok(()) Ok(())
} }
@ -964,7 +810,7 @@ fn run_doc_tests(workspace: &Path, args: ExampleArgs) -> Result<()> {
// Determine the appropriate build target, and cargo features for the given // Determine the appropriate build target, and cargo features for the given
// package and chip: // package and chip:
let target = target_triple(args.package, &chip)?; let target = args.package.target_triple(&chip)?;
let features = vec![chip.to_string(), "unstable".to_string()]; let features = vec![chip.to_string(), "unstable".to_string()];
// We need `nightly` for building the doc tests, unfortunately: // We need `nightly` for building the doc tests, unfortunately: