aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--xtask/Cargo.toml4
-rw-r--r--xtask/src/command.rs131
-rw-r--r--xtask/src/main.rs342
3 files changed, 360 insertions, 117 deletions
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index fa4f7d7..ba0e134 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -7,4 +7,6 @@ publish = false
[dependencies]
anyhow = "1.0.43"
os_pipe = "1.1.2"
-structopt = "0.3.22"
+clap = { version = "4", features = ["derive"] }
+env_logger = "0.10.0"
+log = "0.4.17"
diff --git a/xtask/src/command.rs b/xtask/src/command.rs
index 6be1463..ed73b2d 100644
--- a/xtask/src/command.rs
+++ b/xtask/src/command.rs
@@ -1,8 +1,11 @@
-use crate::Sizearguments;
-use crate::{RunResult, TestRunError};
+use crate::{debug, RunResult, Sizearguments, TestRunError};
use core::fmt;
use os_pipe::pipe;
-use std::{fs::File, io::Read, process::Command};
+use std::{
+ fs::File,
+ io::Read,
+ process::{Command, Stdio},
+};
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -14,17 +17,32 @@ pub enum BuildMode {
#[derive(Debug)]
pub enum CargoCommand<'a> {
Run {
+ cargoarg: &'a Option<&'a str>,
+ example: &'a str,
+ target: &'a str,
+ features: Option<&'a str>,
+ mode: BuildMode,
+ },
+ Build {
+ cargoarg: &'a Option<&'a str>,
example: &'a str,
target: &'a str,
features: Option<&'a str>,
mode: BuildMode,
},
BuildAll {
+ cargoarg: &'a Option<&'a str>,
target: &'a str,
features: Option<&'a str>,
mode: BuildMode,
},
+ CheckAll {
+ cargoarg: &'a Option<&'a str>,
+ target: &'a str,
+ features: Option<&'a str>,
+ },
Size {
+ cargoarg: &'a Option<&'a str>,
example: &'a str,
target: &'a str,
features: Option<&'a str>,
@@ -37,32 +55,30 @@ impl<'a> CargoCommand<'a> {
fn name(&self) -> &str {
match self {
CargoCommand::Run { .. } => "run",
+ CargoCommand::Build { .. } => "build",
CargoCommand::Size { .. } => "size",
CargoCommand::BuildAll { .. } => "build",
+ CargoCommand::CheckAll { .. } => "check",
}
}
pub fn args(&self) -> Vec<&str> {
match self {
CargoCommand::Run {
+ cargoarg,
example,
target,
features,
mode,
} => {
- let mut args = vec![
- "+nightly",
- self.name(),
- "--example",
- example,
- "--target",
- target,
- "--features",
- "test-critical-section",
- ];
+ let mut args = vec!["+nightly"];
+ if let Some(cargoarg) = cargoarg {
+ args.extend_from_slice(&[cargoarg]);
+ }
+ args.extend_from_slice(&[self.name(), "--example", example, "--target", target]);
- if let Some(feature_name) = features {
- args.extend_from_slice(&["--features", feature_name]);
+ if let Some(feature) = features {
+ args.extend_from_slice(&["--features", feature]);
}
if let Some(flag) = mode.to_flag() {
args.push(flag);
@@ -70,22 +86,56 @@ impl<'a> CargoCommand<'a> {
args
}
CargoCommand::BuildAll {
+ cargoarg,
target,
features,
mode,
} => {
- let mut args = vec![
- "+nightly",
- self.name(),
- "--examples",
- "--target",
- target,
- "--features",
- "test-critical-section",
- ];
+ let mut args = vec!["+nightly"];
+ if let Some(cargoarg) = cargoarg {
+ args.extend_from_slice(&[cargoarg]);
+ }
+ args.extend_from_slice(&[self.name(), "--examples", "--target", target]);
- if let Some(feature_name) = features {
- args.extend_from_slice(&["--features", feature_name]);
+ if let Some(feature) = features {
+ args.extend_from_slice(&["--features", feature]);
+ }
+ if let Some(flag) = mode.to_flag() {
+ args.push(flag);
+ }
+ args
+ }
+ CargoCommand::CheckAll {
+ cargoarg,
+ target,
+ features,
+ } => {
+ let mut args = vec!["+nightly"];
+ if let Some(cargoarg) = cargoarg {
+ args.extend_from_slice(&[cargoarg]);
+ }
+ args.extend_from_slice(&[self.name(), "--examples", "--target", target]);
+
+ if let Some(feature) = features {
+ args.extend_from_slice(&["--features", feature]);
+ }
+ args
+ }
+ CargoCommand::Build {
+ cargoarg,
+ example,
+ target,
+ features,
+ mode,
+ } => {
+ let mut args = vec!["+nightly"];
+ if let Some(cargoarg) = cargoarg {
+ args.extend_from_slice(&[cargoarg]);
+ }
+ args.extend_from_slice(&[self.name(), "--example", example, "--target", target]);
+
+ if let Some(feature) = features {
+ args.extend_from_slice(&["--features", feature]);
}
if let Some(flag) = mode.to_flag() {
args.push(flag);
@@ -93,22 +143,19 @@ impl<'a> CargoCommand<'a> {
args
}
CargoCommand::Size {
+ cargoarg,
example,
target,
features,
mode,
arguments,
} => {
- let mut args = vec![
- "+nightly",
- self.name(),
- "--example",
- example,
- "--target",
- target,
- "--features",
- "test-critical-section",
- ];
+ let mut args = vec!["+nightly"];
+ if let Some(cargoarg) = cargoarg {
+ args.extend_from_slice(&[cargoarg]);
+ }
+ args.extend_from_slice(&[self.name(), "--example", example, "--target", target]);
+
if let Some(feature_name) = features {
args.extend_from_slice(&["--features", feature_name]);
}
@@ -155,11 +202,13 @@ impl fmt::Display for BuildMode {
pub fn run_command(command: &CargoCommand) -> anyhow::Result<RunResult> {
let (mut reader, writer) = pipe()?;
- println!("👟 {} {}", command.command(), command.args().join(" "));
+ debug!("👟 {} {}", command.command(), command.args().join(" "));
let mut handle = Command::new(command.command())
.args(command.args())
.stdout(writer)
+ // Throw away stderr, TODO
+ .stderr(Stdio::null())
.spawn()?;
// retrieve output and clean up
@@ -176,16 +225,16 @@ pub fn run_command(command: &CargoCommand) -> anyhow::Result<RunResult> {
/// Check if `run` was successful.
/// returns Ok in case the run went as expected,
/// Err otherwise
-pub fn run_successful(run: &RunResult, expected_output_file: String) -> Result<(), TestRunError> {
+pub fn run_successful(run: &RunResult, expected_output_file: &str) -> Result<(), TestRunError> {
let mut file_handle =
- File::open(expected_output_file.clone()).map_err(|_| TestRunError::FileError {
- file: expected_output_file.clone(),
+ File::open(expected_output_file).map_err(|_| TestRunError::FileError {
+ file: expected_output_file.to_owned(),
})?;
let mut expected_output = String::new();
file_handle
.read_to_string(&mut expected_output)
.map_err(|_| TestRunError::FileError {
- file: expected_output_file.clone(),
+ file: expected_output_file.to_owned(),
})?;
if expected_output != run.output {
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 2d259dc..47f3980 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -2,16 +2,21 @@ mod build;
mod command;
use anyhow::bail;
+use clap::{Parser, Subcommand};
use core::fmt;
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::{
build::init_build_dir,
@@ -21,41 +26,88 @@ use crate::{
const ARMV6M: &str = "thumbv6m-none-eabi";
const ARMV7M: &str = "thumbv7m-none-eabi";
-#[derive(Debug, StructOpt)]
-struct Options {
+const DEFAULT_FEATURES: Option<&str> = Some("test-critical-section");
+
+#[derive(Parser)]
+#[command(author, version, about, long_about = None)]
+/// RTIC xtask powered testing toolbox
+struct Cli {
/// For which ARM target to build: v7 or v6
///
+ /// Defaults to all targets if omitted.
/// The permissible targets are:
- /// * all
- ///
- /// * thumbv6m-none-eabi
///
- /// * thumbv7m-none-eabi
- #[structopt(short, long)]
+ /// thumbv6m-none-eabi
+ /// thumbv7m-none-eabi
+ #[arg(short, long)]
target: Option<String>,
- /// Example to run, by default all examples are run
+
+ /// List of comma separated examples to run, all others are excluded
+ ///
+ /// If omitted all examples are run
///
- /// Example: `cargo xtask --example complex`
- #[structopt(short, long)]
+ /// Example: `cargo xtask --example complex,spawn,init`
+ /// would include complex, spawn and init
+ #[arg(short, long, group = "example_group")]
example: Option<String>,
- /// Enables also running `cargo size` on the selected examples
+
+ /// List of comma separated examples to exclude, all others are included
+ ///
+ /// If omitted all examples are run
+ ///
+ /// Example: `cargo xtask --excludeexample complex,spawn,init`
+ /// would exclude complex, spawn and init
+ #[arg(long, group = "example_group")]
+ exampleexclude: Option<String>,
+
+ /// Enable more verbose output, repeat up to `-vvv` for even more
+ #[arg(short, long, action = clap::ArgAction::Count)]
+ verbose: u8,
+
+ /// Subcommand picking which kind of operation
+ ///
+ /// If omitted run all tests
+ #[command(subcommand)]
+ command: Option<Commands>,
+}
+
+#[derive(Debug, Subcommand)]
+enum Commands {
+ /// Run `cargo size` on selected or all examples
///
/// To pass options to `cargo size`, add `--` and then the following
/// arguments will be passed on
///
- /// Example: `cargo xtask -s -- -A`
- #[structopt(short, long)]
- size: bool,
+ /// Example: `cargo xtask size -- -A`
+ Size(Size),
+
+ /// Run examples in QEMU and compare against expected output
+ ///
+ /// Example runtime output is matched against `rtic/ci/expected/`
+ Qemu {
+ /// If expected output is missing or mismatching, recreate the file
+ ///
+ /// This overwrites only missing or mismatching
+ #[arg(long)]
+ overwrite_expected: bool,
+ },
+ /// Build all examples
+ Build,
+ /// Check all examples
+ Check,
+}
+
+#[derive(Debug, Parser)]
+struct Size {
/// Options to pass to `cargo size`
- #[structopt(subcommand)]
+ #[command(subcommand)]
sizearguments: Option<Sizearguments>,
}
-#[derive(Clone, Debug, PartialEq, StructOpt)]
+#[derive(Clone, Debug, PartialEq, Parser)]
pub enum Sizearguments {
- // `external_subcommand` tells structopt to put
- // all the extra arguments into this Vec
- #[structopt(external_subcommand)]
+ /// All remaining flags and options
+ #[command(external_subcommand)]
Other(Vec<String>),
}
@@ -85,7 +137,7 @@ impl fmt::Display for TestRunError {
write!(f, "{got}")
}
TestRunError::FileError { file } => {
- write!(f, "File error on: {file}")
+ write!(f, "File error on: {file}\nSee flag overwrite.")
}
TestRunError::CommandError(e) => {
write!(
@@ -114,7 +166,7 @@ fn main() -> anyhow::Result<()> {
bail!("xtasks can only be executed from the root of the `rtic` repository");
}
- let targets = [ARMV7M, ARMV6M];
+ let mut targets: Vec<String> = [ARMV7M.to_owned(), ARMV6M.to_owned()].to_vec();
let mut examples: Vec<_> = std::fs::read_dir("./rtic/examples")?
.filter_map(|p| p.ok())
@@ -123,92 +175,196 @@ fn main() -> anyhow::Result<()> {
.map(|path| path.file_stem().unwrap().to_str().unwrap().to_string())
.collect();
- println!("examples: {examples:?}");
+ let cli = Cli::parse();
+
+ 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();
- let opts = Options::from_args();
- let target = &opts.target;
- let check_size = opts.size;
- let size_arguments = &opts.sizearguments;
- let example = opts.example;
+ trace!("default logging level: {0}", cli.verbose);
+ trace!("examples: {examples:?}");
+
+ let target = cli.target;
+ let example = cli.example;
if let Some(example) = example {
if examples.contains(&example) {
- println!("\nTesting example: {example}");
+ info!("Testing example: {example}");
// If we managed to filter, set the examples to test to only this one
examples = vec![example]
} else {
- eprintln!(
+ error!(
"\nThe example you specified is not available. Available examples are:\
\n{examples:#?}\n\
- By default all examples are tested.",
+ By default if example flag is emitted, all examples are tested.",
+ );
+ process::exit(1);
+ }
+ }
+ if let Some(target) = target {
+ if targets.contains(&target) {
+ debug!("\nTesting target: {target}");
+ // If we managed to filter, set the targets to test to only this one
+ targets = vec![target]
+ } else {
+ error!(
+ "\nThe target you specified is not available. Available targets are:\
+ \n{targets:#?}\n\
+ By default all targets are tested.",
);
process::exit(1);
}
}
+
init_build_dir()?;
+ #[allow(clippy::if_same_then_else)]
+ let cargoarg = if log_enabled!(Level::Trace) {
+ Some("-vv")
+ } else if log_enabled!(Level::Debug) {
+ Some("-v")
+ } else if log_enabled!(Level::Info) {
+ None
+ } else if log_enabled!(Level::Warn) || log_enabled!(Level::Error) {
+ Some("--quiet")
+ } else {
+ // Off case
+ Some("--quiet")
+ };
- match target {
- None => {
- for t in targets {
- println!("Testing all targets: {targets:?}");
- run_test(t, &examples, check_size, size_arguments)?;
+ match cli.command {
+ Some(Commands::Size(arguments)) => {
+ debug!("Measuring on target(s): {targets:?}");
+ for t in &targets {
+ info!("Measuring for target: {t:?}");
+ build_and_check_size(&cargoarg, t, &examples, &arguments.sizearguments)?;
}
}
- Some(target) => {
- if targets.contains(&target.as_str()) {
- run_test(target, &examples, check_size, size_arguments)?;
- } else {
- eprintln!(
- "The target you specified is not available. Available targets are:\
- \n{targets:?}\n",
- );
- process::exit(1);
+ Some(Commands::Qemu {
+ overwrite_expected: overwrite,
+ }) => {
+ debug!("Testing on target(s): {targets:?}");
+ for t in &targets {
+ info!("Testing for target: {t:?}");
+ run_test(&cargoarg, t, &examples, overwrite)?;
+ }
+ }
+ Some(Commands::Build) => {
+ debug!("Building for target(s): {targets:?}");
+ for t in &targets {
+ info!("Building for target: {t:?}");
+ build_all(&cargoarg, t)?;
+ }
+ }
+ Some(Commands::Check) => {
+ debug!("Checking on target(s): {targets:?}");
+ for t in &targets {
+ info!("Checking on target: {t:?}");
+ check_all(&cargoarg, t)?;
}
}
+ None => {
+ todo!();
+ }
}
Ok(())
}
+fn build_all(cargoarg: &Option<&str>, target: &str) -> anyhow::Result<()> {
+ arm_example(
+ &CargoCommand::BuildAll {
+ cargoarg,
+ target,
+ features: DEFAULT_FEATURES,
+ mode: BuildMode::Release,
+ },
+ false,
+ )?;
+ Ok(())
+}
+
+fn check_all(cargoarg: &Option<&str>, target: &str) -> anyhow::Result<()> {
+ arm_example(
+ &CargoCommand::CheckAll {
+ cargoarg,
+ target,
+ features: DEFAULT_FEATURES,
+ },
+ false,
+ )?;
+ Ok(())
+}
+
fn run_test(
+ cargoarg: &Option<&str>,
target: &str,
examples: &[String],
- check_size: bool,
- size_arguments: &Option<Sizearguments>,
+ overwrite: bool,
) -> anyhow::Result<()> {
- arm_example(&CargoCommand::BuildAll {
- target,
- features: None,
- mode: BuildMode::Release,
- })?;
-
for example in examples {
+ let cmd = CargoCommand::Build {
+ cargoarg: &Some("--quiet"),
+ example,
+ target,
+ features: DEFAULT_FEATURES,
+ mode: BuildMode::Release,
+ };
+ arm_example(&cmd, false)?;
+
let cmd = CargoCommand::Run {
+ cargoarg,
example,
target,
- features: None,
+ features: DEFAULT_FEATURES,
mode: BuildMode::Release,
};
- arm_example(&cmd)?;
+ arm_example(&cmd, overwrite)?;
}
- if check_size {
- for example in examples {
- arm_example(&CargoCommand::Size {
- example,
- target,
- features: None,
- mode: BuildMode::Release,
- arguments: size_arguments.clone(),
- })?;
- }
+
+ Ok(())
+}
+
+fn build_and_check_size(
+ cargoarg: &Option<&str>,
+ target: &str,
+ examples: &[String],
+ size_arguments: &Option<Sizearguments>,
+) -> anyhow::Result<()> {
+ for example in examples {
+ // Make sure the requested example(s) are built
+ let cmd = CargoCommand::Build {
+ cargoarg: &Some("--quiet"),
+ example,
+ target,
+ features: DEFAULT_FEATURES,
+ mode: BuildMode::Release,
+ };
+ arm_example(&cmd, false)?;
+
+ let cmd = CargoCommand::Size {
+ cargoarg,
+ example,
+ target,
+ features: DEFAULT_FEATURES,
+ mode: BuildMode::Release,
+ arguments: size_arguments.clone(),
+ };
+ arm_example(&cmd, false)?;
}
Ok(())
}
// run example binary `example`
-fn arm_example(command: &CargoCommand) -> anyhow::Result<()> {
+fn arm_example(command: &CargoCommand, overwrite: bool) -> anyhow::Result<()> {
match *command {
CargoCommand::Run { example, .. } => {
let run_file = format!("{example}.run");
@@ -219,26 +375,62 @@ fn arm_example(command: &CargoCommand) -> anyhow::Result<()> {
.into_string()
.map_err(TestRunError::PathConversionError)?;
- // command is either build or run
+ // cargo run <..>
let cargo_run_result = run_command(command)?;
- println!("{}", cargo_run_result.output);
+ info!("{}", cargo_run_result.output);
- if let CargoCommand::Run { .. } = &command {
- 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!("Creating/updating file: {expected_output_file}");
+ file_handle.write_all(cargo_run_result.output.as_bytes())?;
+ };
+ } else {
+ run_successful(&cargo_run_result, &expected_output_file)?;
+ }
+ Ok(())
+ }
+ CargoCommand::Build { .. } => {
+ // cargo run <..>
+ let cargo_build_result = run_command(command)?;
+ if !cargo_build_result.output.is_empty() {
+ info!("{}", cargo_build_result.output);
}
Ok(())
}
CargoCommand::BuildAll { .. } => {
- // command is either build or run
- let cargo_run_result = run_command(command)?;
- println!("{}", cargo_run_result.output);
+ // cargo build --examples
+ let cargo_build_result = run_command(command)?;
+ if !cargo_build_result.output.is_empty() {
+ info!("{}", cargo_build_result.output);
+ }
+
+ Ok(())
+ }
+ CargoCommand::CheckAll { .. } => {
+ // cargo check --examples
+ let cargo_check_result = run_command(command)?;
+ if !cargo_check_result.output.is_empty() {
+ info!("{}", cargo_check_result.output);
+ }
Ok(())
}
CargoCommand::Size { .. } => {
- let cargo_run_result = run_command(command)?;
- println!("{}", cargo_run_result.output);
+ // cargo size
+ let cargo_size_result = run_command(command)?;
+ if !cargo_size_result.output.is_empty() {
+ info!("{}", cargo_size_result.output);
+ }
Ok(())
}
}