Build examples in debug mode (#2078)

* Build examples in debug mode

* Allow building psram examples in debug mode in CI

* Don't rebuild tests, try to avoid rebuilding dependencies
This commit is contained in:
Dániel Buga 2024-09-05 12:04:07 +02:00 committed by GitHub
parent 42a0417fba
commit 5370afb1eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 120 additions and 75 deletions

View File

@ -57,5 +57,7 @@ runs:
--target=${{ inputs.target }} \ --target=${{ inputs.target }} \
esp-hal esp-hal
- name: Build (examples) - name: Build (examples)
env:
CI: 1
shell: bash shell: bash
run: cargo +${{ inputs.toolchain }} xtask build-examples esp-hal ${{ inputs.device }} run: cargo +${{ inputs.toolchain }} xtask build-examples esp-hal ${{ inputs.device }} --debug

View File

@ -6,14 +6,14 @@ fn main() -> Result<(), String> {
if cfg!(feature = "esp32") { if cfg!(feature = "esp32") {
match std::env::var("OPT_LEVEL") { match std::env::var("OPT_LEVEL") {
Ok(level) => { Ok(level) if std::env::var("CI").is_err() => {
if level != "2" && level != "3" { if level != "2" && level != "3" {
Err(format!("Building esp-storage for ESP32 needs optimization level 2 or 3 - yours is {}. See https://github.com/esp-rs/esp-storage", level)) Err(format!("Building esp-storage for ESP32 needs optimization level 2 or 3 - yours is {}. See https://github.com/esp-rs/esp-storage", level))
} else { } else {
Ok(()) Ok(())
} }
} }
Err(_err) => Ok(()), _ => Ok(()),
} }
} else { } else {
Ok(()) Ok(())

View File

@ -8,4 +8,12 @@ fn main() {
if cfg!(feature = "esp-wifi") { if cfg!(feature = "esp-wifi") {
println!("cargo::rustc-link-arg=-Trom_functions.x"); println!("cargo::rustc-link-arg=-Trom_functions.x");
} }
// Allow building examples in CI in debug mode
println!("cargo:rustc-check-cfg=cfg(is_not_release)");
println!("cargo:rerun-if-env-changed=CI");
#[cfg(debug_assertions)]
if std::env::var("CI").is_err() {
println!("cargo::rustc-cfg=is_not_release");
}
} }

View File

@ -22,7 +22,7 @@ use esp_println::{print, println};
const WIDTH: usize = 80; const WIDTH: usize = 80;
#[cfg(debug_assertions)] #[cfg(is_not_release)]
compile_error!("Run this example in release mode"); compile_error!("Run this example in release mode");
#[embassy_executor::task] #[embassy_executor::task]

View File

@ -25,11 +25,11 @@ fn init_psram_heap() {
} }
} }
#[cfg(is_not_release)]
compile_error!("PSRAM example must be built in release mode!");
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
#[cfg(debug_assertions)]
compile_error!("This example MUST be built in release mode!");
let peripherals = esp_hal::init(esp_hal::Config::default()); let peripherals = esp_hal::init(esp_hal::Config::default());
psram::init_psram(peripherals.PSRAM); psram::init_psram(peripherals.PSRAM);

View File

@ -1,6 +1,6 @@
//! This shows how to use PSRAM as heap-memory via esp-alloc //! This shows how to use PSRAM as heap-memory via esp-alloc
//! //!
//! You need an ESP32, ESP32-S2, or ESP32-S3 with at least 2 MB of PSRAM memory. //! You need an ESP32, ESP32-S2 or ESP32-S3 with at least 2 MB of PSRAM memory.
//% CHIPS: esp32 esp32s2 esp32s3 //% CHIPS: esp32 esp32s2 esp32s3
//% FEATURES: psram-2m //% FEATURES: psram-2m
@ -25,11 +25,11 @@ fn init_psram_heap() {
} }
} }
#[entry] #[cfg(is_not_release)]
fn main() -> ! {
#[cfg(debug_assertions)]
compile_error!("PSRAM example must be built in release mode!"); compile_error!("PSRAM example must be built in release mode!");
#[entry]
fn main() -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default()); let peripherals = esp_hal::init(esp_hal::Config::default());
psram::init_psram(peripherals.PSRAM); psram::init_psram(peripherals.PSRAM);

View File

@ -113,6 +113,14 @@ impl CargoArgsBuilder {
self self
} }
pub fn add_arg<S>(&mut self, arg: S) -> &mut Self
where
S: Into<String>,
{
self.args.push(arg.into());
self
}
#[must_use] #[must_use]
pub fn build(self) -> Vec<String> { pub fn build(self) -> Vec<String> {
let mut args = vec![]; let mut args = vec![];

View File

@ -57,11 +57,11 @@ pub enum Package {
pub struct Metadata { pub struct Metadata {
example_path: PathBuf, example_path: PathBuf,
chips: Vec<Chip>, chips: Vec<Chip>,
feature_sets: Vec<Vec<String>>, feature_set: Vec<String>,
} }
impl Metadata { impl Metadata {
pub fn new(example_path: &Path, chips: Vec<Chip>, feature_sets: Vec<Vec<String>>) -> Self { pub fn new(example_path: &Path, chips: Vec<Chip>, feature_set: Vec<String>) -> Self {
let chips = if chips.is_empty() { let chips = if chips.is_empty() {
Chip::iter().collect() Chip::iter().collect()
} else { } else {
@ -71,7 +71,7 @@ impl Metadata {
Self { Self {
example_path: example_path.to_path_buf(), example_path: example_path.to_path_buf(),
chips, chips,
feature_sets, feature_set,
} }
} }
@ -89,9 +89,9 @@ impl Metadata {
.replace(".rs", "") .replace(".rs", "")
} }
/// A list of all features required for building a given examples. /// A list of all features required for building a given example.
pub fn feature_sets(&self) -> &[Vec<String>] { pub fn feature_set(&self) -> &[String] {
&self.feature_sets &self.feature_set
} }
/// If the specified chip is in the list of chips, then it is supported. /// If the specified chip is in the list of chips, then it is supported.
@ -154,7 +154,7 @@ pub fn build_documentation(
} }
/// Load all examples at the given path, and parse their metadata. /// Load all examples at the given path, and parse their metadata.
pub fn load_examples(path: &Path) -> Result<Vec<Metadata>> { pub fn load_examples(path: &Path, action: CargoAction) -> Result<Vec<Metadata>> {
let mut examples = Vec::new(); let mut examples = Vec::new();
for entry in fs::read_dir(path)? { for entry in fs::read_dir(path)? {
@ -172,7 +172,7 @@ pub fn load_examples(path: &Path) -> Result<Vec<Metadata>> {
.trim() .trim()
.split_ascii_whitespace() .split_ascii_whitespace()
.map(|s| s.to_string()) .map(|s| s.to_string())
.collect::<VecDeque<_>>(); .collect::<Vec<_>>();
if split.len() < 2 { if split.len() < 2 {
bail!( bail!(
@ -182,7 +182,7 @@ pub fn load_examples(path: &Path) -> Result<Vec<Metadata>> {
} }
// The trailing ':' on metadata keys is optional :) // The trailing ':' on metadata keys is optional :)
let key = split.pop_front().unwrap(); let key = split.swap_remove(0);
let key = key.trim_end_matches(':'); let key = key.trim_end_matches(':');
if key == "CHIPS" { if key == "CHIPS" {
@ -191,14 +191,30 @@ pub fn load_examples(path: &Path) -> Result<Vec<Metadata>> {
.map(|s| Chip::from_str(s, false).unwrap()) .map(|s| Chip::from_str(s, false).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
} else if key == "FEATURES" { } else if key == "FEATURES" {
feature_sets.push(split.into()); // Sort the features so they are in a deterministic order:
split.sort();
feature_sets.push(split);
} else { } else {
log::warn!("Unrecognized metadata key '{key}', ignoring"); log::warn!("Unrecognized metadata key '{key}', ignoring");
} }
} }
examples.push(Metadata::new(&path, chips, feature_sets)); if feature_sets.is_empty() {
feature_sets.push(Vec::new());
} }
if action == CargoAction::Build {
// Only build the first feature set for each example.
// Rebuilding with a different feature set just wastes time because the latter
// one will overwrite the former one(s).
feature_sets.truncate(1);
}
for feature_set in feature_sets {
examples.push(Metadata::new(&path, chips.clone(), feature_set));
}
}
// Sort by feature set, to prevent rebuilding packages if not necessary.
examples.sort_by_key(|e| e.feature_set().join(","));
Ok(examples) Ok(examples)
} }
@ -210,7 +226,8 @@ pub fn execute_app(
target: &str, target: &str,
app: &Metadata, app: &Metadata,
action: CargoAction, action: CargoAction,
repeat: usize, mut repeat: usize,
debug: bool,
) -> Result<()> { ) -> Result<()> {
log::info!( log::info!(
"Building example '{}' for '{}'", "Building example '{}' for '{}'",
@ -218,30 +235,7 @@ pub fn execute_app(
chip chip
); );
let feature_sets = if app.feature_sets().is_empty() { let mut features = app.feature_set().to_vec();
vec![vec![]]
} else {
app.feature_sets().to_vec()
};
for features in feature_sets {
execute_app_with_features(package_path, chip, target, app, action, repeat, features)?;
}
Ok(())
}
/// Run or build the specified test or example for the specified chip, with the
/// specified features enabled.
pub fn execute_app_with_features(
package_path: &Path,
chip: Chip,
target: &str,
app: &Metadata,
action: CargoAction,
mut repeat: usize,
mut features: Vec<String>,
) -> Result<()> {
if !features.is_empty() { if !features.is_empty() {
log::info!("Features: {}", features.join(",")); log::info!("Features: {}", features.join(","));
} }
@ -269,19 +263,22 @@ pub fn execute_app_with_features(
let mut builder = CargoArgsBuilder::default() let mut builder = CargoArgsBuilder::default()
.subcommand(subcommand) .subcommand(subcommand)
.arg("--release")
.target(target) .target(target)
.features(&features) .features(&features)
.arg(bin); .arg(bin);
if !debug {
builder.add_arg("--release");
}
if subcommand == "test" && chip == Chip::Esp32c2 { if subcommand == "test" && chip == Chip::Esp32c2 {
builder = builder.arg("--").arg("--speed").arg("15000"); builder.add_arg("--").add_arg("--speed").add_arg("15000");
} }
// If targeting an Xtensa device, we must use the '+esp' toolchain modifier: // If targeting an Xtensa device, we must use the '+esp' toolchain modifier:
if target.starts_with("xtensa") { if target.starts_with("xtensa") {
builder = builder.toolchain("esp"); builder = builder.toolchain("esp");
builder = builder.arg("-Zbuild-std=core,alloc") builder.add_arg("-Zbuild-std=core,alloc");
} }
let args = builder.build(); let args = builder.build();

View File

@ -58,6 +58,9 @@ struct ExampleArgs {
chip: Chip, chip: Chip,
/// Optional example to act on (all examples used if omitted) /// Optional example to act on (all examples used if omitted)
example: Option<String>, example: Option<String>,
/// Build examples in debug mode only
#[arg(long)]
debug: bool,
} }
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -202,7 +205,7 @@ fn examples(workspace: &Path, mut args: ExampleArgs, action: CargoAction) -> Res
}; };
// Load all examples which support the specified chip and parse their metadata: // Load all examples which support the specified chip and parse their metadata:
let mut examples = xtask::load_examples(&example_path)? let mut examples = xtask::load_examples(&example_path, action)?
.iter() .iter()
.filter_map(|example| { .filter_map(|example| {
if example.supports_chip(args.chip) { if example.supports_chip(args.chip) {
@ -227,8 +230,13 @@ fn build_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Pat
// 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 = target_triple(args.package, &args.chip)?;
if let Some(example) = examples.iter().find(|ex| Some(ex.name()) == args.example) { if examples
.iter()
.find(|ex| Some(ex.name()) == args.example)
.is_some()
{
// Attempt to build only the specified example: // Attempt to build only the specified example:
for example in examples.iter().filter(|ex| Some(ex.name()) == args.example) {
xtask::execute_app( xtask::execute_app(
package_path, package_path,
args.chip, args.chip,
@ -236,7 +244,10 @@ fn build_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Pat
example, example,
CargoAction::Build, CargoAction::Build,
1, 1,
) args.debug,
)?;
}
Ok(())
} else if args.example.is_some() { } else if args.example.is_some() {
// An invalid argument was provided: // An invalid argument was provided:
bail!("Example not found or unsupported for the given chip") bail!("Example not found or unsupported for the given chip")
@ -250,6 +261,7 @@ fn build_examples(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Pat
example, example,
CargoAction::Build, CargoAction::Build,
1, 1,
args.debug,
) )
}) })
} }
@ -261,7 +273,9 @@ fn run_example(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path)
// 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:
if let Some(example) = examples.iter().find(|ex| Some(ex.name()) == args.example) { let mut found_one = false;
for example in examples.iter().filter(|ex| Some(ex.name()) == args.example) {
found_one = true;
xtask::execute_app( xtask::execute_app(
package_path, package_path,
args.chip, args.chip,
@ -269,10 +283,17 @@ fn run_example(args: ExampleArgs, examples: Vec<Metadata>, package_path: &Path)
example, example,
CargoAction::Run, CargoAction::Run,
1, 1,
) args.debug,
} else { )?;
bail!("Example not found or unsupported for the given chip")
} }
ensure!(
found_one,
"Example not found or unsupported for {}",
args.chip
);
Ok(())
} }
fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
@ -283,7 +304,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
let target = target_triple(Package::HilTest, &args.chip)?; let target = target_triple(Package::HilTest, &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::load_examples(&package_path.join("tests"))? let mut tests = xtask::load_examples(&package_path.join("tests"), action)?
.into_iter() .into_iter()
.filter(|example| example.supports_chip(args.chip)) .filter(|example| example.supports_chip(args.chip))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -292,7 +313,12 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
tests.sort_by_key(|a| a.name()); tests.sort_by_key(|a| a.name());
// Execute the specified action: // Execute the specified action:
if let Some(test) = tests.iter().find(|test| Some(test.name()) == args.test) { if tests
.iter()
.find(|test| Some(test.name()) == args.test)
.is_some()
{
for test in tests.iter().filter(|test| Some(test.name()) == args.test) {
xtask::execute_app( xtask::execute_app(
&package_path, &package_path,
args.chip, args.chip,
@ -300,7 +326,10 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
test, test,
action, action,
args.repeat.unwrap_or(1), args.repeat.unwrap_or(1),
) false,
)?;
}
Ok(())
} else if args.test.is_some() { } else if args.test.is_some() {
bail!("Test not found or unsupported for the given chip") bail!("Test not found or unsupported for the given chip")
} else { } else {
@ -313,6 +342,7 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> {
&test, &test,
action, action,
args.repeat.unwrap_or(1), args.repeat.unwrap_or(1),
false,
) )
.is_err() .is_err()
{ {