diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2023-03-04 21:10:24 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-04 21:10:24 +0000 |
| commit | 7c7d6558f6d9c50fbb4d2487c98c9a5be15f2f7b (patch) | |
| tree | 80a47f0dc40059014e9448c4c2eb34c54dff45fe /xtask/src/main.rs | |
| parent | 1c5db277e4161470136dbd2a11e914ff1d383581 (diff) | |
| parent | 98c5490d94950608d31cd5ad9dd260f2f853735c (diff) | |
Merge #694
694: RTIC 2 r=AfoHT a=korken89
Co-authored-by: Emil Fresk <emil.fresk@gmail.com>
Co-authored-by: Per Lindgren <per.lindgren@ltu.se>
Diffstat (limited to 'xtask/src/main.rs')
| -rw-r--r-- | xtask/src/main.rs | 342 |
1 files changed, 262 insertions, 80 deletions
diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 76ce04b..aed74eb 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,36 +1,51 @@ +mod argument_parsing; mod build; +mod cargo_commands; mod command; use anyhow::bail; +use argument_parsing::{ExtraArguments, Package}; +use clap::Parser; use core::fmt; +use diffy::{create_patch, PatchFormatter}; use std::{ error::Error, ffi::OsString, + fs::File, + io::prelude::*, path::{Path, PathBuf}, process, process::ExitStatus, str, }; -use structopt::StructOpt; + +use env_logger::Env; +use log::{debug, error, info, log_enabled, trace, Level}; use crate::{ + argument_parsing::{Backends, BuildOrCheck, Cli, Commands, PackageOpt}, build::init_build_dir, - command::{run_command, run_successful, BuildMode, CargoCommand}, + cargo_commands::{ + build_and_check_size, cargo, cargo_book, cargo_clippy, cargo_doc, cargo_example, + cargo_format, cargo_test, run_test, + }, + command::{run_command, run_successful, CargoCommand}, }; +// x86_64-unknown-linux-gnu +const _X86_64: &str = "x86_64-unknown-linux-gnu"; const ARMV6M: &str = "thumbv6m-none-eabi"; const ARMV7M: &str = "thumbv7m-none-eabi"; +const ARMV8MBASE: &str = "thumbv8m.base-none-eabi"; +const ARMV8MMAIN: &str = "thumbv8m.main-none-eabi"; -#[derive(Debug, StructOpt)] -struct Options { - #[structopt(short, long)] - target: String, -} +const DEFAULT_FEATURES: &str = "test-critical-section"; #[derive(Debug, Clone)] pub struct RunResult { exit_status: ExitStatus, - output: String, + stdout: String, + stderr: String, } #[derive(Debug)] @@ -41,31 +56,31 @@ pub enum TestRunError { CommandError(RunResult), IncompatibleCommand, } - impl fmt::Display for TestRunError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TestRunError::FileCmpError { expected, got } => { - writeln!(f, "Differing output in files.")?; - writeln!(f, "")?; - writeln!(f, "Expected:")?; - writeln!(f, "{}", expected)?; - writeln!(f, "")?; - writeln!(f, "Got:")?; - write!(f, "{}", got) + let patch = create_patch(expected, got); + writeln!(f, "Differing output in files.\n")?; + let pf = PatchFormatter::new().with_color(); + writeln!(f, "{}", pf.fmt_patch(&patch))?; + write!( + f, + "See flag --overwrite-expected to create/update expected output." + ) } TestRunError::FileError { file } => { - write!(f, "File error on: {}", file) + write!(f, "File error on: {file}\nSee flag --overwrite-expected to create/update expected output.") } TestRunError::CommandError(e) => { write!( f, "Command failed with exit status {}: {}", - e.exit_status, e.output + e.exit_status, e.stdout ) } TestRunError::PathConversionError(p) => { - write!(f, "Can't convert path from `OsString` to `String`: {:?}", p) + write!(f, "Can't convert path from `OsString` to `String`: {p:?}") } TestRunError::IncompatibleCommand => { write!(f, "Can't run that command in this context") @@ -80,95 +95,262 @@ fn main() -> anyhow::Result<()> { // if there's an `xtask` folder, we're *probably* at the root of this repo (we can't just // check the name of `env::current_dir()` because people might clone it into a different name) let probably_running_from_repo_root = Path::new("./xtask").exists(); - if probably_running_from_repo_root == false { - bail!("xtasks can only be executed from the root of the `cortex-m-rtic` repository"); + if !probably_running_from_repo_root { + bail!("xtasks can only be executed from the root of the `rtic` repository"); } - let targets = [ARMV7M, ARMV6M]; - - let examples: Vec<_> = std::fs::read_dir("./examples")? - .filter_map(|path| { - path.map(|p| p.path().file_stem().unwrap().to_str().unwrap().to_string()) - .ok() - }) + let examples: Vec<_> = std::fs::read_dir("./rtic/examples")? + .filter_map(|p| p.ok()) + .map(|p| p.path()) + .filter(|p| p.display().to_string().ends_with(".rs")) + .map(|path| path.file_stem().unwrap().to_str().unwrap().to_string()) .collect(); - let opts = Options::from_args(); - let target = &opts.target; + let cli = Cli::parse(); - init_build_dir()?; + let env_logger_default_level = match cli.verbose { + 0 => Env::default().default_filter_or("error"), + 1 => Env::default().default_filter_or("info"), + 2 => Env::default().default_filter_or("debug"), + _ => Env::default().default_filter_or("trace"), + }; + env_logger::Builder::from_env(env_logger_default_level) + .format_module_path(false) + .format_timestamp(None) + .init(); - if target == "all" { - for t in targets { - run_test(t, &examples)?; - } - } else if targets.contains(&target.as_str()) { - run_test(&target, &examples)?; + trace!("default logging level: {0}", cli.verbose); + + let backend = if let Some(backend) = cli.backend { + backend } else { - eprintln!( - "The target you specified is not available. Available targets are:\ - \n{:?}\n\ - as well as `all` (testing on all of the above)", - targets - ); - process::exit(1); - } + Backends::default() + }; - Ok(()) -} + let example = cli.example; + let exampleexclude = cli.exampleexclude; + + let examples_to_run = { + let mut examples_to_run = examples.clone(); + + if let Some(example) = example { + examples_to_run = examples.clone(); + let examples_to_exclude = example.split(',').collect::<Vec<&str>>(); + // From the list of all examples, remove all not listed as included + for ex in examples_to_exclude { + examples_to_run.retain(|x| *x.as_str() == *ex); + } + }; -fn run_test(target: &str, examples: &[String]) -> anyhow::Result<()> { - arm_example(&CargoCommand::BuildAll { - target, - features: None, - mode: BuildMode::Release, - })?; - - for example in examples { - let cmd = CargoCommand::Run { - example, - target, - features: None, - mode: BuildMode::Release, + if let Some(example) = exampleexclude { + examples_to_run = examples.clone(); + let examples_to_exclude = example.split(',').collect::<Vec<&str>>(); + // From the list of all examples, remove all those listed as excluded + for ex in examples_to_exclude { + examples_to_run.retain(|x| *x.as_str() != *ex); + } }; - arm_example(&cmd)?; + if log_enabled!(Level::Trace) { + trace!("All examples:\n{examples:?} number: {}", examples.len()); + trace!( + "examples_to_run:\n{examples_to_run:?} number: {}", + examples_to_run.len() + ); + } + + if examples_to_run.is_empty() { + error!( + "\nThe example(s) you specified is not available. Available examples are:\ + \n{examples:#?}\n\ + By default if example flag is emitted, all examples are tested.", + ); + process::exit(exitcode::USAGE); + } else { + } + examples_to_run + }; + + init_build_dir()?; + #[allow(clippy::if_same_then_else)] + let cargologlevel = if log_enabled!(Level::Trace) { + Some("-v") + } else if log_enabled!(Level::Debug) { + None + } else if log_enabled!(Level::Info) { + None + } else if log_enabled!(Level::Warn) || log_enabled!(Level::Error) { + None + } else { + // Off case + Some("--quiet") + }; + + match cli.command { + Commands::FormatCheck(args) => { + info!("Running cargo fmt: {args:?}"); + let check_only = true; + cargo_format(&cargologlevel, &args, check_only)?; + } + Commands::Format(args) => { + info!("Running cargo fmt --check: {args:?}"); + let check_only = false; + cargo_format(&cargologlevel, &args, check_only)?; + } + Commands::Clippy(args) => { + info!("Running clippy on backend: {backend:?}"); + cargo_clippy(&cargologlevel, &args, backend)?; + } + Commands::Check(args) => { + info!("Checking on backend: {backend:?}"); + cargo(BuildOrCheck::Check, &cargologlevel, &args, backend)?; + } + Commands::Build(args) => { + info!("Building for backend: {backend:?}"); + cargo(BuildOrCheck::Build, &cargologlevel, &args, backend)?; + } + Commands::ExampleCheck => { + info!("Checking on backend: {backend:?}"); + cargo_example( + BuildOrCheck::Check, + &cargologlevel, + backend, + &examples_to_run, + )?; + } + Commands::ExampleBuild => { + info!("Building for backend: {backend:?}"); + cargo_example( + BuildOrCheck::Build, + &cargologlevel, + backend, + &examples_to_run, + )?; + } + Commands::Size(args) => { + // x86_64 target not valid + info!("Measuring for backend: {backend:?}"); + build_and_check_size(&cargologlevel, backend, &examples_to_run, &args.arguments)?; + } + Commands::Qemu(args) | Commands::Run(args) => { + // x86_64 target not valid + info!("Testing for backend: {backend:?}"); + run_test( + &cargologlevel, + backend, + &examples_to_run, + args.overwrite_expected, + )?; + } + Commands::Doc(args) => { + info!("Running cargo doc on backend: {backend:?}"); + cargo_doc(&cargologlevel, backend, &args.arguments)?; + } + Commands::Test(args) => { + info!("Running cargo test on backend: {backend:?}"); + cargo_test(&args, backend)?; + } + Commands::Book(args) => { + info!("Running mdbook"); + cargo_book(&args.arguments)?; + } } Ok(()) } +/// Get the features needed given the selected package +/// +/// Without package specified the features for RTIC are required +/// With only a single package which is not RTIC, no special +/// features are needed +fn package_feature_extractor(package: &PackageOpt, backend: Backends) -> Option<String> { + let default_features = Some(format!( + "{},{}", + DEFAULT_FEATURES, + backend.to_rtic_feature() + )); + if let Some(package) = package.package { + debug!("\nTesting package: {package}"); + match package { + Package::Rtic => default_features, + Package::RticMacros => Some(backend.to_rtic_macros_feature().to_owned()), + _ => None, + } + } else { + default_features + } +} + // run example binary `example` -fn arm_example(command: &CargoCommand) -> anyhow::Result<()> { +fn command_parser(command: &CargoCommand, overwrite: bool) -> anyhow::Result<()> { match *command { - CargoCommand::Run { example, .. } => { - let run_file = format!("{}.run", example); - let expected_output_file = ["ci", "expected", &run_file] + CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { + let run_file = format!("{example}.run"); + let expected_output_file = ["rtic", "ci", "expected", &run_file] .iter() .collect::<PathBuf>() .into_os_string() .into_string() - .map_err(|e| TestRunError::PathConversionError(e))?; + .map_err(TestRunError::PathConversionError)?; - // command is either build or run - let cargo_run_result = run_command(&command)?; - println!("{}", cargo_run_result.output); + // cargo run <..> + info!("Running example: {example}"); + let cargo_run_result = run_command(command)?; + info!("{}", cargo_run_result.stdout); - match &command { - CargoCommand::Run { .. } => { - run_successful(&cargo_run_result, expected_output_file)?; - } - _ => (), + // Create a file for the expected output if it does not exist or mismatches + if overwrite { + let result = run_successful(&cargo_run_result, &expected_output_file); + if let Err(e) = result { + // FileError means the file did not exist or was unreadable + error!("Error: {e}"); + let mut file_handle = File::create(&expected_output_file).map_err(|_| { + TestRunError::FileError { + file: expected_output_file.clone(), + } + })?; + info!("Flag --overwrite-expected enabled"); + info!("Creating/updating file: {expected_output_file}"); + file_handle.write_all(cargo_run_result.stdout.as_bytes())?; + }; + } else { + run_successful(&cargo_run_result, &expected_output_file)?; } - Ok(()) } - CargoCommand::BuildAll { .. } => { - // command is either build or run - let cargo_run_result = run_command(&command)?; - println!("{}", cargo_run_result.output); + CargoCommand::Format { .. } + | CargoCommand::ExampleCheck { .. } + | CargoCommand::ExampleBuild { .. } + | CargoCommand::Check { .. } + | CargoCommand::Build { .. } + | CargoCommand::Clippy { .. } + | CargoCommand::Doc { .. } + | CargoCommand::Test { .. } + | CargoCommand::Book { .. } + | CargoCommand::ExampleSize { .. } => { + let cargo_result = run_command(command)?; + if let Some(exit_code) = cargo_result.exit_status.code() { + if exit_code != exitcode::OK { + error!("Exit code from command: {exit_code}"); + if !cargo_result.stdout.is_empty() { + info!("{}", cargo_result.stdout); + } + if !cargo_result.stderr.is_empty() { + error!("{}", cargo_result.stderr); + } + process::exit(exit_code); + } else { + if !cargo_result.stdout.is_empty() { + info!("{}", cargo_result.stdout); + } + if !cargo_result.stderr.is_empty() { + info!("{}", cargo_result.stderr); + } + } + } Ok(()) - } // _ => Err(anyhow::Error::new(TestRunError::IncompatibleCommand)), + } } } |
