Use cargo-batch to build tests and examples, avoid linting so much (#4108)

* Use cargo-batch

* Run CI on mac runner

* Rely on MSRV and nightly jobs to lint

* Build docs separately

* Don't copy examples - fix builds on stable

* Run everything by default, set CI env var in ci command

* Run batched commands with RUSTC_BOOTSTRAP enabled

* Force cargo-batch to correctly ignore unstable option

* Test with nightly

* Use a persistent target folder, remove cache

* Don't delete the lp examples

* Restore target dir

* Build with stable again

* Fix rebase fail

* Remove handling tests

* Remove redundant code

* Restore repeated test run option

* Add simpler cargo check

* Introduce check-packages

* Remove stabilized -Zdoctest-xcompile

* Clean up commented code

* Remove more stuff

* Fix uart_uhci test

* No badger for us
This commit is contained in:
Dániel Buga 2025-09-18 13:25:11 +02:00 committed by GitHub
parent 8e58a753b6
commit 1eed542f64
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
94 changed files with 1063 additions and 402 deletions

View File

@ -4,7 +4,7 @@
# updates which must be made to the CI workflow in order to reflect this; the
# changes are:
#
# 1.) In the 'esp-hal' job, add the name of the chip to the `matrix.soc` array.
# 1.) In the 'esp-hal' job, add the appropriate build and semver-check command.
# 1a.) If the device has a low-power core (which is supported in
# `esp-lp-hal`), then update the `if` condition to build prerequisites.
# 2.) In the 'msrv' job, add checks as needed for the new chip.
@ -40,8 +40,7 @@ jobs:
# Build Packages
esp-hal:
name: esp-hal (${{ matrix.device.soc }})
runs-on: ubuntu-latest
runs-on: macos-m1-self-hosted
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target
CI: 1
@ -51,20 +50,6 @@ jobs:
GATEWAY_IP: 1.1.1.1
HOST_IP: 1.1.1.1
strategy:
fail-fast: false
matrix:
device: [
# RISC-V devices:
{ soc: "esp32c2", toolchain: "stable" },
{ soc: "esp32c3", toolchain: "stable" },
{ soc: "esp32c6", toolchain: "stable" },
{ soc: "esp32h2", toolchain: "stable" },
# Xtensa devices:
{ soc: "esp32", toolchain: "esp" },
{ soc: "esp32s2", toolchain: "esp" },
{ soc: "esp32s3", toolchain: "esp" },
]
steps:
- uses: actions/checkout@v4
@ -79,26 +64,36 @@ jobs:
target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf
toolchain: stable
components: rust-src
# Install the Rust nightly toolchain for RISC-V devices, because of `-Zdoctest-xcompile`
- uses: dtolnay/rust-toolchain@v1
with:
target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf
toolchain: nightly
components: rust-src
- uses: Swatinem/rust-cache@v2
with:
prefix-key: "ci-${{ matrix.device.soc }}"
cache-all-crates: true
- name: Setup cargo-batch
run: |
if ! command -v cargo-batch &> /dev/null; then
cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked --force
fi
- name: Build and Check
shell: bash
run: cargo xcheck ci ${{ matrix.device.soc }} --toolchain ${{ matrix.device.toolchain }}
run: |
# lints and docs are checked separately
cargo xcheck ci esp32 --toolchain esp --no-lint --no-docs
cargo xcheck ci esp32s2 --toolchain esp --no-lint --no-docs
cargo xcheck ci esp32s3 --toolchain esp --no-lint --no-docs
cargo xcheck ci esp32c2 --toolchain stable --no-lint --no-docs
cargo xcheck ci esp32c3 --toolchain stable --no-lint --no-docs
cargo xcheck ci esp32c6 --toolchain stable --no-lint --no-docs
cargo xcheck ci esp32h2 --toolchain stable --no-lint --no-docs
- name: Semver-Check
shell: bash
# always invokes +esp internally
run: cargo xcheck semver-check --chips ${{ matrix.device.soc }} check
run: |
cargo xcheck semver-check --chips esp32 check
cargo xcheck semver-check --chips esp32s2 check
cargo xcheck semver-check --chips esp32s3 check
cargo xcheck semver-check --chips esp32c2 check
cargo xcheck semver-check --chips esp32c3 check
cargo xcheck semver-check --chips esp32c6 check
cargo xcheck semver-check --chips esp32h2 check
detect-extras-runner:
uses: ./.github/workflows/check_runner.yml
@ -127,11 +122,39 @@ jobs:
- name: Build ieee802154-sniffer
run: cd extras/ieee802154-sniffer && cargo build
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Install the Rust toolchain for Xtensa devices:
- uses: esp-rs/xtensa-toolchain@v1.6
with:
version: 1.88.0.0
# Install the Rust stable toolchain for RISC-V devices:
- uses: dtolnay/rust-toolchain@v1
with:
target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf
toolchain: stable
components: rust-src
# Install the Rust nightly toolchain for RISC-V devices:
- uses: dtolnay/rust-toolchain@v1
with:
target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf
toolchain: nightly
components: rust-src
- name: Build docs
shell: bash
run: |
cargo xtask build documentation
# --------------------------------------------------------------------------
# MSRV
msrv:
runs-on: macos-m1-self-hosted
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@ -140,7 +163,7 @@ jobs:
version: ${{ env.MSRV }}
- uses: dtolnay/rust-toolchain@v1
with:
target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf
target: riscv32imc-unknown-none-elf,riscv32imac-unknown-none-elf,x86_64-unknown-linux-gnu
toolchain: ${{ env.MSRV }}
components: rust-src,clippy
@ -165,7 +188,7 @@ jobs:
with:
primary-runner: macos-m1-self-hosted
fallback-runner: ubuntu-latest
host-tests:
needs: detect-host-tests-runner
runs-on: ${{ needs.detect-host-tests-runner.outputs.selected-runner }}
@ -188,7 +211,7 @@ jobs:
# Check metadata generation for all packages:
- run: cargo xtask update-metadata --check
# Run host tests for all applicable packages:
# Run host tests for all applicable packages:
- run: cargo xtask host-tests
# --------------------------------------------------------------------------
@ -211,4 +234,3 @@ jobs:
'./**/*.rs'
'./**/*.md'
'./**/*.toml'

View File

@ -37,6 +37,15 @@ jobs:
toolchain: nightly
components: rust-src, clippy, rustfmt
- name: Setup cargo-batch
run: |
# Note this is linux-only, but the macOS runner has cargo batch installed
if ! command -v cargo-batch &> /dev/null; then
mkdir -p $HOME/.cargo/bin
curl -L https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.6.0/cargo-batch > $HOME/.cargo/bin/cargo-batch
chmod +x $HOME/.cargo/bin/cargo-batch
fi
- name: Build and Check
shell: bash
run: cargo xtask ci ${{ matrix.device }} --toolchain nightly

View File

@ -11,3 +11,6 @@ target = "riscv32imac-unknown-none-elf" # ESP32-C6
rustflags = [
"-C", "link-arg=-Tlink.x",
]
[unstable]
build-std = ["core"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -1,3 +1,10 @@
[target.'cfg(target_arch = "riscv32")']
runner = "espflash flash --monitor"
rustflags = [
"-C", "link-arg=-Tlinkall.x",
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
@ -14,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -5,8 +5,20 @@ rustflags = [
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
# GNU LD
"-C", "link-arg=-Wl,-Tlinkall.x",
"-C", "link-arg=-nostartfiles",
# LLD
# "-C", "link-arg=-Tlinkall.x",
# "-C", "linker=rust-lld",
]
[env]
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -1,3 +1,10 @@
[target.'cfg(target_arch = "riscv32")']
runner = "espflash flash --monitor"
rustflags = [
"-C", "link-arg=-Tlinkall.x",
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
@ -14,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["alloc", "core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -1,3 +1,10 @@
[target.'cfg(target_arch = "riscv32")']
runner = "espflash flash --monitor"
rustflags = [
"-C", "link-arg=-Tlinkall.x",
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
@ -14,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -1,3 +1,10 @@
[target.'cfg(target_arch = "riscv32")']
runner = "espflash flash --monitor"
rustflags = [
"-C", "link-arg=-Tlinkall.x",
"-C", "force-frame-pointers",
]
[target.'cfg(target_arch = "xtensa")']
runner = "espflash flash --monitor"
rustflags = [
@ -14,4 +21,4 @@ rustflags = [
ESP_LOG = "info"
[unstable]
build-std = ["core"]
build-std = ["core", "alloc"]

View File

@ -21,6 +21,9 @@ rustflags = [
ESP_LOG = "info"
SSID = "SSID"
PASSWORD = "PASSWORD"
STATIC_IP = "1.1.1.1 "
GATEWAY_IP = "1.1.1.1"
HOST_IP = "1.1.1.1"
[unstable]
build-std = ["alloc", "core"]

View File

@ -7,207 +7,207 @@ publish = false
[lib]
name = "hil_test"
[[test]]
[[bin]]
name = "aes"
harness = false
[[test]]
[[bin]]
name = "alloc_psram"
harness = false
required-features = ["psram"]
[[test]]
[[bin]]
name = "clock_monitor"
harness = false
[[test]]
[[bin]]
name = "critical_section"
harness = false
[[test]]
[[bin]]
name = "delay_async"
harness = false
[[test]]
[[bin]]
name = "dma_macros"
harness = false
[[test]]
[[bin]]
name = "dma_mem2mem"
harness = false
[[test]]
[[bin]]
name = "ecc"
harness = false
[[test]]
[[bin]]
name = "flip_link"
harness = false
[[test]]
[[bin]]
name = "gpio"
harness = false
[[test]]
[[bin]]
name = "gpio_custom_handler"
harness = false
[[test]]
[[bin]]
name = "interrupt"
harness = false
[[test]]
[[bin]]
name = "i2c"
harness = false
[[test]]
[[bin]]
name = "init"
harness = false
[[test]]
[[bin]]
name = "i2s"
harness = false
[[test]]
[[bin]]
name = "i2s_parallel"
harness = false
[[test]]
[[bin]]
name = "lcd_cam"
harness = false
[[test]]
[[bin]]
name = "lcd_cam_i8080"
harness = false
[[test]]
[[bin]]
name = "lcd_cam_i8080_async"
harness = false
[[test]]
[[bin]]
name = "misc_simple"
harness = false
[[test]]
[[bin]]
name = "qspi"
harness = false
[[test]]
[[bin]]
name = "rng"
harness = false
[[test]]
[[bin]]
name = "spi_full_duplex"
harness = false
[[test]]
[[bin]]
name = "spi_half_duplex_read"
harness = false
[[test]]
[[bin]]
name = "spi_half_duplex_write"
harness = false
[[test]]
[[bin]]
name = "spi_half_duplex_write_psram"
harness = false
[[test]]
[[bin]]
name = "spi_slave"
harness = false
[[test]]
[[bin]]
name = "storage_read_app_desc"
harness = false
[[test]]
[[bin]]
name = "stack_protector"
harness = false
[[test]]
[[bin]]
name = "parl_io"
harness = false
[[test]]
[[bin]]
name = "parl_io_tx"
harness = false
[[test]]
[[bin]]
name = "parl_io_tx_async"
harness = false
[[test]]
[[bin]]
name = "pcnt"
harness = false
[[test]]
[[bin]]
name = "rmt"
harness = false
[[test]]
[[bin]]
name = "rsa"
harness = false
[[test]]
[[bin]]
name = "sha"
harness = false
[[test]]
[[bin]]
name = "uart"
harness = false
[[test]]
[[bin]]
name = "uart_async"
harness = false
required-features = ["embassy"]
[[test]]
[[bin]]
name = "uart_uhci"
harness = false
required-features = ["embassy"]
[[test]]
[[bin]]
name = "uart_regression"
harness = false
[[test]]
[[bin]]
name = "uart_tx_rx_async"
harness = false
[[test]]
[[bin]]
name = "embassy_timers_executors"
harness = false
required-features = ["embassy"]
[[test]]
[[bin]]
name = "embassy_interrupt_executor"
harness = false
required-features = ["embassy"]
[[test]]
[[bin]]
name = "embassy_interrupt_spi_dma"
harness = false
required-features = ["embassy"]
[[test]]
[[bin]]
name = "systimer"
harness = false
[[test]]
[[bin]]
name = "twai"
harness = false
[[test]]
[[bin]]
name = "esp_radio_floats"
harness = false
required-features = ["esp-radio", "esp-alloc"]
[[test]]
[[bin]]
name = "esp_radio_ble_controller"
harness = false
required-features = ["esp-radio", "esp-alloc"]
[[test]]
[[bin]]
name = "esp_radio_init"
harness = false
required-features = ["esp-radio", "esp-alloc"]
@ -241,13 +241,12 @@ portable-atomic = "1.11.0"
static_cell = { version = "2.1.0" }
semihosting = { version = "0.1", features= ["stdio", "panic-handler"] }
[dev-dependencies]
crypto-bigint = { version = "0.5.5", default-features = false }
digest = { version = "0.10.7", default-features = false }
elliptic-curve = { version = "0.13.8", default-features = false, features = ["sec1"] }
embassy-executor = { version = "0.9.0", default-features = false }
# Add the `embedded-test/defmt` feature for more verbose testing
embedded-test = { version = "0.7.0-alpha.3", default-features = false, features = ["embassy", "external-executor", "semihosting"] }
embedded-test = { version = "0.7.0-alpha.3", default-features = false, features = ["embassy", "external-executor", "semihosting"], git = "https://github.com/probe-rs/embedded-test", branch = "next" }
embedded-test-macros = { version = "0.7.0-alpha.2", default-features = false, git = "https://github.com/probe-rs/embedded-test", branch = "next" }
hex-literal = "1.0.0"
nb = "1.1.0"
p192 = { version = "0.13.0", default-features = false, features = ["arithmetic"] }

View File

@ -344,7 +344,6 @@ fn run_unaligned_dma_tests<const MAX_SHIFT: usize>(memory: &mut [u8]) {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 10, executor = hil_test::Executor::new())] // defmt slows the tests down a bit
mod tests {
use super::*;

View File

@ -19,7 +19,6 @@ use hil_test as _;
extern crate alloc;
#[cfg(test)]
#[embedded_test::tests]
mod tests {
use alloc::vec::Vec as AllocVec;

View File

@ -13,7 +13,6 @@ struct Context<'a> {
rtc: Rtc<'a>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -39,7 +39,6 @@ fn test_access_at_priority(peripherals: Peripherals, priority: Priority) {
loop {}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -72,7 +72,6 @@ async fn test_async_delay_ms(mut timer: impl DelayNs, duration: u32) {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 2, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -22,7 +22,6 @@ pub(crate) const fn compute_circular_size(size: usize, chunk_size: usize) -> usi
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
// defmt::* is load-bearing, it ensures that the assert in dma_buffers! is not

View File

@ -20,7 +20,6 @@ struct Context {
mem2mem: Mem2Mem<'static, Blocking>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -48,7 +48,6 @@ struct Context<'a> {
_rng_source: TrngSource<'a>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 10)]
mod tests {
use super::*;

View File

@ -71,7 +71,6 @@ struct Context {
cpu_control: CpuControl<'static>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod test {
use esp_hal_embassy::Callbacks;

View File

@ -76,7 +76,6 @@ async fn interrupt_driven_task(i2s_tx: esp_hal::i2s::master::I2s<'static, Blocki
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test {
use super::*;

View File

@ -111,7 +111,6 @@ fn set_up_embassy_with_systimer(peripherals: Peripherals) {
esp_hal_embassy::init(systimer.alarm0);
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod test {

View File

@ -37,7 +37,6 @@ fn _esp_radio_can_be_reinited() {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -48,7 +48,6 @@ cfg_if::cfg_if! {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -44,7 +44,6 @@ async fn try_init(
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = esp_hal_embassy::Executor::new())]
mod tests {
use super::*;

View File

@ -9,7 +9,6 @@
use hil_test as _;
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
#[test]

View File

@ -118,7 +118,6 @@ async fn edge_counter_task(
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -104,7 +104,6 @@ async fn sense_pin(gpio: AnyPin<'static>, done: &'static Signal<CriticalSectionR
done.signal(());
}
#[cfg(test)]
#[embedded_test::tests(executor = hil_test::Executor::new(), default_timeout = 3)]
mod tests {

View File

@ -57,7 +57,6 @@ async fn waiting_blocking_task() {
esp_hal::delay::Delay::new().delay_millis(10);
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -94,7 +94,6 @@ fn enable_loopback() {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -14,7 +14,6 @@ use esp_hal::{
};
use hil_test as _;
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -17,7 +17,6 @@ use esp_hal::{
};
use hil_test as _;
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -58,7 +58,6 @@ fn interrupt20() {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -32,7 +32,6 @@ struct Context {
dma_rx_buf: DmaRxBuf,
}
#[cfg(test)]
#[embedded_test::tests]
mod tests {
use super::*;

View File

@ -44,7 +44,6 @@ struct Context<'d> {
dma_buf: DmaTxBuf,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -27,7 +27,6 @@ struct Context<'d> {
dma_buf: DmaTxBuf,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -30,7 +30,6 @@ struct Context {
p: Peripherals,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 2)]
mod tests {
use super::*;

View File

@ -47,7 +47,6 @@ struct Context {
extra_data_pins: [AnyPin<'static>; 7],
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -44,7 +44,6 @@ struct Context {
pcnt_unit: Unit<'static, 0>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use defmt::info;

View File

@ -44,7 +44,6 @@ struct Context {
pcnt_unit: Unit<'static, 0>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use defmt::info;

View File

@ -20,7 +20,6 @@ struct Context<'d> {
delay: Delay,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -183,7 +183,6 @@ fn execute_write(
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -228,7 +228,6 @@ fn do_rmt_single_shot<const TX_LEN: usize>(
Ok(())
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 1, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -8,7 +8,6 @@
use hil_test as _;
#[cfg(test)]
#[embedded_test::tests(default_timeout = 5)]
mod tests {
use esp_hal::rng::{Rng, Trng, TrngSource};

View File

@ -63,7 +63,6 @@ const fn compute_mprime(modulus: &U512) -> u32 {
(-1 * m_inv as i64 & (u32::MAX as i64)) as u32
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 5, executor = hil_test::Executor::new())]
mod tests {
use esp_hal::rsa::{RsaBackend, RsaContext};

View File

@ -153,7 +153,6 @@ pub struct Context {
sha: Sha<'static>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 6)]
mod tests {
use super::*;

View File

@ -58,7 +58,6 @@ struct Context {
pcnt_unit: Unit<'static, 0>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -24,7 +24,6 @@ struct Context {
miso_mirror: Output<'static>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -101,7 +101,6 @@ fn perform_spidmabus_writes_are_correctly_by_pcnt(ctx: Context, mode: DataMode)
assert_eq!(unit.value(), (6 * DMA_BUFFER_SIZE) as _);
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -45,7 +45,6 @@ struct Context {
pcnt_source: InputSignal<'static>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -94,7 +94,6 @@ impl BitbangSpi {
}
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 10, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -38,7 +38,6 @@ fn trigger_overflow() {
stack[SIZE - 1] = 42;
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -12,7 +12,6 @@ use esp_bootloader_esp_idf::EspAppDesc;
use esp_storage::FlashStorage;
use hil_test as _;
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -88,7 +88,6 @@ fn target_fail_test_if_called_twice() {
assert!(COUNTER.load(Ordering::Relaxed) != 2);
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -18,7 +18,6 @@ struct Context {
twai: twai::Twai<'static, Blocking>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;

View File

@ -23,7 +23,6 @@ struct Context {
delay: Delay,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use esp_hal::gpio::Pin;

View File

@ -46,7 +46,6 @@ async fn long_string_reader(
signal.signal(());
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {

View File

@ -6,7 +6,6 @@
#![no_std]
#![no_main]
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use esp_hal::{

View File

@ -17,7 +17,6 @@ struct Context {
tx: UartTx<'static, Async>,
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
mod tests {
use embassy_futures::{

View File

@ -30,7 +30,6 @@ const BAUDRATE: u32 = 921600;
// const BAUDRATES: &[u32] = &[9600, 19200, 28800, 38400, 57600, 76800, 115200, 230400, 460800,
// 576000, 921600];
#[cfg(test)]
#[embedded_test::tests(default_timeout = 5, executor = hil_test::Executor::new())]
mod tests {
use super::*;

View File

@ -1,6 +1,7 @@
//! Tools for working with Cargo.
use std::{
collections::HashMap,
ffi::OsStr,
path::{Path, PathBuf},
process::{Command, Stdio},
@ -117,14 +118,40 @@ fn get_cargo() -> String {
/// A builder for constructing cargo command line arguments.
#[derive(Clone, Debug, Default)]
pub struct CargoArgsBuilder {
toolchain: Option<String>,
subcommand: String,
target: Option<String>,
features: Vec<String>,
args: Vec<String>,
pub(crate) artifact_name: String,
pub(crate) config_path: Option<PathBuf>,
pub(crate) manifest_path: Option<PathBuf>,
pub(crate) toolchain: Option<String>,
pub(crate) subcommand: String,
pub(crate) target: Option<String>,
pub(crate) features: Vec<String>,
pub(crate) args: Vec<String>,
pub(crate) configs: Vec<String>,
pub(crate) env_vars: HashMap<String, String>,
}
impl CargoArgsBuilder {
pub fn new(artifact_name: String) -> Self {
Self {
artifact_name,
..Default::default()
}
}
/// Set the path to the Cargo manifest file (Cargo.toml)
#[must_use]
pub fn manifest_path(mut self, path: PathBuf) -> Self {
self.manifest_path = Some(path);
self
}
/// Set the path to the Cargo configuration file (.cargo/config.toml)
#[must_use]
pub fn config_path(mut self, path: PathBuf) -> Self {
self.config_path = Some(path);
self
}
/// Set the Rust toolchain to use.
#[must_use]
pub fn toolchain<S>(mut self, toolchain: S) -> Self
@ -193,6 +220,34 @@ impl CargoArgsBuilder {
self
}
/// Adds a raw configuration argument (--config, -Z, ...)
#[must_use]
pub fn config<S>(mut self, arg: S) -> Self
where
S: Into<String>,
{
self.add_config(arg);
self
}
/// Adds a raw configuration argument (--config, -Z, ...)
pub fn add_config<S>(&mut self, arg: S) -> &mut Self
where
S: Into<String>,
{
self.configs.push(arg.into());
self
}
/// Adds an environment variable
pub fn add_env_var<S>(&mut self, key: S, value: S) -> &mut Self
where
S: Into<String>,
{
self.env_vars.insert(key.into(), value.into());
self
}
/// Build the final list of cargo command line arguments.
#[must_use]
pub fn build(&self) -> Vec<String> {
@ -204,10 +259,24 @@ impl CargoArgsBuilder {
args.push(self.subcommand.clone());
if let Some(manifest_path) = &self.manifest_path {
args.push("--manifest-path".to_string());
args.push(manifest_path.display().to_string());
}
if let Some(config_path) = &self.config_path {
args.push("--config".to_string());
args.push(config_path.display().to_string());
}
if let Some(ref target) = self.target {
args.push(format!("--target={target}"));
}
for config in self.configs.iter() {
args.push(config.clone());
}
if !self.features.is_empty() {
args.push(format!("--features={}", self.features.join(",")));
}
@ -221,6 +290,199 @@ impl CargoArgsBuilder {
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
struct BatchKey {
config_file: String,
toolchain: Option<String>,
config: Vec<String>,
env_vars: Vec<(String, String)>,
}
impl BatchKey {
fn from_command(command: &CargoArgsBuilder) -> Self {
let config_file = if let Some(config_path) = &command.config_path {
std::fs::read_to_string(config_path).unwrap_or_default()
} else {
String::new()
};
Self {
toolchain: command.toolchain.clone(),
config: command.configs.clone(),
config_file,
env_vars: {
let mut env_vars = command
.env_vars
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<_>>();
env_vars.sort();
env_vars
},
}
}
}
#[derive(Debug)]
pub struct CargoCommandBatcher {
commands: HashMap<BatchKey, Vec<CargoArgsBuilder>>,
}
#[derive(Debug, Clone)]
pub struct BuiltCommand {
pub artifact_name: String,
pub command: Vec<String>,
pub env_vars: Vec<(String, String)>,
}
impl BuiltCommand {
pub fn run(&self, capture: bool) -> Result<String> {
let env_vars = self.env_vars.clone();
let cwd = std::env::current_dir()?;
run_with_env(&self.command, &cwd, env_vars, capture)
}
}
impl CargoCommandBatcher {
pub fn new() -> Self {
Self {
commands: HashMap::new(),
}
}
pub fn push(&mut self, command: CargoArgsBuilder) {
let key = BatchKey::from_command(&command);
self.commands.entry(key).or_default().push(command);
}
fn build_for_cargo_batch(&self) -> Vec<BuiltCommand> {
let mut all = Vec::new();
for (key, group) in self.commands.iter() {
// No need to batch if there's only one command
if group.len() == 1 {
all.push(Self::build_one_for_cargo(&group[0]));
continue;
}
let mut command = Vec::new();
if let Some(tc) = key.toolchain.as_ref() {
command.push(format!("+{tc}"));
}
command.push("batch".to_string());
if !key.config_file.is_empty()
&& let Some(config_path) = &group[0].config_path
{
// All grouped projects have the same config file content, pick one:
command.push("--config".to_string());
command.push(config_path.display().to_string());
}
let mut commands_in_batch = 0;
command.extend_from_slice(&key.config);
for item in group.iter() {
// Only build and doc can be batched
let batchable = [
"build", "doc",
// "check" // soon(TM)
];
if !batchable
.iter()
.any(|&subcommand| subcommand == item.subcommand)
{
all.push(Self::build_one_for_cargo(item));
continue;
}
let mut c = item.clone();
c.toolchain = None;
c.configs = Vec::new();
c.config_path = None;
command.push("---".to_string());
command.extend_from_slice(&c.build());
commands_in_batch += 1;
}
if commands_in_batch > 0 {
all.push(BuiltCommand {
artifact_name: String::from("batch"),
command,
env_vars: key.env_vars.clone(),
});
}
}
all
}
fn build_for_cargo(&self) -> Vec<BuiltCommand> {
let mut all = Vec::new();
for group in self.commands.values() {
for item in group.iter() {
all.push(Self::build_one_for_cargo(item));
}
}
all
}
pub fn build_one_for_cargo(item: &CargoArgsBuilder) -> BuiltCommand {
BuiltCommand {
artifact_name: item.artifact_name.clone(),
command: {
let mut args = item.build();
if item.args.iter().any(|arg| arg == "--artifact-dir") {
args.push("-Zunstable-options".to_string());
}
args
},
env_vars: item
.env_vars
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
}
}
pub fn build(&self, no_batch: bool) -> Vec<BuiltCommand> {
let cargo_batch_available = Command::new("cargo")
.arg("batch")
.arg("-h")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map(|s| s.success())
.unwrap_or(false);
if cargo_batch_available && !no_batch {
self.build_for_cargo_batch()
} else {
if !no_batch {
log::warn!("You don't have cargo batch installed. Falling back to cargo.");
log::warn!("You should really install cargo-batch.");
log::warn!(
"cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked"
);
}
self.build_for_cargo()
}
}
}
impl Drop for CargoCommandBatcher {
fn drop(&mut self) {}
}
/// A representation of a Cargo.toml file for a specific package.
pub struct CargoToml<'a> {
/// The workspace path where the Cargo.toml is located.

View File

@ -8,7 +8,8 @@ use strum::IntoEnumIterator as _;
use super::{ExamplesArgs, TestsArgs};
use crate::{
Package,
cargo::{self, CargoAction, CargoArgsBuilder},
cargo::{self, CargoAction, CargoArgsBuilder, CargoCommandBatcher},
commands::move_artifacts,
firmware::Metadata,
};
@ -143,20 +144,35 @@ pub fn build_examples(
let target = args.package.target_triple(&chip)?;
// Attempt to build each supported example, with all required features enabled:
examples.iter().try_for_each(|example| {
crate::execute_app(
package_path,
let action = CargoAction::Build(out_path.map(|p| p.to_path_buf()));
let mut commands = CargoCommandBatcher::new();
// Build command list
for example in examples.iter() {
let command = crate::generate_build_command(
&package_path,
chip,
&target,
example,
CargoAction::Build(out_path.map(|p| p.to_path_buf())),
1,
action.clone(),
args.debug,
args.toolchain.as_deref(),
args.timings,
&[],
)
})
)?;
commands.push(command);
}
// Execute the specified action:
for c in commands.build(false) {
println!(
"Command: cargo {}",
c.command.join(" ").replace("---", "\n ---")
);
c.run(false)?;
}
move_artifacts(chip, &action);
Ok(())
}
/// Build the specified package with the given options.

View File

@ -7,7 +7,10 @@ use inquire::Select;
use strum::IntoEnumIterator;
pub use self::{build::*, check_changelog::*, release::*, run::*};
use crate::{Package, cargo::CargoAction};
use crate::{
Package,
cargo::{CargoAction, CargoCommandBatcher},
};
mod build;
mod check_changelog;
mod release;
@ -221,7 +224,7 @@ pub fn tests(workspace: &Path, args: TestsArgs, action: CargoAction) -> Result<(
let target = Package::HilTest.target_triple(&args.chip)?;
// Load all tests which support the specified chip and parse their metadata:
let mut tests = crate::firmware::load(&package_path.join("tests"))?
let mut tests = crate::firmware::load(&package_path.join("src").join("bin"))?
.into_iter()
.filter(|example| example.supports_chip(args.chip))
.collect::<Vec<_>>();
@ -234,53 +237,108 @@ pub fn tests(workspace: &Path, args: TestsArgs, action: CargoAction) -> Result<(
.flatten()
.unwrap_or(&[]);
if let CargoAction::Build(Some(out_dir)) = &action {
// Make sure the tmp directory has no garbage for us.
let tmp_dir = out_dir.join("tmp");
_ = std::fs::remove_dir_all(&tmp_dir);
std::fs::create_dir_all(&tmp_dir).unwrap();
}
let mut commands = CargoCommandBatcher::new();
// Execute the specified action:
if tests.iter().any(|test| test.matches(test_arg.as_deref())) {
for test in tests
.iter()
.filter(|test| test.matches(test_arg.as_deref()))
{
crate::execute_app(
&package_path,
args.chip,
&target,
test,
action.clone(),
args.repeat,
false,
args.toolchain.as_deref(),
args.timings,
run_test_extra_args,
)?;
}
Ok(())
} else if test_arg.is_some() {
bail!("Test not found or unsupported for the given chip")
} else {
let mut failed = Vec::new();
for test in tests {
if crate::execute_app(
let command = crate::generate_build_command(
&package_path,
args.chip,
&target,
&test,
action.clone(),
args.repeat,
false,
args.toolchain.as_deref(),
args.timings,
run_test_extra_args,
)
.is_err()
{
failed.push(test.name_with_configuration());
)?;
commands.push(command);
}
} else if test_arg.is_some() {
bail!("Test not found or unsupported for the given chip")
} else {
for test in tests {
let command = crate::generate_build_command(
&package_path,
args.chip,
&target,
&test,
action.clone(),
false,
args.toolchain.as_deref(),
args.timings,
run_test_extra_args,
)?;
commands.push(command);
}
}
let mut failed = Vec::new();
for c in commands.build(false) {
let repeat = if matches!(action, CargoAction::Run) {
args.repeat
} else {
1
};
println!(
"Command: cargo {}",
c.command.join(" ").replace("---", "\n ---")
);
for i in 0..repeat {
if repeat != 1 {
log::info!("Run {}/{}", i + 1, repeat);
}
if c.run(false).is_err() {
failed.push(c.artifact_name.clone());
}
}
}
if !failed.is_empty() {
bail!("Failed tests: {:#?}", failed);
move_artifacts(args.chip, &action);
if !failed.is_empty() {
bail!("Failed tests: {:#?}", failed);
}
Ok(())
}
fn move_artifacts(chip: Chip, action: &CargoAction) {
if let CargoAction::Build(Some(out_dir)) = action {
// Move binaries
let from = out_dir.join("tmp");
let to = out_dir.join(chip.to_string());
std::fs::create_dir_all(&to).unwrap();
// Binaries are in nested folders. There is one file in each folder. The name of the
// final binary should be the name of the source binary's parent folder.
for dir_entry in std::fs::read_dir(&from).unwrap() {
let dir = dir_entry.unwrap();
let mut bin_folder = std::fs::read_dir(dir.path()).unwrap();
let file = bin_folder
.next()
.expect("No binary found")
.expect("Failed to read entry");
assert!(
bin_folder.next().is_none(),
"Only one binary should be present in each folder"
);
let source_file = file.path();
let dest = to.join(dir.path().file_name().unwrap().to_string_lossy().as_ref());
std::fs::rename(source_file, dest).unwrap();
}
Ok(())
// Clean up
std::fs::remove_dir_all(from).unwrap();
}
}

View File

@ -73,7 +73,6 @@ pub fn run_doc_tests(workspace: &Path, args: DocTestArgs) -> Result<()> {
.toolchain(toolchain)
.subcommand("test")
.arg("--doc")
.arg("-Zdoctest-xcompile")
.arg("-Zbuild-std=core,panic_abort")
.target(target)
.features(&features)
@ -177,23 +176,22 @@ pub fn run_examples(
}
}
if !skip {
while !skip
&& crate::execute_app(
package_path,
chip,
&target,
&example,
CargoAction::Run,
1,
args.debug,
args.toolchain.as_deref(),
args.timings,
&[],
)
.is_err()
{
log::info!("Failed to run example. Retry or skip? (r/s)");
while !skip {
let result = crate::execute_app(
package_path,
chip,
&target,
&example,
CargoAction::Run,
args.debug,
args.toolchain.as_deref(),
args.timings,
&[],
);
if let Err(error) = result {
log::error!("Failed to run example: {}", error);
log::info!("Retry or skip? (r/s)");
loop {
let key = console.read_key();
@ -206,6 +204,8 @@ pub fn run_examples(
_ => (),
}
}
} else {
break;
}
}
}

View File

@ -9,7 +9,7 @@ use esp_metadata::{Chip, Config, TokenStream};
use serde::{Deserialize, Serialize};
use crate::{
cargo::{CargoArgsBuilder, CargoToml},
cargo::{CargoArgsBuilder, CargoCommandBatcher, CargoToml},
firmware::Metadata,
};
@ -137,6 +137,9 @@ impl Package {
/// Does the package have any host tests?
pub fn has_host_tests(&self, workspace: &Path) -> bool {
if *self == Package::HilTest {
return false;
}
let package_path = workspace.join(self.to_string()).join("src");
walkdir::WalkDir::new(package_path)
@ -292,10 +295,63 @@ impl Package {
features
}
/// Additional feature rules to test subsets of features for a package.
pub fn check_feature_rules(&self, config: &Config) -> Vec<Vec<String>> {
let mut cases = Vec::new();
// For now we run a lot of checks, but that will change.
cases.push(self.feature_rules(config));
match self {
Package::EspHal => {
// This checks if the `esp-hal` crate compiles with the no features (other than the
// chip selection)
// This tests that disabling the `rt` feature works
cases.push(vec![]);
// This checks if the `esp-hal` crate compiles _without_ the `unstable` feature
// enabled
cases.push(vec!["rt".to_owned()]);
}
Package::EspRadio => {
// Minimal set of features that when enabled _should_ still compile:
cases.push(vec!["esp-hal/rt".to_owned(), "esp-hal/unstable".to_owned()]);
if config.contains("wifi") {
// This tests if `wifi` feature works without `esp-radio/unstable`
cases.push(vec![
"esp-hal/rt".to_owned(),
"esp-hal/unstable".to_owned(),
"wifi".to_owned(),
]);
// This tests `wifi-eap` feature
cases.push(vec![
"esp-hal/rt".to_owned(),
"esp-hal/unstable".to_owned(),
"wifi-eap".to_owned(),
"unstable".to_owned(),
]);
}
}
Package::EspMetadataGenerated => {
cases.push(vec!["build-script".to_owned()]);
}
Package::EspPreempt => {
cases.push(vec!["esp-alloc".to_owned(), "esp-hal/unstable".to_owned()])
}
_ => {}
}
log::debug!("Lint feature cases for package '{}': {:?}", self, cases);
cases
}
/// 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();
// For now we run a lot of clippy checks, but that will change.
cases.push(self.feature_rules(config));
match self {
Package::EspHal => {
// This checks if the `esp-hal` crate compiles with the no features (other than the
@ -405,7 +461,6 @@ pub fn execute_app(
target: &str,
app: &Metadata,
action: CargoAction,
repeat: usize,
debug: bool,
toolchain: Option<&str>,
timings: bool,
@ -414,9 +469,47 @@ pub fn execute_app(
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 builder = generate_build_command(
package_path,
chip,
target,
app,
action,
debug,
toolchain,
timings,
extra_args,
)?;
let command = CargoCommandBatcher::build_one_for_cargo(&builder);
command.run(false)?;
Ok(())
}
pub fn generate_build_command(
package_path: &Path,
chip: Chip,
target: &str,
app: &Metadata,
action: CargoAction,
debug: bool,
toolchain: Option<&str>,
timings: bool,
extra_args: &[&str],
) -> Result<CargoArgsBuilder> {
let package = app.example_path().strip_prefix(package_path)?;
log::info!(
"Building command: {} '{}' for '{}'",
if matches!(action, CargoAction::Build(_)) {
"Build"
} else {
"Run"
},
package.display(),
chip
);
let mut features = app.feature_set().to_vec();
if !features.is_empty() {
@ -424,19 +517,28 @@ pub fn execute_app(
}
features.push(chip.to_string());
let env_vars = app.env_vars();
for (key, value) in env_vars {
log::info!(" esp-config: {} = {}", key, value);
}
let cwd = if package_path.ends_with("examples") {
package_path.join(package).to_path_buf()
} else {
package_path.to_path_buf()
};
let mut builder = CargoArgsBuilder::default()
let mut builder = CargoArgsBuilder::new(app.output_file_name())
.manifest_path(cwd.join("Cargo.toml"))
.config_path(cwd.join(".cargo").join("config.toml"))
.target(target)
.features(&features);
.features(&features)
.args(extra_args);
let subcommand = if matches!(action, CargoAction::Build(_)) {
"build"
} else {
"run"
};
builder = builder.subcommand(subcommand);
let bin_arg = if package.starts_with("src/bin") {
Some(format!("--bin={}", app.binary_name()))
} else if package.starts_with("tests") {
Some(format!("--test={}", app.binary_name()))
} else if !package_path.ends_with("examples") {
Some(format!("--example={}", app.binary_name()))
} else {
@ -447,23 +549,24 @@ pub fn execute_app(
builder.add_arg(arg);
}
let subcommand = if matches!(action, CargoAction::Build(_)) {
"build"
} else if package.starts_with("tests") {
"test"
} else {
"run"
};
builder = builder.subcommand(subcommand);
if !app.configuration().is_empty() {
log::info!(" Configuration: {}", app.configuration());
}
for config in app.cargo_config() {
log::info!(" Cargo --config: {config}");
builder.add_arg("--config").add_arg(config);
builder.add_config("--config").add_config(config);
// Some configuration requires nightly rust, so let's just assume it. May be
// overwritten by the esp toolchain on xtensa.
builder = builder.toolchain("nightly");
}
let env_vars = app.env_vars();
for (key, value) in env_vars {
log::info!(" esp-config: {} = {}", key, value);
builder.add_env_var(key, value);
}
if !debug {
builder.add_arg("--release");
}
@ -478,57 +581,24 @@ pub fn execute_app(
_ if target.starts_with("xtensa") => Some("esp"),
_ => None,
};
if let Some(toolchain) = toolchain {
if toolchain.starts_with("esp") {
builder = builder.arg("-Zbuild-std=core,alloc");
}
builder = builder.toolchain(toolchain);
}
builder = builder.args(extra_args);
let args = builder.build();
log::debug!("{args:#?}");
let cwd = if package_path.ends_with("examples") {
package_path.join(package).to_path_buf()
} else {
package_path.to_path_buf()
};
if let CargoAction::Build(out_dir) = action {
cargo::run_with_env(&args, &cwd, env_vars, false)?;
if let Some(out_dir) = out_dir {
// 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_with_env(&args, &cwd, env_vars, true)?;
for line in output.lines() {
if let Ok(artifact) = serde_json::from_str::<cargo::Artifact>(line) {
let out_dir = out_dir.join(chip.to_string());
std::fs::create_dir_all(&out_dir)?;
let output_file = out_dir.join(app.output_file_name());
std::fs::copy(artifact.executable, &output_file)?;
log::info!("Output ready: {}", output_file.display());
}
}
}
} else {
for i in 0..repeat {
if repeat != 1 {
log::info!("Run {}/{}", i + 1, repeat);
}
cargo::run_with_env(&args, &cwd, env_vars, false)?;
}
if let CargoAction::Build(Some(out_dir)) = action {
// We have to place the binary into a directory named after the app, because
// we can't set the binary name.
builder.add_arg("--artifact-dir");
builder.add_arg(
out_dir
.join("tmp") // This will be deleted in one go
.join(app.output_file_name()) // This sets the name of the binary
.display()
.to_string(),
);
}
Ok(())
Ok(builder)
}
// ----------------------------------------------------------------------------

View File

@ -1,8 +1,4 @@
use std::{
fs,
path::{Path, PathBuf},
time::Instant,
};
use std::{path::Path, time::Instant};
use anyhow::{Context, Result, bail};
use clap::{Args, Parser};
@ -10,7 +6,7 @@ use esp_metadata::{Chip, Config};
use strum::IntoEnumIterator;
use xtask::{
Package,
cargo::{CargoAction, CargoArgsBuilder},
cargo::{CargoAction, CargoArgsBuilder, CargoCommandBatcher},
commands::*,
update_metadata,
};
@ -37,6 +33,8 @@ enum Cli {
FmtPackages(FmtPackagesArgs),
/// Run cargo clean
Clean(CleanArgs),
/// Check all packages in the workspace with cargo check
CheckPackages(CheckPackagesArgs),
/// Lint all packages in the workspace with clippy
LintPackages(LintPackagesArgs),
/// Semver Checks
@ -58,6 +56,14 @@ struct CiArgs {
/// The toolchain used to run the lints
#[arg(long)]
toolchain: Option<String>,
/// Whether to skip running lints
#[arg(long)]
no_lint: bool,
/// Whether to skip building documentation
#[arg(long)]
no_docs: bool,
}
#[derive(Debug, Args)]
@ -85,6 +91,21 @@ struct HostTestsArgs {
packages: Vec<Package>,
}
#[derive(Debug, Args)]
struct CheckPackagesArgs {
/// Package(s) to target.
#[arg(value_enum, default_values_t = Package::iter())]
packages: Vec<Package>,
/// Check for a specific chip
#[arg(long, value_enum, value_delimiter = ',', default_values_t = Chip::iter())]
chips: Vec<Chip>,
/// The toolchain used to run the checks
#[arg(long)]
toolchain: Option<String>,
}
#[derive(Debug, Args)]
struct LintPackagesArgs {
/// Package(s) to target.
@ -133,7 +154,11 @@ fn main() -> Result<()> {
let workspace =
std::env::current_dir().with_context(|| format!("Failed to get the current dir!"))?;
let target_path = Path::new("target");
let target_path = workspace.join("target");
if std::env::var("CARGO_TARGET_DIR").is_err() {
unsafe { std::env::set_var("CARGO_TARGET_DIR", target_path.to_str().unwrap()) };
}
match Cli::parse() {
// Build-related subcommands:
@ -141,11 +166,7 @@ fn main() -> Result<()> {
Build::Documentation(args) => build_documentation(&workspace, args),
#[cfg(feature = "deploy-docs")]
Build::DocumentationIndex => build_documentation_index(&workspace),
Build::Examples(args) => examples(
&workspace,
args,
CargoAction::Build(Some(target_path.join("examples"))),
),
Build::Examples(args) => examples(&workspace, args, CargoAction::Build(None)),
Build::Package(args) => build_package(&workspace, args),
Build::Tests(args) => tests(
&workspace,
@ -182,6 +203,7 @@ fn main() -> Result<()> {
Cli::Ci(args) => run_ci_checks(&workspace, args),
Cli::FmtPackages(args) => fmt_packages(&workspace, args),
Cli::Clean(args) => clean(&workspace, args),
Cli::CheckPackages(args) => check_packages(&workspace, args),
Cli::LintPackages(args) => lint_packages(&workspace, args),
Cli::SemverCheck(args) => semver_checks(&workspace, args),
Cli::CheckChangelog(args) => check_changelog(&workspace, &args.packages, args.normalize),
@ -212,7 +234,11 @@ fn clean(workspace: &Path, args: CleanArgs) -> Result<()> {
log::info!("Cleaning package: {}", package);
let path = workspace.join(package.to_string());
let cargo_args = CargoArgsBuilder::default().subcommand("clean").build();
let cargo_args = CargoArgsBuilder::default()
.subcommand("clean")
.arg("--target-dir")
.arg(path.join("target").display().to_string())
.build();
xtask::cargo::run(&cargo_args, &path).with_context(|| {
format!(
@ -225,6 +251,103 @@ fn clean(workspace: &Path, args: CleanArgs) -> Result<()> {
Ok(())
}
fn check_packages(workspace: &Path, args: CheckPackagesArgs) -> Result<()> {
log::debug!("Checking packages: {:?}", args.packages);
let mut packages = args.packages;
packages.sort();
let mut commands = CargoCommandBatcher::new();
for package in packages.iter().filter(|p| p.is_published(workspace)) {
// Unfortunately each package has its own unique requirements for
// building, so we need to handle each individually (though there
// is *some* overlap)
for chip in &args.chips {
log::debug!(" for chip: {}", chip);
let device = Config::for_chip(chip);
if package.validate_package_chip(chip).is_err() {
continue;
}
for mut features in package.check_feature_rules(device) {
if package.has_chip_features() {
features.push(device.name())
}
commands.push(build_check_package_command(
workspace,
*package,
chip,
&["--no-default-features"],
&features,
args.toolchain.as_deref(),
)?);
}
}
}
for c in commands.build(false) {
println!(
"Command: cargo {}",
c.command.join(" ").replace("---", "\n ---")
);
c.run(false)?;
}
Ok(())
}
fn build_check_package_command(
workspace: &Path,
package: Package,
chip: &Chip,
args: &[&str],
features: &[String],
mut toolchain: Option<&str>,
) -> Result<CargoArgsBuilder> {
log::info!(
"Linting package: {} ({}, features: {:?})",
package,
chip,
features
);
let path = workspace.join(package.to_string());
let mut builder = CargoArgsBuilder::default()
.subcommand("check")
.manifest_path(path.join("Cargo.toml"));
if !package.build_on_host(features) {
if chip.is_xtensa() {
// In case the user doesn't specify a toolchain, make sure we use +esp
toolchain.get_or_insert("esp");
}
builder = builder.target(package.target_triple(chip)?);
}
if let Some(toolchain) = toolchain {
if !package.build_on_host(features) && toolchain.starts_with("esp") {
builder = builder.config("-Zbuild-std=core,alloc");
}
builder = builder.toolchain(toolchain);
}
builder = builder.args(&args);
if !features.is_empty() {
builder = builder.arg(format!("--features={}", features.join(",")));
}
// TODO: these should come from the outside
builder.add_env_var("CI", "1");
builder.add_env_var("DEFMT_LOG", "trace");
builder.add_env_var("ESP_LOG", "trace");
Ok(builder)
}
fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> {
log::debug!("Linting packages: {:?}", args.packages);
let mut packages = args.packages;
@ -242,13 +365,7 @@ fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> {
continue;
}
let feature_sets = [
vec![package.feature_rules(device)], // initially test all features
package.lint_feature_rules(device), // add separate test cases
]
.concat();
for mut features in feature_sets {
for mut features in package.lint_feature_rules(device) {
if package.has_chip_features() {
features.push(device.name())
}
@ -299,7 +416,7 @@ fn lint_package(
if let Some(toolchain) = toolchain {
if !package.build_on_host(features) && toolchain.starts_with("esp") {
builder = builder.arg("-Zbuild-std=core,alloc");
builder = builder.config("-Zbuild-std=core,alloc");
}
builder = builder.toolchain(toolchain);
}
@ -389,18 +506,48 @@ fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> {
let mut runner = Runner::new();
runner.run("Lint", || {
lint_packages(
unsafe {
std::env::set_var("CI", "true");
}
// TODO: enable checking all crates once cargo-batch has check support
// runner.run("Check crates", || {
// check_packages(
// workspace,
// LintPackagesArgs {
// packages: Package::iter().collect(),
// chips: vec![args.chip],
// fix: false,
// toolchain: args.toolchain.clone(),
// },
// )
// });
runner.run("Check esp-hal", || {
check_packages(
workspace,
LintPackagesArgs {
packages: Package::iter().collect(),
CheckPackagesArgs {
packages: vec![Package::EspHal],
chips: vec![args.chip],
fix: false,
toolchain: args.toolchain.clone(),
},
)
});
if !args.no_lint {
runner.run("Lint", || {
lint_packages(
workspace,
LintPackagesArgs {
packages: Package::iter().collect(),
chips: vec![args.chip],
fix: false,
toolchain: args.toolchain.clone(),
},
)
});
}
runner.run("Run Doc Test", || {
run_doc_tests(
workspace,
@ -411,16 +558,18 @@ fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> {
)
});
runner.run("Build Docs", || {
build_documentation(
workspace,
BuildDocumentationArgs {
packages: vec![Package::EspHal, Package::EspRadio, Package::EspHalEmbassy],
chips: vec![args.chip],
..Default::default()
},
)
});
if !args.no_docs {
runner.run("Build Docs", || {
build_documentation(
workspace,
BuildDocumentationArgs {
packages: vec![Package::EspHal, Package::EspRadio, Package::EspHalEmbassy],
chips: vec![args.chip],
..Default::default()
},
)
});
}
// for chips with esp-lp-hal: Build all supported examples for the low-power
// core first
@ -430,6 +579,17 @@ fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> {
// path element then we copy it to the place where the HP core example
// expects it
runner.run("Build LP-HAL Examples", || {
// The LP examples aren't really that demanding, but they need to be at a certain place.
// Instead of trying to figure out where the results are, let's just make sure the
// target folder is set up as expected.
let original_target_dir = std::env::var("CARGO_TARGET_DIR");
unsafe {
std::env::set_var(
"CARGO_TARGET_DIR",
workspace.join("esp-lp-hal").join("target"),
);
}
let result = examples(
workspace,
ExamplesArgs {
@ -440,81 +600,77 @@ fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> {
toolchain: args.toolchain.clone(),
timings: false,
},
CargoAction::Build(Some(PathBuf::from(format!(
"./esp-lp-hal/target/{}/release/examples",
args.chip.target()
)))),
)
.and_then(|_| {
let from_dir = PathBuf::from(format!(
"./esp-lp-hal/target/{}/release/examples/{}",
args.chip.target(),
args.chip
));
let to_dir = PathBuf::from(format!(
"./esp-lp-hal/target/{}/release/examples",
args.chip.target()
));
from_dir
.read_dir()
.with_context(|| format!("Failed to read from {}", from_dir.display()))?
.for_each(|entry| {
let entry = entry.unwrap();
let path = entry.path();
let to = to_dir.join(entry.file_name());
fs::copy(&path, &to).expect(
format!("Failed to copy {} to {}", path.display(), to.display())
.as_str(),
);
});
Ok(())
});
CargoAction::Build(None),
);
// remove the (now) obsolete duplicates
log::debug!("Removing obsolete LP-HAL example duplicates");
std::fs::remove_dir_all(PathBuf::from(format!(
"./esp-lp-hal/target/{}/release/examples/{}",
args.chip.target(),
args.chip
)))
.with_context(|| {
format!(
"Failed to remove duplicates in ./esp-lp-hal/target/{}/release/examples/{}",
args.chip.target(),
args.chip
)
})?;
// Still need to rename examples to remove the fingerprint off of their names:
let dir = workspace
.join("esp-lp-hal")
.join("target")
.join(Package::EspLpHal.target_triple(&args.chip)?)
.join("release")
.join("examples");
let examples = dir
.read_dir()
.with_context(|| format!("Failed to read examples directory: {}", dir.display()))?;
for example in examples {
let example = example.context("Failed to read example")?;
if example
.file_type()
.with_context(|| {
format!(
"Failed to get file type for example: {}",
example.path().display()
)
})?
.is_file()
&& example.path().extension().is_none()
{
let example_name = example.file_name().to_string_lossy().to_string();
let without_fingerprint = example_name
.rsplit_once('-')
.map(|(a, _)| a)
.unwrap_or(&example_name);
// Copy so we don't trigger a rebuild unnecessarily by deleting the original
std::fs::copy(example.path(), dir.join(without_fingerprint)).with_context(
|| {
format!(
"Failed to copy example: {} to {}",
example.path().display(),
dir.join(without_fingerprint).display()
)
},
)?;
}
}
// Restore the original target directory
unsafe {
if let Ok(target) = original_target_dir {
std::env::set_var("CARGO_TARGET_DIR", target);
} else {
std::env::remove_var("CARGO_TARGET_DIR");
}
}
result
});
// Check documentation
runner.run("Build LP-HAL docs", || {
build_documentation(
workspace,
BuildDocumentationArgs {
packages: vec![Package::EspLpHal],
chips: vec![args.chip],
..Default::default()
},
)
});
if !args.no_docs {
// Check documentation
runner.run("Build LP-HAL docs", || {
build_documentation(
workspace,
BuildDocumentationArgs {
packages: vec![Package::EspLpHal],
chips: vec![args.chip],
..Default::default()
},
)
});
}
}
// Make sure we're able to build the HAL without the default features enabled
runner.run("Build HAL", || {
build_package(
workspace,
BuildPackageArgs {
package: Package::EspHal,
target: Some(args.chip.target().to_string()),
features: vec![args.chip.to_string()],
no_default_features: true,
toolchain: args.toolchain.clone(),
},
)
});
runner.run("Build examples", || {
// The `ota_example` expects a file named `examples/target/ota_image` - it
// doesn't care about the contents however