From b87d55f960178971652df95257ddd1ea4f39c6c0 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 11:12:44 +0200 Subject: Move run into a subdirectory and split `iter` stuff into a module --- xtask/src/run/iter.rs | 48 +++++ xtask/src/run/mod.rs | 487 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 xtask/src/run/iter.rs create mode 100644 xtask/src/run/mod.rs (limited to 'xtask/src/run') diff --git a/xtask/src/run/iter.rs b/xtask/src/run/iter.rs new file mode 100644 index 0000000..d18ad49 --- /dev/null +++ b/xtask/src/run/iter.rs @@ -0,0 +1,48 @@ +use super::FinalRunResult; + +pub use iter::*; + +pub trait CoalescingRunner<'c> { + /// Run all the commands in this iterator, and coalesce the results into + /// one error (if any individual commands failed) + fn run_and_coalesce(self) -> Vec>; +} + +#[cfg(not(feature = "rayon"))] +mod iter { + use super::*; + use crate::{argument_parsing::Globals, cargo_command::*, run::run_and_convert}; + + pub fn into_iter(var: T) -> impl Iterator { + var.into_iter() + } + + impl<'g, 'c, I> CoalescingRunner<'c> for I + where + I: Iterator, bool)>, + { + fn run_and_coalesce(self) -> Vec> { + self.map(run_and_convert).collect() + } + } +} + +#[cfg(feature = "rayon")] +mod iter { + use super::*; + use crate::{argument_parsing::Globals, cargo_command::*, run::run_and_convert}; + use rayon::prelude::*; + + pub fn into_iter(var: T) -> impl ParallelIterator { + var.into_par_iter() + } + + impl<'g, 'c, I> CoalescingRunner<'c> for I + where + I: ParallelIterator, bool)>, + { + fn run_and_coalesce(self) -> Vec> { + self.map(run_and_convert).collect() + } + } +} diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs new file mode 100644 index 0000000..daa6256 --- /dev/null +++ b/xtask/src/run/mod.rs @@ -0,0 +1,487 @@ +use std::{ + fs::File, + io::Read, + path::PathBuf, + process::{Command, Stdio}, +}; + +mod iter; +use iter::{into_iter, CoalescingRunner}; + +use crate::{ + argument_parsing::{Backends, BuildOrCheck, ExtraArguments, Globals, PackageOpt, TestMetadata}, + cargo_command::{BuildMode, CargoCommand}, + command_parser, RunResult, TestRunError, +}; + +use log::{error, info, Level}; + +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum OutputMode { + PipedAndCollected, + Inherited, +} + +impl From for Stdio { + fn from(value: OutputMode) -> Self { + match value { + OutputMode::PipedAndCollected => Stdio::piped(), + OutputMode::Inherited => Stdio::inherit(), + } + } +} + +#[derive(Debug)] +pub enum FinalRunResult<'c> { + Success(CargoCommand<'c>, RunResult), + Failed(CargoCommand<'c>, RunResult), + CommandError(CargoCommand<'c>, anyhow::Error), +} + +fn run_and_convert<'a>( + (global, command, overwrite): (&Globals, CargoCommand<'a>, bool), +) -> FinalRunResult<'a> { + // Run the command + let result = command_parser(global, &command, overwrite); + + let output = match result { + // If running the command succeeded without looking at any of the results, + // log the data and see if the actual execution was succesfull too. + Ok(result) => { + if result.exit_status.success() { + FinalRunResult::Success(command, result) + } else { + FinalRunResult::Failed(command, result) + } + } + // If it didn't and some IO error occured, just panic + Err(e) => FinalRunResult::CommandError(command, e), + }; + + log::trace!("Final result: {output:?}"); + + output +} + +/// Cargo command to either build or check +pub fn cargo<'c>( + globals: &Globals, + operation: BuildOrCheck, + cargoarg: &'c Option<&'c str>, + package: &'c PackageOpt, + backend: Backends, +) -> Vec> { + let runner = package + .packages() + .flat_map(|package| { + let target = backend.to_target(); + let features = package.features(target, backend, globals.partial); + into_iter(features).map(move |f| (package, target, f)) + }) + .map(move |(package, target, features)| { + let target = target.into(); + let command = match operation { + BuildOrCheck::Check => CargoCommand::Check { + cargoarg, + package: Some(package.name()), + target, + features, + mode: BuildMode::Release, + dir: None, + }, + BuildOrCheck::Build => CargoCommand::Build { + cargoarg, + package: Some(package.name()), + target, + features, + mode: BuildMode::Release, + dir: None, + }, + }; + + (globals, command, false) + }); + + runner.run_and_coalesce() +} + +/// Cargo command to build a usage example. +/// +/// The usage examples are in examples/ +pub fn cargo_usage_example( + globals: &Globals, + operation: BuildOrCheck, + usage_examples: Vec, +) -> Vec> { + into_iter(&usage_examples) + .map(|example| { + let path = format!("examples/{example}"); + + let command = match operation { + BuildOrCheck::Check => CargoCommand::Check { + cargoarg: &None, + mode: BuildMode::Release, + dir: Some(path.into()), + package: None, + target: None, + features: None, + }, + BuildOrCheck::Build => CargoCommand::Build { + cargoarg: &None, + package: None, + target: None, + features: None, + mode: BuildMode::Release, + dir: Some(path.into()), + }, + }; + (globals, command, false) + }) + .run_and_coalesce() +} + +/// Cargo command to either build or check all examples +/// +/// The examples are in rtic/examples +pub fn cargo_example<'c>( + globals: &Globals, + operation: BuildOrCheck, + cargoarg: &'c Option<&'c str>, + backend: Backends, + examples: &'c [String], +) -> Vec> { + let runner = into_iter(examples).map(|example| { + let features = Some(backend.to_target().and_features(backend.to_rtic_feature())); + + let command = match operation { + BuildOrCheck::Check => CargoCommand::ExampleCheck { + cargoarg, + example, + target: Some(backend.to_target()), + features, + mode: BuildMode::Release, + }, + BuildOrCheck::Build => CargoCommand::ExampleBuild { + cargoarg, + example, + target: Some(backend.to_target()), + features, + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + }, + }; + (globals, command, false) + }); + runner.run_and_coalesce() +} + +/// Run cargo clippy on selected package +pub fn cargo_clippy<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + package: &'c PackageOpt, + backend: Backends, +) -> Vec> { + let runner = package + .packages() + .flat_map(|package| { + let target = backend.to_target(); + let features = package.features(target, backend, globals.partial); + into_iter(features).map(move |f| (package, target, f)) + }) + .map(move |(package, target, features)| { + let command = CargoCommand::Clippy { + cargoarg, + package: Some(package.name()), + target: target.into(), + features, + }; + + (globals, command, false) + }); + + runner.run_and_coalesce() +} + +/// Run cargo fmt on selected package +pub fn cargo_format<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + package: &'c PackageOpt, + check_only: bool, +) -> Vec> { + let runner = package.packages().map(|p| { + ( + globals, + CargoCommand::Format { + cargoarg, + package: Some(p.name()), + check_only, + }, + false, + ) + }); + runner.run_and_coalesce() +} + +/// Run cargo doc +pub fn cargo_doc<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + backend: Backends, + arguments: &'c Option, +) -> Vec> { + let features = Some(backend.to_target().and_features(backend.to_rtic_feature())); + + let command = CargoCommand::Doc { + cargoarg, + features, + arguments: arguments.clone(), + }; + + vec![run_and_convert((globals, command, false))] +} + +/// Run cargo test on the selected package or all packages +/// +/// If no package is specified, loop through all packages +pub fn cargo_test<'c>( + globals: &Globals, + package: &'c PackageOpt, + backend: Backends, +) -> Vec> { + package + .packages() + .map(|p| (globals, TestMetadata::match_package(p, backend), false)) + .run_and_coalesce() +} + +/// Use mdbook to build the book +pub fn cargo_book<'c>( + globals: &Globals, + arguments: &'c Option, +) -> Vec> { + vec![run_and_convert(( + globals, + CargoCommand::Book { + arguments: arguments.clone(), + }, + false, + ))] +} + +/// Run examples +/// +/// Supports updating the expected output via the overwrite argument +pub fn qemu_run_examples<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + backend: Backends, + examples: &'c [String], + overwrite: bool, +) -> Vec> { + let target = backend.to_target(); + let features = Some(target.and_features(backend.to_rtic_feature())); + + into_iter(examples) + .flat_map(|example| { + let target = target.into(); + let cmd_build = CargoCommand::ExampleBuild { + cargoarg: &None, + example, + target, + features: features.clone(), + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + }; + + let cmd_qemu = CargoCommand::Qemu { + cargoarg, + example, + target, + features: features.clone(), + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + }; + + into_iter([cmd_build, cmd_qemu]) + }) + .map(|cmd| (globals, cmd, overwrite)) + .run_and_coalesce() +} + +/// Check the binary sizes of examples +pub fn build_and_check_size<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + backend: Backends, + examples: &'c [String], + arguments: &'c Option, +) -> Vec> { + let target = backend.to_target(); + let features = Some(target.and_features(backend.to_rtic_feature())); + + let runner = into_iter(examples).map(|example| { + let target = target.into(); + + // Make sure the requested example(s) are built + let cmd = CargoCommand::ExampleBuild { + cargoarg: &Some("--quiet"), + example, + target, + features: features.clone(), + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + }; + if let Err(err) = command_parser(globals, &cmd, false) { + error!("{err}"); + } + + let cmd = CargoCommand::ExampleSize { + cargoarg, + example, + target, + features: features.clone(), + mode: BuildMode::Release, + arguments: arguments.clone(), + dir: Some(PathBuf::from("./rtic")), + }; + (globals, cmd, false) + }); + + runner.run_and_coalesce() +} + +pub fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Result { + log::info!("👟 {command}"); + + let mut process = Command::new(command.executable()); + + process + .args(command.args()) + .stdout(Stdio::piped()) + .stderr(stderr_mode); + + if let Some(dir) = command.chdir() { + process.current_dir(dir.canonicalize()?); + } + + let result = process.output()?; + + let exit_status = result.status; + let stderr = String::from_utf8(result.stderr).unwrap_or("Not displayable".into()); + let stdout = String::from_utf8(result.stdout).unwrap_or("Not displayable".into()); + + if command.print_stdout_intermediate() && exit_status.success() { + log::info!("\n{}", stdout); + } + + if exit_status.success() { + log::info!("✅ Success.") + } else { + log::error!("❌ Command failed. Run to completion for the summary."); + } + + Ok(RunResult { + exit_status, + stdout, + stderr, + }) +} + +/// 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: &str) -> Result<(), TestRunError> { + let mut file_handle = + 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.to_owned(), + })?; + + if expected_output != run.stdout { + Err(TestRunError::FileCmpError { + expected: expected_output.clone(), + got: run.stdout.clone(), + }) + } else if !run.exit_status.success() { + Err(TestRunError::CommandError(run.clone())) + } else { + Ok(()) + } +} + +pub fn handle_results(globals: &Globals, results: Vec) -> Result<(), ()> { + let errors = results.iter().filter_map(|r| { + if let FinalRunResult::Failed(c, r) = r { + Some((c, &r.stdout, &r.stderr)) + } else { + None + } + }); + + let successes = results.iter().filter_map(|r| { + if let FinalRunResult::Success(c, r) = r { + Some((c, &r.stdout, &r.stderr)) + } else { + None + } + }); + + let command_errors = results.iter().filter_map(|r| { + if let FinalRunResult::CommandError(c, e) = r { + Some((c, e)) + } else { + None + } + }); + + let log_stdout_stderr = |level: Level| { + move |(cmd, stdout, stderr): (&CargoCommand, &String, &String)| { + let cmd = cmd.as_cmd_string(); + if !stdout.is_empty() && !stderr.is_empty() { + log::log!(level, "\n{cmd}\nStdout:\n{stdout}\nStderr:\n{stderr}"); + } else if !stdout.is_empty() { + log::log!(level, "\n{cmd}\nStdout:\n{}", stdout.trim_end()); + } else if !stderr.is_empty() { + log::log!(level, "\n{cmd}\nStderr:\n{}", stderr.trim_end()); + } + } + }; + + successes.for_each(|(cmd, stdout, stderr)| { + if globals.verbose > 0 { + info!("✅ Success: {cmd}\n {}", cmd.as_cmd_string()); + } else { + info!("✅ Success: {cmd}"); + } + + log_stdout_stderr(Level::Debug)((cmd, stdout, stderr)); + }); + + errors.clone().for_each(|(cmd, stdout, stderr)| { + error!("❌ Failed: {cmd}\n {}", cmd.as_cmd_string()); + log_stdout_stderr(Level::Error)((cmd, stdout, stderr)); + }); + + command_errors + .clone() + .for_each(|(cmd, error)| error!("❌ Failed: {cmd}\n {}\n{error}", cmd.as_cmd_string())); + + let ecount = errors.count() + command_errors.count(); + if ecount != 0 { + log::error!("{ecount} commands failed."); + Err(()) + } else { + info!("🚀🚀🚀 All tasks succeeded 🚀🚀🚀"); + Ok(()) + } +} -- cgit v1.2.3 From 65f1f4c1b7f8de50c6f9462a39c98dcd3a73b4cc Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 11:26:23 +0200 Subject: Also separate all results and data --- xtask/src/main.rs | 126 +------------------------------ xtask/src/run/data.rs | 87 +++++++++++++++++++++ xtask/src/run/mod.rs | 191 ++++++++++++++++++----------------------------- xtask/src/run/results.rs | 122 ++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+), 243 deletions(-) create mode 100644 xtask/src/run/data.rs create mode 100644 xtask/src/run/results.rs (limited to 'xtask/src/run') diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1712d71..30c3da0 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -3,26 +3,16 @@ mod build; mod cargo_command; mod run; -use argument_parsing::{ExtraArguments, Globals}; +use argument_parsing::ExtraArguments; 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::ExitStatus, - str, -}; +use std::{path::Path, str}; use log::{error, info, log_enabled, trace, Level}; use crate::{ argument_parsing::{Backends, BuildOrCheck, Cli, Commands}, build::init_build_dir, - cargo_command::CargoCommand, run::*, }; @@ -65,56 +55,6 @@ const ARMV7M: Target = Target::new("thumbv7m-none-eabi", false); const ARMV8MBASE: Target = Target::new("thumbv8m.base-none-eabi", false); const ARMV8MMAIN: Target = Target::new("thumbv8m.main-none-eabi", false); -#[derive(Debug, Clone)] -pub struct RunResult { - exit_status: ExitStatus, - stdout: String, - stderr: String, -} - -#[derive(Debug)] -pub enum TestRunError { - FileCmpError { expected: String, got: String }, - FileError { file: String }, - PathConversionError(OsString), - 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 } => { - 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}\nSee flag --overwrite-expected to create/update expected output.") - } - TestRunError::CommandError(e) => { - write!( - f, - "Command failed with exit status {}: {} {}", - e.exit_status, e.stdout, e.stderr - ) - } - TestRunError::PathConversionError(p) => { - write!(f, "Can't convert path from `OsString` to `String`: {p:?}") - } - TestRunError::IncompatibleCommand => { - write!(f, "Can't run that command in this context") - } - } - } -} - -impl Error for TestRunError {} - 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) @@ -299,65 +239,3 @@ fn main() -> anyhow::Result<()> { handle_results(globals, final_run_results).map_err(|_| anyhow::anyhow!("Commands failed")) } - -// run example binary `example` -fn command_parser( - glob: &Globals, - command: &CargoCommand, - overwrite: bool, -) -> anyhow::Result { - let output_mode = if glob.stderr_inherited { - OutputMode::Inherited - } else { - OutputMode::PipedAndCollected - }; - - match *command { - CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { - let run_file = format!("{example}.run"); - let expected_output_file = ["rtic", "ci", "expected", &run_file] - .iter() - .collect::() - .into_os_string() - .into_string() - .map_err(TestRunError::PathConversionError)?; - - // cargo run <..> - let cargo_run_result = run_command(command, output_mode)?; - - // 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(cargo_run_result) - } - 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, output_mode)?; - Ok(cargo_result) - } - } -} diff --git a/xtask/src/run/data.rs b/xtask/src/run/data.rs new file mode 100644 index 0000000..eacd72c --- /dev/null +++ b/xtask/src/run/data.rs @@ -0,0 +1,87 @@ +use std::{ + ffi::OsString, + process::{ExitStatus, Stdio}, +}; + +use diffy::{create_patch, PatchFormatter}; + +use crate::cargo_command::CargoCommand; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum OutputMode { + PipedAndCollected, + Inherited, +} + +impl From for Stdio { + fn from(value: OutputMode) -> Self { + match value { + OutputMode::PipedAndCollected => Stdio::piped(), + OutputMode::Inherited => Stdio::inherit(), + } + } +} + +#[derive(Debug, Clone)] +pub struct RunResult { + pub exit_status: ExitStatus, + pub stdout: String, + pub stderr: String, +} + +#[derive(Debug)] +pub enum FinalRunResult<'c> { + Success(CargoCommand<'c>, RunResult), + Failed(CargoCommand<'c>, RunResult), + CommandError(CargoCommand<'c>, anyhow::Error), +} + +#[derive(Debug)] +pub enum TestRunError { + FileCmpError { + expected: String, + got: String, + }, + FileError { + file: String, + }, + PathConversionError(OsString), + CommandError(RunResult), + #[allow(dead_code)] + IncompatibleCommand, +} + +impl core::fmt::Display for TestRunError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestRunError::FileCmpError { expected, 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}\nSee flag --overwrite-expected to create/update expected output.") + } + TestRunError::CommandError(e) => { + write!( + f, + "Command failed with exit status {}: {} {}", + e.exit_status, e.stdout, e.stderr + ) + } + TestRunError::PathConversionError(p) => { + write!(f, "Can't convert path from `OsString` to `String`: {p:?}") + } + TestRunError::IncompatibleCommand => { + write!(f, "Can't run that command in this context") + } + } + } +} + +impl std::error::Error for TestRunError {} diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs index daa6256..501849d 100644 --- a/xtask/src/run/mod.rs +++ b/xtask/src/run/mod.rs @@ -1,45 +1,30 @@ use std::{ fs::File, - io::Read, + io::Write, path::PathBuf, process::{Command, Stdio}, }; +mod results; +pub use results::handle_results; + +mod data; +use data::*; + mod iter; use iter::{into_iter, CoalescingRunner}; use crate::{ argument_parsing::{Backends, BuildOrCheck, ExtraArguments, Globals, PackageOpt, TestMetadata}, cargo_command::{BuildMode, CargoCommand}, - command_parser, RunResult, TestRunError, }; -use log::{error, info, Level}; +use log::{error, info}; #[cfg(feature = "rayon")] use rayon::prelude::*; -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum OutputMode { - PipedAndCollected, - Inherited, -} - -impl From for Stdio { - fn from(value: OutputMode) -> Self { - match value { - OutputMode::PipedAndCollected => Stdio::piped(), - OutputMode::Inherited => Stdio::inherit(), - } - } -} - -#[derive(Debug)] -pub enum FinalRunResult<'c> { - Success(CargoCommand<'c>, RunResult), - Failed(CargoCommand<'c>, RunResult), - CommandError(CargoCommand<'c>, anyhow::Error), -} +use self::results::run_successful; fn run_and_convert<'a>( (global, command, overwrite): (&Globals, CargoCommand<'a>, bool), @@ -66,6 +51,68 @@ fn run_and_convert<'a>( output } +// run example binary `example` +fn command_parser( + glob: &Globals, + command: &CargoCommand, + overwrite: bool, +) -> anyhow::Result { + let output_mode = if glob.stderr_inherited { + OutputMode::Inherited + } else { + OutputMode::PipedAndCollected + }; + + match *command { + CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { + let run_file = format!("{example}.run"); + let expected_output_file = ["rtic", "ci", "expected", &run_file] + .iter() + .collect::() + .into_os_string() + .into_string() + .map_err(TestRunError::PathConversionError)?; + + // cargo run <..> + let cargo_run_result = run_command(command, output_mode)?; + + // 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(cargo_run_result) + } + 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, output_mode)?; + Ok(cargo_result) + } + } +} + /// Cargo command to either build or check pub fn cargo<'c>( globals: &Globals, @@ -355,7 +402,7 @@ pub fn build_and_check_size<'c>( runner.run_and_coalesce() } -pub fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Result { +fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Result { log::info!("👟 {command}"); let mut process = Command::new(command.executable()); @@ -391,97 +438,3 @@ pub fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::R stderr, }) } - -/// 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: &str) -> Result<(), TestRunError> { - let mut file_handle = - 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.to_owned(), - })?; - - if expected_output != run.stdout { - Err(TestRunError::FileCmpError { - expected: expected_output.clone(), - got: run.stdout.clone(), - }) - } else if !run.exit_status.success() { - Err(TestRunError::CommandError(run.clone())) - } else { - Ok(()) - } -} - -pub fn handle_results(globals: &Globals, results: Vec) -> Result<(), ()> { - let errors = results.iter().filter_map(|r| { - if let FinalRunResult::Failed(c, r) = r { - Some((c, &r.stdout, &r.stderr)) - } else { - None - } - }); - - let successes = results.iter().filter_map(|r| { - if let FinalRunResult::Success(c, r) = r { - Some((c, &r.stdout, &r.stderr)) - } else { - None - } - }); - - let command_errors = results.iter().filter_map(|r| { - if let FinalRunResult::CommandError(c, e) = r { - Some((c, e)) - } else { - None - } - }); - - let log_stdout_stderr = |level: Level| { - move |(cmd, stdout, stderr): (&CargoCommand, &String, &String)| { - let cmd = cmd.as_cmd_string(); - if !stdout.is_empty() && !stderr.is_empty() { - log::log!(level, "\n{cmd}\nStdout:\n{stdout}\nStderr:\n{stderr}"); - } else if !stdout.is_empty() { - log::log!(level, "\n{cmd}\nStdout:\n{}", stdout.trim_end()); - } else if !stderr.is_empty() { - log::log!(level, "\n{cmd}\nStderr:\n{}", stderr.trim_end()); - } - } - }; - - successes.for_each(|(cmd, stdout, stderr)| { - if globals.verbose > 0 { - info!("✅ Success: {cmd}\n {}", cmd.as_cmd_string()); - } else { - info!("✅ Success: {cmd}"); - } - - log_stdout_stderr(Level::Debug)((cmd, stdout, stderr)); - }); - - errors.clone().for_each(|(cmd, stdout, stderr)| { - error!("❌ Failed: {cmd}\n {}", cmd.as_cmd_string()); - log_stdout_stderr(Level::Error)((cmd, stdout, stderr)); - }); - - command_errors - .clone() - .for_each(|(cmd, error)| error!("❌ Failed: {cmd}\n {}\n{error}", cmd.as_cmd_string())); - - let ecount = errors.count() + command_errors.count(); - if ecount != 0 { - log::error!("{ecount} commands failed."); - Err(()) - } else { - info!("🚀🚀🚀 All tasks succeeded 🚀🚀🚀"); - Ok(()) - } -} diff --git a/xtask/src/run/results.rs b/xtask/src/run/results.rs new file mode 100644 index 0000000..072cbcf --- /dev/null +++ b/xtask/src/run/results.rs @@ -0,0 +1,122 @@ +use log::{error, info, log, Level}; + +use crate::{argument_parsing::Globals, cargo_command::CargoCommand}; + +use super::data::{FinalRunResult, RunResult, TestRunError}; + +const TARGET: &str = "xtask::results"; + +/// 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: &str) -> Result<(), TestRunError> { + let file = expected_output_file.to_string(); + + let expected_output = std::fs::read(expected_output_file) + .map(|d| String::from_utf8(d).map_err(|_| TestRunError::FileError { file: file.clone() })) + .map_err(|_| TestRunError::FileError { file })??; + + if expected_output != run.stdout { + Err(TestRunError::FileCmpError { + expected: expected_output.clone(), + got: run.stdout.clone(), + }) + } else if !run.exit_status.success() { + Err(TestRunError::CommandError(run.clone())) + } else { + Ok(()) + } +} + +pub fn handle_results(globals: &Globals, results: Vec) -> Result<(), ()> { + let errors = results.iter().filter_map(|r| { + if let FinalRunResult::Failed(c, r) = r { + Some((c, &r.stdout, &r.stderr)) + } else { + None + } + }); + + let successes = results.iter().filter_map(|r| { + if let FinalRunResult::Success(c, r) = r { + Some((c, &r.stdout, &r.stderr)) + } else { + None + } + }); + + let command_errors = results.iter().filter_map(|r| { + if let FinalRunResult::CommandError(c, e) = r { + Some((c, e)) + } else { + None + } + }); + + let log_stdout_stderr = |level: Level| { + move |(cmd, stdout, stderr): (&CargoCommand, &String, &String)| { + let cmd = cmd.as_cmd_string(); + if !stdout.is_empty() && !stderr.is_empty() { + log!( + target: TARGET, + level, + "\n{cmd}\nStdout:\n{stdout}\nStderr:\n{stderr}" + ); + } else if !stdout.is_empty() { + log!( + target: TARGET, + level, + "\n{cmd}\nStdout:\n{}", + stdout.trim_end() + ); + } else if !stderr.is_empty() { + log!( + target: TARGET, + level, + "\n{cmd}\nStderr:\n{}", + stderr.trim_end() + ); + } + } + }; + + successes.for_each(|(cmd, stdout, stderr)| { + if globals.verbose > 0 { + info!( + target: TARGET, + "✅ Success: {cmd}\n {}", + cmd.as_cmd_string() + ); + } else { + info!(target: TARGET, "✅ Success: {cmd}"); + } + + log_stdout_stderr(Level::Debug)((cmd, stdout, stderr)); + }); + + errors.clone().for_each(|(cmd, stdout, stderr)| { + error!( + target: TARGET, + "❌ Failed: {cmd}\n {}", + cmd.as_cmd_string() + ); + log_stdout_stderr(Level::Error)((cmd, stdout, stderr)); + }); + + command_errors.clone().for_each(|(cmd, error)| { + error!( + target: TARGET, + "❌ Failed: {cmd}\n {}\n{error}", + cmd.as_cmd_string() + ) + }); + + let ecount = errors.count() + command_errors.count(); + if ecount != 0 { + error!(target: TARGET, "{ecount} commands failed."); + Err(()) + } else { + info!(target: TARGET, "🚀🚀🚀 All tasks succeeded 🚀🚀🚀"); + Ok(()) + } +} -- cgit v1.2.3 From fd011cd5ecb65b076f94376214ff31e7edcff189 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 11:37:36 +0200 Subject: FIx printing for Run and Qemu --- xtask/src/run/mod.rs | 58 ++++++++++++++++++++++++++++++++++++++++-------- xtask/src/run/results.rs | 24 +------------------- 2 files changed, 50 insertions(+), 32 deletions(-) (limited to 'xtask/src/run') diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs index 501849d..035dc7a 100644 --- a/xtask/src/run/mod.rs +++ b/xtask/src/run/mod.rs @@ -24,8 +24,6 @@ use log::{error, info}; #[cfg(feature = "rayon")] use rayon::prelude::*; -use self::results::run_successful; - fn run_and_convert<'a>( (global, command, overwrite): (&Globals, CargoCommand<'a>, bool), ) -> FinalRunResult<'a> { @@ -65,6 +63,42 @@ fn command_parser( match *command { CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { + /// 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: &str, + ) -> Result<(), TestRunError> { + let file = expected_output_file.to_string(); + + let expected_output = std::fs::read(expected_output_file) + .map(|d| { + String::from_utf8(d) + .map_err(|_| TestRunError::FileError { file: file.clone() }) + }) + .map_err(|_| TestRunError::FileError { file })??; + + let res = if expected_output != run.stdout { + Err(TestRunError::FileCmpError { + expected: expected_output.clone(), + got: run.stdout.clone(), + }) + } else if !run.exit_status.success() { + Err(TestRunError::CommandError(run.clone())) + } else { + Ok(()) + }; + + if res.is_ok() { + log::info!("✅ Success."); + } else { + log::error!("❌ Command failed. Run to completion for the summary."); + } + + res + } + let run_file = format!("{example}.run"); let expected_output_file = ["rtic", "ci", "expected", &run_file] .iter() @@ -74,7 +108,7 @@ fn command_parser( .map_err(TestRunError::PathConversionError)?; // cargo run <..> - let cargo_run_result = run_command(command, output_mode)?; + let cargo_run_result = run_command(command, output_mode, false)?; // Create a file for the expected output if it does not exist or mismatches if overwrite { @@ -107,7 +141,7 @@ fn command_parser( | CargoCommand::Test { .. } | CargoCommand::Book { .. } | CargoCommand::ExampleSize { .. } => { - let cargo_result = run_command(command, output_mode)?; + let cargo_result = run_command(command, output_mode, true)?; Ok(cargo_result) } } @@ -402,7 +436,11 @@ pub fn build_and_check_size<'c>( runner.run_and_coalesce() } -fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Result { +fn run_command( + command: &CargoCommand, + stderr_mode: OutputMode, + print_command_success: bool, +) -> anyhow::Result { log::info!("👟 {command}"); let mut process = Command::new(command.executable()); @@ -426,10 +464,12 @@ fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Resul log::info!("\n{}", stdout); } - if exit_status.success() { - log::info!("✅ Success.") - } else { - log::error!("❌ Command failed. Run to completion for the summary."); + if print_command_success { + if exit_status.success() { + log::info!("✅ Success.") + } else { + log::error!("❌ Command failed. Run to completion for the summary."); + } } Ok(RunResult { diff --git a/xtask/src/run/results.rs b/xtask/src/run/results.rs index 072cbcf..b64e7b1 100644 --- a/xtask/src/run/results.rs +++ b/xtask/src/run/results.rs @@ -2,32 +2,10 @@ use log::{error, info, log, Level}; use crate::{argument_parsing::Globals, cargo_command::CargoCommand}; -use super::data::{FinalRunResult, RunResult, TestRunError}; +use super::data::FinalRunResult; const TARGET: &str = "xtask::results"; -/// 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: &str) -> Result<(), TestRunError> { - let file = expected_output_file.to_string(); - - let expected_output = std::fs::read(expected_output_file) - .map(|d| String::from_utf8(d).map_err(|_| TestRunError::FileError { file: file.clone() })) - .map_err(|_| TestRunError::FileError { file })??; - - if expected_output != run.stdout { - Err(TestRunError::FileCmpError { - expected: expected_output.clone(), - got: run.stdout.clone(), - }) - } else if !run.exit_status.success() { - Err(TestRunError::CommandError(run.clone())) - } else { - Ok(()) - } -} - pub fn handle_results(globals: &Globals, results: Vec) -> Result<(), ()> { let errors = results.iter().filter_map(|r| { if let FinalRunResult::Failed(c, r) = r { -- cgit v1.2.3 From 4d3361658b2487154d8140b06b140e72c0227441 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 11:51:28 +0200 Subject: Unconditionally deny warnings for clippy --- xtask/src/cargo_command.rs | 21 +++++++++++++++++++-- xtask/src/run/mod.rs | 1 + 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'xtask/src/run') diff --git a/xtask/src/cargo_command.rs b/xtask/src/cargo_command.rs index f399b87..9cf4f65 100644 --- a/xtask/src/cargo_command.rs +++ b/xtask/src/cargo_command.rs @@ -65,6 +65,7 @@ pub enum CargoCommand<'a> { package: Option, target: Option>, features: Option, + deny_warnings: bool, }, Format { cargoarg: &'a Option<&'a str>, @@ -244,10 +245,16 @@ impl core::fmt::Display for CargoCommand<'_> { package, target, features, + deny_warnings, } => { let details = details(target, None, features, cargoarg, None); let package = p(package); - write!(f, "Clippy {package} {details}") + let deny_warns = if *deny_warnings { + format!(" (deny warnings)") + } else { + format!("") + }; + write!(f, "Clippy{deny_warns} {package} {details}") } CargoCommand::Format { cargoarg, @@ -483,9 +490,19 @@ impl<'a> CargoCommand<'a> { cargoarg, package, features, + deny_warnings, // Target is added by build_args target: _, - } => self.build_args(true, cargoarg, features, None, p(package)), + } => { + let package = p(package); + let extra = if *deny_warnings { + vec!["--", "-D", "warnings"].into_iter() + } else { + vec![].into_iter() + }; + + self.build_args(true, cargoarg, features, None, package.chain(extra)) + } CargoCommand::Doc { cargoarg, features, diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs index 035dc7a..ac35f5c 100644 --- a/xtask/src/run/mod.rs +++ b/xtask/src/run/mod.rs @@ -279,6 +279,7 @@ pub fn cargo_clippy<'c>( package: Some(package.name()), target: target.into(), features, + deny_warnings: true, }; (globals, command, false) -- cgit v1.2.3 From 2db26c1015f0a32fe43e71d60f5fd75dbb25f40c Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 12:51:11 +0200 Subject: Deny on warnings in xtasks --- .github/workflows/build.yml | 24 ++------- rtic-common/src/lib.rs | 1 - rtic-macros/src/lib.rs | 1 - rtic-monotonics/src/lib.rs | 1 - rtic-sync/src/lib.rs | 1 - rtic-time/src/lib.rs | 1 - rtic/src/lib.rs | 1 - xtask/src/argument_parsing.rs | 16 +++++- xtask/src/cargo_command.rs | 110 +++++++++++++++++++++++++++++++----------- xtask/src/run/mod.rs | 19 +++++++- 10 files changed, 120 insertions(+), 55 deletions(-) (limited to 'xtask/src/run') diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fa2b80..ad0f5ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,9 +25,6 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Fail on warnings - run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} + - - name: Cache Dependencies uses: Swatinem/rust-cache@v2 @@ -62,13 +59,10 @@ jobs: rustup target add thumbv8m.base-none-eabi rustup target add thumbv8m.main-none-eabi - - name: Fail on warnings - run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} + - - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - - run: cargo xtask --backend ${{ matrix.backend }} check + - run: cargo xtask --deny-warnings --backend ${{ matrix.backend }} check # Clippy clippy: @@ -101,13 +95,10 @@ jobs: - name: Add Rust component clippy run: rustup component add clippy - - name: Fail on warnings - run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} + - - name: Cache Dependencies uses: Swatinem/rust-cache@v2 - - run: cargo xtask --backend ${{ matrix.backend }} clippy + - run: cargo xtask --deny-warnings --backend ${{ matrix.backend }} clippy # Verify all examples, checks checkexamples: @@ -219,12 +210,8 @@ jobs: sudo apt update sudo apt install -y qemu-system-arm - - name: Fail on warnings - working-directory: ./rtic - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - name: Run-pass tests - run: cargo xtask --backend ${{ matrix.backend }} qemu + run: cargo xtask --deny-warnings --backend ${{ matrix.backend }} qemu # Run test suite tests: @@ -260,11 +247,8 @@ jobs: rustup target add thumbv8m.base-none-eabi rustup target add thumbv8m.main-none-eabi - - name: Fail on warnings - run: find . -type f -name lib.rs -execdir sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' {} + - - name: Run cargo test - run: cargo xtask --backend ${{ matrix.backend }} test ${{ matrix.package }} + run: cargo xtask --deny-warnings --backend ${{ matrix.backend }} test ${{ matrix.package }} # Build documentation, check links docs: diff --git a/rtic-common/src/lib.rs b/rtic-common/src/lib.rs index 03d0306..e8f5af7 100644 --- a/rtic-common/src/lib.rs +++ b/rtic-common/src/lib.rs @@ -2,7 +2,6 @@ #![no_std] #![deny(missing_docs)] -//deny_warnings_placeholder_for_ci #[cfg(test)] #[macro_use] diff --git a/rtic-macros/src/lib.rs b/rtic-macros/src/lib.rs index cd2a924..e0043d9 100644 --- a/rtic-macros/src/lib.rs +++ b/rtic-macros/src/lib.rs @@ -2,7 +2,6 @@ html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" )] -//deny_warnings_placeholder_for_ci use proc_macro::TokenStream; use std::{env, fs, path::Path}; diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs index 04ce4e2..714200a 100644 --- a/rtic-monotonics/src/lib.rs +++ b/rtic-monotonics/src/lib.rs @@ -2,7 +2,6 @@ #![no_std] #![deny(missing_docs)] -//deny_warnings_placeholder_for_ci #![allow(incomplete_features)] #![feature(async_fn_in_trait)] diff --git a/rtic-sync/src/lib.rs b/rtic-sync/src/lib.rs index ac06220..fd8b6c3 100644 --- a/rtic-sync/src/lib.rs +++ b/rtic-sync/src/lib.rs @@ -2,7 +2,6 @@ #![no_std] #![deny(missing_docs)] -//deny_warnings_placeholder_for_ci pub mod arbiter; pub mod channel; diff --git a/rtic-time/src/lib.rs b/rtic-time/src/lib.rs index 1ed1e9d..7c0c764 100644 --- a/rtic-time/src/lib.rs +++ b/rtic-time/src/lib.rs @@ -2,7 +2,6 @@ #![no_std] #![deny(missing_docs)] -//deny_warnings_placeholder_for_ci #![allow(incomplete_features)] #![feature(async_fn_in_trait)] diff --git a/rtic/src/lib.rs b/rtic/src/lib.rs index ac05d93..53fe287 100644 --- a/rtic/src/lib.rs +++ b/rtic/src/lib.rs @@ -30,7 +30,6 @@ html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" )] -//deny_warnings_placeholder_for_ci #![allow(clippy::inline_always)] pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; diff --git a/xtask/src/argument_parsing.rs b/xtask/src/argument_parsing.rs index 8c8f7d2..d7c0262 100644 --- a/xtask/src/argument_parsing.rs +++ b/xtask/src/argument_parsing.rs @@ -94,7 +94,11 @@ impl Package { pub struct TestMetadata {} impl TestMetadata { - pub fn match_package(package: Package, backend: Backends) -> CargoCommand<'static> { + pub fn match_package( + deny_warnings: bool, + package: Package, + backend: Backends, + ) -> CargoCommand<'static> { match package { Package::Rtic => { let features = format!( @@ -107,32 +111,38 @@ impl TestMetadata { package: Some(package.name()), features, test: Some("ui".to_owned()), + deny_warnings, } } Package::RticMacros => CargoCommand::Test { package: Some(package.name()), features: Some(backend.to_rtic_macros_feature().to_owned()), test: None, + deny_warnings, }, Package::RticSync => CargoCommand::Test { package: Some(package.name()), features: Some("testing".to_owned()), test: None, + deny_warnings, }, Package::RticCommon => CargoCommand::Test { package: Some(package.name()), features: Some("testing".to_owned()), test: None, + deny_warnings, }, Package::RticMonotonics => CargoCommand::Test { package: Some(package.name()), features: None, test: None, + deny_warnings, }, Package::RticTime => CargoCommand::Test { package: Some(package.name()), features: Some("critical-section/std".into()), test: None, + deny_warnings, }, } } @@ -192,6 +202,10 @@ pub enum BuildOrCheck { #[derive(Parser, Clone)] pub struct Globals { + /// Error out on warnings + #[arg(short = 'D', long)] + pub deny_warnings: bool, + /// For which backend to build. #[arg(value_enum, short, default_value = "thumbv7", long, global = true)] pub backend: Option, diff --git a/xtask/src/cargo_command.rs b/xtask/src/cargo_command.rs index 9cf4f65..09487cb 100644 --- a/xtask/src/cargo_command.rs +++ b/xtask/src/cargo_command.rs @@ -28,6 +28,7 @@ pub enum CargoCommand<'a> { features: Option, mode: BuildMode, dir: Option, + deny_warnings: bool, }, ExampleBuild { cargoarg: &'a Option<&'a str>, @@ -36,6 +37,7 @@ pub enum CargoCommand<'a> { features: Option, mode: BuildMode, dir: Option, + deny_warnings: bool, }, ExampleCheck { cargoarg: &'a Option<&'a str>, @@ -43,6 +45,7 @@ pub enum CargoCommand<'a> { target: Option>, features: Option, mode: BuildMode, + deny_warnings: bool, }, Build { cargoarg: &'a Option<&'a str>, @@ -51,6 +54,7 @@ pub enum CargoCommand<'a> { features: Option, mode: BuildMode, dir: Option, + deny_warnings: bool, }, Check { cargoarg: &'a Option<&'a str>, @@ -59,6 +63,7 @@ pub enum CargoCommand<'a> { features: Option, mode: BuildMode, dir: Option, + deny_warnings: bool, }, Clippy { cargoarg: &'a Option<&'a str>, @@ -81,6 +86,7 @@ pub enum CargoCommand<'a> { package: Option, features: Option, test: Option, + deny_warnings: bool, }, Book { arguments: Option, @@ -123,6 +129,7 @@ impl core::fmt::Display for CargoCommand<'_> { } fn details( + deny_warnings: bool, target: &Option, mode: Option<&BuildMode>, features: &Option, @@ -150,14 +157,20 @@ impl core::fmt::Display for CargoCommand<'_> { format!("debug") }; + let deny_warnings = if deny_warnings { + format!("deny warnings, ") + } else { + format!("") + }; + if cargoarg.is_some() && path.is_some() { - format!("({target}, {mode}, {feat}, {carg}, {in_dir})") + format!("({deny_warnings}{target}, {mode}, {feat}, {carg}, {in_dir})") } else if cargoarg.is_some() { - format!("({target}, {mode}, {feat}, {carg})") + format!("({deny_warnings}{target}, {mode}, {feat}, {carg})") } else if path.is_some() { - format!("({target}, {mode}, {feat}, {in_dir})") + format!("({deny_warnings}{target}, {mode}, {feat}, {in_dir})") } else { - format!("({target}, {mode}, {feat})") + format!("({deny_warnings}{target}, {mode}, {feat})") } } @@ -173,7 +186,7 @@ impl core::fmt::Display for CargoCommand<'_> { write!( f, "Run example {example} {}", - details(target, Some(mode), features, cargoarg, dir.as_ref()) + details(false, target, Some(mode), features, cargoarg, dir.as_ref()) ) } CargoCommand::Qemu { @@ -183,8 +196,10 @@ impl core::fmt::Display for CargoCommand<'_> { features, mode, dir, + deny_warnings, } => { - let details = details(target, Some(mode), features, cargoarg, dir.as_ref()); + let warns = *deny_warnings; + let details = details(warns, target, Some(mode), features, cargoarg, dir.as_ref()); write!(f, "Run example {example} in QEMU {details}",) } CargoCommand::ExampleBuild { @@ -194,8 +209,10 @@ impl core::fmt::Display for CargoCommand<'_> { features, mode, dir, + deny_warnings, } => { - let details = details(target, Some(mode), features, cargoarg, dir.as_ref()); + let warns = *deny_warnings; + let details = details(warns, target, Some(mode), features, cargoarg, dir.as_ref()); write!(f, "Build example {example} {details}",) } CargoCommand::ExampleCheck { @@ -204,10 +221,11 @@ impl core::fmt::Display for CargoCommand<'_> { target, features, mode, + deny_warnings, } => write!( f, "Check example {example} {}", - details(target, Some(mode), features, cargoarg, None) + details(*deny_warnings, target, Some(mode), features, cargoarg, None) ), CargoCommand::Build { cargoarg, @@ -216,12 +234,14 @@ impl core::fmt::Display for CargoCommand<'_> { features, mode, dir, + deny_warnings, } => { let package = p(package); + let warns = *deny_warnings; write!( f, "Build {package} {}", - details(target, Some(mode), features, cargoarg, dir.as_ref()) + details(warns, target, Some(mode), features, cargoarg, dir.as_ref()) ) } @@ -232,12 +252,14 @@ impl core::fmt::Display for CargoCommand<'_> { features, mode, dir, + deny_warnings, } => { let package = p(package); + let warns = *deny_warnings; write!( f, "Check {package} {}", - details(target, Some(mode), features, cargoarg, dir.as_ref()) + details(warns, target, Some(mode), features, cargoarg, dir.as_ref()) ) } CargoCommand::Clippy { @@ -247,14 +269,9 @@ impl core::fmt::Display for CargoCommand<'_> { features, deny_warnings, } => { - let details = details(target, None, features, cargoarg, None); + let details = details(*deny_warnings, target, None, features, cargoarg, None); let package = p(package); - let deny_warns = if *deny_warnings { - format!(" (deny warnings)") - } else { - format!("") - }; - write!(f, "Clippy{deny_warns} {package} {details}") + write!(f, "Clippy {package} {details}") } CargoCommand::Format { cargoarg, @@ -297,14 +314,20 @@ impl core::fmt::Display for CargoCommand<'_> { package, features, test, + deny_warnings, } => { let p = p(package); let test = test .clone() .map(|t| format!("test {t}")) .unwrap_or("all tests".into()); + let deny_warnings = if *deny_warnings { + format!("deny warnings, ") + } else { + format!("") + }; let feat = feat(features); - write!(f, "Run {test} in {p} (features: {feat})") + write!(f, "Run {test} in {p} ({deny_warnings}features: {feat})") } CargoCommand::Book { arguments: _ } => write!(f, "Build the book"), CargoCommand::ExampleSize { @@ -316,7 +339,7 @@ impl core::fmt::Display for CargoCommand<'_> { arguments: _, dir, } => { - let details = details(target, Some(mode), features, cargoarg, dir.as_ref()); + let details = details(false, target, Some(mode), features, cargoarg, dir.as_ref()); write!(f, "Compute size of example {example} {details}") } } @@ -459,6 +482,8 @@ impl<'a> CargoCommand<'a> { dir: _, // Target is added by build_args target: _, + // deny_warnings is exposed through `rustflags` + deny_warnings: _, } => self.build_args( true, cargoarg, @@ -471,10 +496,12 @@ impl<'a> CargoCommand<'a> { package, features, mode, - // Dir is exposed through `chdir` - dir: _, // Target is added by build_args target: _, + // Dir is exposed through `chdir` + dir: _, + // deny_warnings is exposed through `rustflags` + deny_warnings: _, } => self.build_args(true, cargoarg, features, Some(mode), p(package)), CargoCommand::Check { cargoarg, @@ -485,23 +512,25 @@ impl<'a> CargoCommand<'a> { dir: _, // Target is added by build_args target: _, + // deny_warnings is exposed through `rustflags` + deny_warnings: _, } => self.build_args(true, cargoarg, features, Some(mode), p(package)), CargoCommand::Clippy { cargoarg, package, features, - deny_warnings, // Target is added by build_args target: _, + deny_warnings, } => { - let package = p(package); - let extra = if *deny_warnings { - vec!["--", "-D", "warnings"].into_iter() + let deny_warnings = if *deny_warnings { + vec!["--", "-D", "warnings"] } else { - vec![].into_iter() + vec![] }; - self.build_args(true, cargoarg, features, None, package.chain(extra)) + let extra = p(package).chain(deny_warnings); + self.build_args(true, cargoarg, features, None, extra) } CargoCommand::Doc { cargoarg, @@ -515,6 +544,8 @@ impl<'a> CargoCommand<'a> { package, features, test, + // deny_warnings is exposed through `rustflags` + deny_warnings: _, } => { let extra = if let Some(test) = test { vec!["--test", test] @@ -564,6 +595,8 @@ impl<'a> CargoCommand<'a> { dir: _, // Target is added by build_args target: _, + // deny_warnings is exposed through `rustflags` + deny_warnings: _, } => self.build_args( true, cargoarg, @@ -578,6 +611,8 @@ impl<'a> CargoCommand<'a> { mode, // Target is added by build_args target: _, + // deny_warnings is exposed through `rustflags` + deny_warnings: _, } => self.build_args( true, cargoarg, @@ -632,6 +667,27 @@ impl<'a> CargoCommand<'a> { } } + pub fn rustflags(&self) -> Option<&str> { + match self { + // Clippy is a special case: it sets deny warnings + // through an argument to rustc. + CargoCommand::Clippy { .. } => None, + CargoCommand::Check { deny_warnings, .. } + | CargoCommand::ExampleCheck { deny_warnings, .. } + | CargoCommand::Build { deny_warnings, .. } + | CargoCommand::ExampleBuild { deny_warnings, .. } + | CargoCommand::Test { deny_warnings, .. } + | CargoCommand::Qemu { deny_warnings, .. } => { + if *deny_warnings { + Some("-D warnings") + } else { + None + } + } + _ => None, + } + } + pub fn print_stdout_intermediate(&self) -> bool { match self { Self::ExampleSize { .. } => true, diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs index ac35f5c..4032ea8 100644 --- a/xtask/src/run/mod.rs +++ b/xtask/src/run/mod.rs @@ -172,6 +172,7 @@ pub fn cargo<'c>( features, mode: BuildMode::Release, dir: None, + deny_warnings: globals.deny_warnings, }, BuildOrCheck::Build => CargoCommand::Build { cargoarg, @@ -180,6 +181,7 @@ pub fn cargo<'c>( features, mode: BuildMode::Release, dir: None, + deny_warnings: globals.deny_warnings, }, }; @@ -209,6 +211,7 @@ pub fn cargo_usage_example( package: None, target: None, features: None, + deny_warnings: globals.deny_warnings, }, BuildOrCheck::Build => CargoCommand::Build { cargoarg: &None, @@ -217,6 +220,7 @@ pub fn cargo_usage_example( features: None, mode: BuildMode::Release, dir: Some(path.into()), + deny_warnings: globals.deny_warnings, }, }; (globals, command, false) @@ -244,6 +248,7 @@ pub fn cargo_example<'c>( target: Some(backend.to_target()), features, mode: BuildMode::Release, + deny_warnings: globals.deny_warnings, }, BuildOrCheck::Build => CargoCommand::ExampleBuild { cargoarg, @@ -252,6 +257,7 @@ pub fn cargo_example<'c>( features, mode: BuildMode::Release, dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, }, }; (globals, command, false) @@ -337,7 +343,10 @@ pub fn cargo_test<'c>( ) -> Vec> { package .packages() - .map(|p| (globals, TestMetadata::match_package(p, backend), false)) + .map(|p| { + let meta = TestMetadata::match_package(globals.deny_warnings, p, backend); + (globals, meta, false) + }) .run_and_coalesce() } @@ -378,6 +387,7 @@ pub fn qemu_run_examples<'c>( features: features.clone(), mode: BuildMode::Release, dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, }; let cmd_qemu = CargoCommand::Qemu { @@ -387,6 +397,7 @@ pub fn qemu_run_examples<'c>( features: features.clone(), mode: BuildMode::Release, dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, }; into_iter([cmd_build, cmd_qemu]) @@ -417,7 +428,9 @@ pub fn build_and_check_size<'c>( features: features.clone(), mode: BuildMode::Release, dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, }; + if let Err(err) = command_parser(globals, &cmd, false) { error!("{err}"); } @@ -455,6 +468,10 @@ fn run_command( process.current_dir(dir.canonicalize()?); } + if let Some(rustflags) = command.rustflags() { + process.env("RUSTFLAGS", rustflags); + } + let result = process.output()?; let exit_status = result.status; -- cgit v1.2.3 From 85e2cd6d4b1ac801dbbce2295a6617a56f9ea5a0 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 13:16:28 +0200 Subject: Also always deny warnings for doc --- xtask/src/cargo_command.rs | 30 ++++++++++++++++++++---------- xtask/src/run/mod.rs | 5 +++-- 2 files changed, 23 insertions(+), 12 deletions(-) (limited to 'xtask/src/run') diff --git a/xtask/src/cargo_command.rs b/xtask/src/cargo_command.rs index 09487cb..ef14a38 100644 --- a/xtask/src/cargo_command.rs +++ b/xtask/src/cargo_command.rs @@ -81,6 +81,7 @@ pub enum CargoCommand<'a> { cargoarg: &'a Option<&'a str>, features: Option, arguments: Option, + deny_warnings: bool, }, Test { package: Option, @@ -297,6 +298,7 @@ impl core::fmt::Display for CargoCommand<'_> { cargoarg, features, arguments, + deny_warnings, } => { let feat = feat(features); let carg = carg(cargoarg); @@ -304,10 +306,15 @@ impl core::fmt::Display for CargoCommand<'_> { .clone() .map(|a| format!("{a}")) .unwrap_or_else(|| "no extra arguments".into()); + let deny_warnings = if *deny_warnings { + format!("deny warnings, ") + } else { + format!("") + }; if cargoarg.is_some() { - write!(f, "Document ({feat}, {carg}, {arguments})") + write!(f, "Document ({deny_warnings}{feat}, {carg}, {arguments})") } else { - write!(f, "Document ({feat}, {arguments})") + write!(f, "Document ({deny_warnings}{feat}, {arguments})") } } CargoCommand::Test { @@ -482,7 +489,7 @@ impl<'a> CargoCommand<'a> { dir: _, // Target is added by build_args target: _, - // deny_warnings is exposed through `rustflags` + // deny_warnings is exposed through `extra_env` deny_warnings: _, } => self.build_args( true, @@ -500,7 +507,7 @@ impl<'a> CargoCommand<'a> { target: _, // Dir is exposed through `chdir` dir: _, - // deny_warnings is exposed through `rustflags` + // deny_warnings is exposed through `extra_env` deny_warnings: _, } => self.build_args(true, cargoarg, features, Some(mode), p(package)), CargoCommand::Check { @@ -512,7 +519,7 @@ impl<'a> CargoCommand<'a> { dir: _, // Target is added by build_args target: _, - // deny_warnings is exposed through `rustflags` + // deny_warnings is exposed through `extra_env` deny_warnings: _, } => self.build_args(true, cargoarg, features, Some(mode), p(package)), CargoCommand::Clippy { @@ -536,6 +543,8 @@ impl<'a> CargoCommand<'a> { cargoarg, features, arguments, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, } => { let extra = Self::extra_args(arguments.as_ref()); self.build_args(true, cargoarg, features, None, extra) @@ -544,7 +553,7 @@ impl<'a> CargoCommand<'a> { package, features, test, - // deny_warnings is exposed through `rustflags` + // deny_warnings is exposed through `extra_env` deny_warnings: _, } => { let extra = if let Some(test) = test { @@ -595,7 +604,7 @@ impl<'a> CargoCommand<'a> { dir: _, // Target is added by build_args target: _, - // deny_warnings is exposed through `rustflags` + // deny_warnings is exposed through `extra_env` deny_warnings: _, } => self.build_args( true, @@ -611,7 +620,7 @@ impl<'a> CargoCommand<'a> { mode, // Target is added by build_args target: _, - // deny_warnings is exposed through `rustflags` + // deny_warnings is exposed through `extra_env` deny_warnings: _, } => self.build_args( true, @@ -667,11 +676,12 @@ impl<'a> CargoCommand<'a> { } } - pub fn rustflags(&self) -> Option<&str> { + pub fn extra_env(&self) -> Option<(&str, &str)> { match self { // Clippy is a special case: it sets deny warnings // through an argument to rustc. CargoCommand::Clippy { .. } => None, + CargoCommand::Doc { .. } => Some(("RUSTDOCFLAGS", "-D warnings")), CargoCommand::Check { deny_warnings, .. } | CargoCommand::ExampleCheck { deny_warnings, .. } | CargoCommand::Build { deny_warnings, .. } @@ -679,7 +689,7 @@ impl<'a> CargoCommand<'a> { | CargoCommand::Test { deny_warnings, .. } | CargoCommand::Qemu { deny_warnings, .. } => { if *deny_warnings { - Some("-D warnings") + Some(("RUSTFLAGS", "-D warnings")) } else { None } diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs index 4032ea8..74179c5 100644 --- a/xtask/src/run/mod.rs +++ b/xtask/src/run/mod.rs @@ -328,6 +328,7 @@ pub fn cargo_doc<'c>( cargoarg, features, arguments: arguments.clone(), + deny_warnings: true, }; vec![run_and_convert((globals, command, false))] @@ -468,8 +469,8 @@ fn run_command( process.current_dir(dir.canonicalize()?); } - if let Some(rustflags) = command.rustflags() { - process.env("RUSTFLAGS", rustflags); + if let Some((k, v)) = command.extra_env() { + process.env(k, v); } let result = process.output()?; -- cgit v1.2.3 From bc92e43b113dae2a34db6b10812d84227ac5a6d6 Mon Sep 17 00:00:00 2001 From: datdenkikniet Date: Sun, 16 Apr 2023 13:22:10 +0200 Subject: Rename + better printout --- xtask/src/cargo_command.rs | 2 +- xtask/src/run.rs | 499 +++++++++++++++++++++++++++++++++++++++++++++ xtask/src/run/mod.rs | 499 --------------------------------------------- 3 files changed, 500 insertions(+), 500 deletions(-) create mode 100644 xtask/src/run.rs delete mode 100644 xtask/src/run/mod.rs (limited to 'xtask/src/run') diff --git a/xtask/src/cargo_command.rs b/xtask/src/cargo_command.rs index ef14a38..b0102ce 100644 --- a/xtask/src/cargo_command.rs +++ b/xtask/src/cargo_command.rs @@ -149,7 +149,7 @@ impl core::fmt::Display for CargoCommand<'_> { let target = if let Some(target) = target { format!("{target}") } else { - format!("") + format!("") }; let mode = if let Some(mode) = mode { diff --git a/xtask/src/run.rs b/xtask/src/run.rs new file mode 100644 index 0000000..74179c5 --- /dev/null +++ b/xtask/src/run.rs @@ -0,0 +1,499 @@ +use std::{ + fs::File, + io::Write, + path::PathBuf, + process::{Command, Stdio}, +}; + +mod results; +pub use results::handle_results; + +mod data; +use data::*; + +mod iter; +use iter::{into_iter, CoalescingRunner}; + +use crate::{ + argument_parsing::{Backends, BuildOrCheck, ExtraArguments, Globals, PackageOpt, TestMetadata}, + cargo_command::{BuildMode, CargoCommand}, +}; + +use log::{error, info}; + +#[cfg(feature = "rayon")] +use rayon::prelude::*; + +fn run_and_convert<'a>( + (global, command, overwrite): (&Globals, CargoCommand<'a>, bool), +) -> FinalRunResult<'a> { + // Run the command + let result = command_parser(global, &command, overwrite); + + let output = match result { + // If running the command succeeded without looking at any of the results, + // log the data and see if the actual execution was succesfull too. + Ok(result) => { + if result.exit_status.success() { + FinalRunResult::Success(command, result) + } else { + FinalRunResult::Failed(command, result) + } + } + // If it didn't and some IO error occured, just panic + Err(e) => FinalRunResult::CommandError(command, e), + }; + + log::trace!("Final result: {output:?}"); + + output +} + +// run example binary `example` +fn command_parser( + glob: &Globals, + command: &CargoCommand, + overwrite: bool, +) -> anyhow::Result { + let output_mode = if glob.stderr_inherited { + OutputMode::Inherited + } else { + OutputMode::PipedAndCollected + }; + + match *command { + CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { + /// 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: &str, + ) -> Result<(), TestRunError> { + let file = expected_output_file.to_string(); + + let expected_output = std::fs::read(expected_output_file) + .map(|d| { + String::from_utf8(d) + .map_err(|_| TestRunError::FileError { file: file.clone() }) + }) + .map_err(|_| TestRunError::FileError { file })??; + + let res = if expected_output != run.stdout { + Err(TestRunError::FileCmpError { + expected: expected_output.clone(), + got: run.stdout.clone(), + }) + } else if !run.exit_status.success() { + Err(TestRunError::CommandError(run.clone())) + } else { + Ok(()) + }; + + if res.is_ok() { + log::info!("✅ Success."); + } else { + log::error!("❌ Command failed. Run to completion for the summary."); + } + + res + } + + let run_file = format!("{example}.run"); + let expected_output_file = ["rtic", "ci", "expected", &run_file] + .iter() + .collect::() + .into_os_string() + .into_string() + .map_err(TestRunError::PathConversionError)?; + + // cargo run <..> + let cargo_run_result = run_command(command, output_mode, false)?; + + // 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(cargo_run_result) + } + 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, output_mode, true)?; + Ok(cargo_result) + } + } +} + +/// Cargo command to either build or check +pub fn cargo<'c>( + globals: &Globals, + operation: BuildOrCheck, + cargoarg: &'c Option<&'c str>, + package: &'c PackageOpt, + backend: Backends, +) -> Vec> { + let runner = package + .packages() + .flat_map(|package| { + let target = backend.to_target(); + let features = package.features(target, backend, globals.partial); + into_iter(features).map(move |f| (package, target, f)) + }) + .map(move |(package, target, features)| { + let target = target.into(); + let command = match operation { + BuildOrCheck::Check => CargoCommand::Check { + cargoarg, + package: Some(package.name()), + target, + features, + mode: BuildMode::Release, + dir: None, + deny_warnings: globals.deny_warnings, + }, + BuildOrCheck::Build => CargoCommand::Build { + cargoarg, + package: Some(package.name()), + target, + features, + mode: BuildMode::Release, + dir: None, + deny_warnings: globals.deny_warnings, + }, + }; + + (globals, command, false) + }); + + runner.run_and_coalesce() +} + +/// Cargo command to build a usage example. +/// +/// The usage examples are in examples/ +pub fn cargo_usage_example( + globals: &Globals, + operation: BuildOrCheck, + usage_examples: Vec, +) -> Vec> { + into_iter(&usage_examples) + .map(|example| { + let path = format!("examples/{example}"); + + let command = match operation { + BuildOrCheck::Check => CargoCommand::Check { + cargoarg: &None, + mode: BuildMode::Release, + dir: Some(path.into()), + package: None, + target: None, + features: None, + deny_warnings: globals.deny_warnings, + }, + BuildOrCheck::Build => CargoCommand::Build { + cargoarg: &None, + package: None, + target: None, + features: None, + mode: BuildMode::Release, + dir: Some(path.into()), + deny_warnings: globals.deny_warnings, + }, + }; + (globals, command, false) + }) + .run_and_coalesce() +} + +/// Cargo command to either build or check all examples +/// +/// The examples are in rtic/examples +pub fn cargo_example<'c>( + globals: &Globals, + operation: BuildOrCheck, + cargoarg: &'c Option<&'c str>, + backend: Backends, + examples: &'c [String], +) -> Vec> { + let runner = into_iter(examples).map(|example| { + let features = Some(backend.to_target().and_features(backend.to_rtic_feature())); + + let command = match operation { + BuildOrCheck::Check => CargoCommand::ExampleCheck { + cargoarg, + example, + target: Some(backend.to_target()), + features, + mode: BuildMode::Release, + deny_warnings: globals.deny_warnings, + }, + BuildOrCheck::Build => CargoCommand::ExampleBuild { + cargoarg, + example, + target: Some(backend.to_target()), + features, + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, + }, + }; + (globals, command, false) + }); + runner.run_and_coalesce() +} + +/// Run cargo clippy on selected package +pub fn cargo_clippy<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + package: &'c PackageOpt, + backend: Backends, +) -> Vec> { + let runner = package + .packages() + .flat_map(|package| { + let target = backend.to_target(); + let features = package.features(target, backend, globals.partial); + into_iter(features).map(move |f| (package, target, f)) + }) + .map(move |(package, target, features)| { + let command = CargoCommand::Clippy { + cargoarg, + package: Some(package.name()), + target: target.into(), + features, + deny_warnings: true, + }; + + (globals, command, false) + }); + + runner.run_and_coalesce() +} + +/// Run cargo fmt on selected package +pub fn cargo_format<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + package: &'c PackageOpt, + check_only: bool, +) -> Vec> { + let runner = package.packages().map(|p| { + ( + globals, + CargoCommand::Format { + cargoarg, + package: Some(p.name()), + check_only, + }, + false, + ) + }); + runner.run_and_coalesce() +} + +/// Run cargo doc +pub fn cargo_doc<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + backend: Backends, + arguments: &'c Option, +) -> Vec> { + let features = Some(backend.to_target().and_features(backend.to_rtic_feature())); + + let command = CargoCommand::Doc { + cargoarg, + features, + arguments: arguments.clone(), + deny_warnings: true, + }; + + vec![run_and_convert((globals, command, false))] +} + +/// Run cargo test on the selected package or all packages +/// +/// If no package is specified, loop through all packages +pub fn cargo_test<'c>( + globals: &Globals, + package: &'c PackageOpt, + backend: Backends, +) -> Vec> { + package + .packages() + .map(|p| { + let meta = TestMetadata::match_package(globals.deny_warnings, p, backend); + (globals, meta, false) + }) + .run_and_coalesce() +} + +/// Use mdbook to build the book +pub fn cargo_book<'c>( + globals: &Globals, + arguments: &'c Option, +) -> Vec> { + vec![run_and_convert(( + globals, + CargoCommand::Book { + arguments: arguments.clone(), + }, + false, + ))] +} + +/// Run examples +/// +/// Supports updating the expected output via the overwrite argument +pub fn qemu_run_examples<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + backend: Backends, + examples: &'c [String], + overwrite: bool, +) -> Vec> { + let target = backend.to_target(); + let features = Some(target.and_features(backend.to_rtic_feature())); + + into_iter(examples) + .flat_map(|example| { + let target = target.into(); + let cmd_build = CargoCommand::ExampleBuild { + cargoarg: &None, + example, + target, + features: features.clone(), + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, + }; + + let cmd_qemu = CargoCommand::Qemu { + cargoarg, + example, + target, + features: features.clone(), + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, + }; + + into_iter([cmd_build, cmd_qemu]) + }) + .map(|cmd| (globals, cmd, overwrite)) + .run_and_coalesce() +} + +/// Check the binary sizes of examples +pub fn build_and_check_size<'c>( + globals: &Globals, + cargoarg: &'c Option<&'c str>, + backend: Backends, + examples: &'c [String], + arguments: &'c Option, +) -> Vec> { + let target = backend.to_target(); + let features = Some(target.and_features(backend.to_rtic_feature())); + + let runner = into_iter(examples).map(|example| { + let target = target.into(); + + // Make sure the requested example(s) are built + let cmd = CargoCommand::ExampleBuild { + cargoarg: &Some("--quiet"), + example, + target, + features: features.clone(), + mode: BuildMode::Release, + dir: Some(PathBuf::from("./rtic")), + deny_warnings: globals.deny_warnings, + }; + + if let Err(err) = command_parser(globals, &cmd, false) { + error!("{err}"); + } + + let cmd = CargoCommand::ExampleSize { + cargoarg, + example, + target, + features: features.clone(), + mode: BuildMode::Release, + arguments: arguments.clone(), + dir: Some(PathBuf::from("./rtic")), + }; + (globals, cmd, false) + }); + + runner.run_and_coalesce() +} + +fn run_command( + command: &CargoCommand, + stderr_mode: OutputMode, + print_command_success: bool, +) -> anyhow::Result { + log::info!("👟 {command}"); + + let mut process = Command::new(command.executable()); + + process + .args(command.args()) + .stdout(Stdio::piped()) + .stderr(stderr_mode); + + if let Some(dir) = command.chdir() { + process.current_dir(dir.canonicalize()?); + } + + if let Some((k, v)) = command.extra_env() { + process.env(k, v); + } + + let result = process.output()?; + + let exit_status = result.status; + let stderr = String::from_utf8(result.stderr).unwrap_or("Not displayable".into()); + let stdout = String::from_utf8(result.stdout).unwrap_or("Not displayable".into()); + + if command.print_stdout_intermediate() && exit_status.success() { + log::info!("\n{}", stdout); + } + + if print_command_success { + if exit_status.success() { + log::info!("✅ Success.") + } else { + log::error!("❌ Command failed. Run to completion for the summary."); + } + } + + Ok(RunResult { + exit_status, + stdout, + stderr, + }) +} diff --git a/xtask/src/run/mod.rs b/xtask/src/run/mod.rs deleted file mode 100644 index 74179c5..0000000 --- a/xtask/src/run/mod.rs +++ /dev/null @@ -1,499 +0,0 @@ -use std::{ - fs::File, - io::Write, - path::PathBuf, - process::{Command, Stdio}, -}; - -mod results; -pub use results::handle_results; - -mod data; -use data::*; - -mod iter; -use iter::{into_iter, CoalescingRunner}; - -use crate::{ - argument_parsing::{Backends, BuildOrCheck, ExtraArguments, Globals, PackageOpt, TestMetadata}, - cargo_command::{BuildMode, CargoCommand}, -}; - -use log::{error, info}; - -#[cfg(feature = "rayon")] -use rayon::prelude::*; - -fn run_and_convert<'a>( - (global, command, overwrite): (&Globals, CargoCommand<'a>, bool), -) -> FinalRunResult<'a> { - // Run the command - let result = command_parser(global, &command, overwrite); - - let output = match result { - // If running the command succeeded without looking at any of the results, - // log the data and see if the actual execution was succesfull too. - Ok(result) => { - if result.exit_status.success() { - FinalRunResult::Success(command, result) - } else { - FinalRunResult::Failed(command, result) - } - } - // If it didn't and some IO error occured, just panic - Err(e) => FinalRunResult::CommandError(command, e), - }; - - log::trace!("Final result: {output:?}"); - - output -} - -// run example binary `example` -fn command_parser( - glob: &Globals, - command: &CargoCommand, - overwrite: bool, -) -> anyhow::Result { - let output_mode = if glob.stderr_inherited { - OutputMode::Inherited - } else { - OutputMode::PipedAndCollected - }; - - match *command { - CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { - /// 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: &str, - ) -> Result<(), TestRunError> { - let file = expected_output_file.to_string(); - - let expected_output = std::fs::read(expected_output_file) - .map(|d| { - String::from_utf8(d) - .map_err(|_| TestRunError::FileError { file: file.clone() }) - }) - .map_err(|_| TestRunError::FileError { file })??; - - let res = if expected_output != run.stdout { - Err(TestRunError::FileCmpError { - expected: expected_output.clone(), - got: run.stdout.clone(), - }) - } else if !run.exit_status.success() { - Err(TestRunError::CommandError(run.clone())) - } else { - Ok(()) - }; - - if res.is_ok() { - log::info!("✅ Success."); - } else { - log::error!("❌ Command failed. Run to completion for the summary."); - } - - res - } - - let run_file = format!("{example}.run"); - let expected_output_file = ["rtic", "ci", "expected", &run_file] - .iter() - .collect::() - .into_os_string() - .into_string() - .map_err(TestRunError::PathConversionError)?; - - // cargo run <..> - let cargo_run_result = run_command(command, output_mode, false)?; - - // 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(cargo_run_result) - } - 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, output_mode, true)?; - Ok(cargo_result) - } - } -} - -/// Cargo command to either build or check -pub fn cargo<'c>( - globals: &Globals, - operation: BuildOrCheck, - cargoarg: &'c Option<&'c str>, - package: &'c PackageOpt, - backend: Backends, -) -> Vec> { - let runner = package - .packages() - .flat_map(|package| { - let target = backend.to_target(); - let features = package.features(target, backend, globals.partial); - into_iter(features).map(move |f| (package, target, f)) - }) - .map(move |(package, target, features)| { - let target = target.into(); - let command = match operation { - BuildOrCheck::Check => CargoCommand::Check { - cargoarg, - package: Some(package.name()), - target, - features, - mode: BuildMode::Release, - dir: None, - deny_warnings: globals.deny_warnings, - }, - BuildOrCheck::Build => CargoCommand::Build { - cargoarg, - package: Some(package.name()), - target, - features, - mode: BuildMode::Release, - dir: None, - deny_warnings: globals.deny_warnings, - }, - }; - - (globals, command, false) - }); - - runner.run_and_coalesce() -} - -/// Cargo command to build a usage example. -/// -/// The usage examples are in examples/ -pub fn cargo_usage_example( - globals: &Globals, - operation: BuildOrCheck, - usage_examples: Vec, -) -> Vec> { - into_iter(&usage_examples) - .map(|example| { - let path = format!("examples/{example}"); - - let command = match operation { - BuildOrCheck::Check => CargoCommand::Check { - cargoarg: &None, - mode: BuildMode::Release, - dir: Some(path.into()), - package: None, - target: None, - features: None, - deny_warnings: globals.deny_warnings, - }, - BuildOrCheck::Build => CargoCommand::Build { - cargoarg: &None, - package: None, - target: None, - features: None, - mode: BuildMode::Release, - dir: Some(path.into()), - deny_warnings: globals.deny_warnings, - }, - }; - (globals, command, false) - }) - .run_and_coalesce() -} - -/// Cargo command to either build or check all examples -/// -/// The examples are in rtic/examples -pub fn cargo_example<'c>( - globals: &Globals, - operation: BuildOrCheck, - cargoarg: &'c Option<&'c str>, - backend: Backends, - examples: &'c [String], -) -> Vec> { - let runner = into_iter(examples).map(|example| { - let features = Some(backend.to_target().and_features(backend.to_rtic_feature())); - - let command = match operation { - BuildOrCheck::Check => CargoCommand::ExampleCheck { - cargoarg, - example, - target: Some(backend.to_target()), - features, - mode: BuildMode::Release, - deny_warnings: globals.deny_warnings, - }, - BuildOrCheck::Build => CargoCommand::ExampleBuild { - cargoarg, - example, - target: Some(backend.to_target()), - features, - mode: BuildMode::Release, - dir: Some(PathBuf::from("./rtic")), - deny_warnings: globals.deny_warnings, - }, - }; - (globals, command, false) - }); - runner.run_and_coalesce() -} - -/// Run cargo clippy on selected package -pub fn cargo_clippy<'c>( - globals: &Globals, - cargoarg: &'c Option<&'c str>, - package: &'c PackageOpt, - backend: Backends, -) -> Vec> { - let runner = package - .packages() - .flat_map(|package| { - let target = backend.to_target(); - let features = package.features(target, backend, globals.partial); - into_iter(features).map(move |f| (package, target, f)) - }) - .map(move |(package, target, features)| { - let command = CargoCommand::Clippy { - cargoarg, - package: Some(package.name()), - target: target.into(), - features, - deny_warnings: true, - }; - - (globals, command, false) - }); - - runner.run_and_coalesce() -} - -/// Run cargo fmt on selected package -pub fn cargo_format<'c>( - globals: &Globals, - cargoarg: &'c Option<&'c str>, - package: &'c PackageOpt, - check_only: bool, -) -> Vec> { - let runner = package.packages().map(|p| { - ( - globals, - CargoCommand::Format { - cargoarg, - package: Some(p.name()), - check_only, - }, - false, - ) - }); - runner.run_and_coalesce() -} - -/// Run cargo doc -pub fn cargo_doc<'c>( - globals: &Globals, - cargoarg: &'c Option<&'c str>, - backend: Backends, - arguments: &'c Option, -) -> Vec> { - let features = Some(backend.to_target().and_features(backend.to_rtic_feature())); - - let command = CargoCommand::Doc { - cargoarg, - features, - arguments: arguments.clone(), - deny_warnings: true, - }; - - vec![run_and_convert((globals, command, false))] -} - -/// Run cargo test on the selected package or all packages -/// -/// If no package is specified, loop through all packages -pub fn cargo_test<'c>( - globals: &Globals, - package: &'c PackageOpt, - backend: Backends, -) -> Vec> { - package - .packages() - .map(|p| { - let meta = TestMetadata::match_package(globals.deny_warnings, p, backend); - (globals, meta, false) - }) - .run_and_coalesce() -} - -/// Use mdbook to build the book -pub fn cargo_book<'c>( - globals: &Globals, - arguments: &'c Option, -) -> Vec> { - vec![run_and_convert(( - globals, - CargoCommand::Book { - arguments: arguments.clone(), - }, - false, - ))] -} - -/// Run examples -/// -/// Supports updating the expected output via the overwrite argument -pub fn qemu_run_examples<'c>( - globals: &Globals, - cargoarg: &'c Option<&'c str>, - backend: Backends, - examples: &'c [String], - overwrite: bool, -) -> Vec> { - let target = backend.to_target(); - let features = Some(target.and_features(backend.to_rtic_feature())); - - into_iter(examples) - .flat_map(|example| { - let target = target.into(); - let cmd_build = CargoCommand::ExampleBuild { - cargoarg: &None, - example, - target, - features: features.clone(), - mode: BuildMode::Release, - dir: Some(PathBuf::from("./rtic")), - deny_warnings: globals.deny_warnings, - }; - - let cmd_qemu = CargoCommand::Qemu { - cargoarg, - example, - target, - features: features.clone(), - mode: BuildMode::Release, - dir: Some(PathBuf::from("./rtic")), - deny_warnings: globals.deny_warnings, - }; - - into_iter([cmd_build, cmd_qemu]) - }) - .map(|cmd| (globals, cmd, overwrite)) - .run_and_coalesce() -} - -/// Check the binary sizes of examples -pub fn build_and_check_size<'c>( - globals: &Globals, - cargoarg: &'c Option<&'c str>, - backend: Backends, - examples: &'c [String], - arguments: &'c Option, -) -> Vec> { - let target = backend.to_target(); - let features = Some(target.and_features(backend.to_rtic_feature())); - - let runner = into_iter(examples).map(|example| { - let target = target.into(); - - // Make sure the requested example(s) are built - let cmd = CargoCommand::ExampleBuild { - cargoarg: &Some("--quiet"), - example, - target, - features: features.clone(), - mode: BuildMode::Release, - dir: Some(PathBuf::from("./rtic")), - deny_warnings: globals.deny_warnings, - }; - - if let Err(err) = command_parser(globals, &cmd, false) { - error!("{err}"); - } - - let cmd = CargoCommand::ExampleSize { - cargoarg, - example, - target, - features: features.clone(), - mode: BuildMode::Release, - arguments: arguments.clone(), - dir: Some(PathBuf::from("./rtic")), - }; - (globals, cmd, false) - }); - - runner.run_and_coalesce() -} - -fn run_command( - command: &CargoCommand, - stderr_mode: OutputMode, - print_command_success: bool, -) -> anyhow::Result { - log::info!("👟 {command}"); - - let mut process = Command::new(command.executable()); - - process - .args(command.args()) - .stdout(Stdio::piped()) - .stderr(stderr_mode); - - if let Some(dir) = command.chdir() { - process.current_dir(dir.canonicalize()?); - } - - if let Some((k, v)) = command.extra_env() { - process.env(k, v); - } - - let result = process.output()?; - - let exit_status = result.status; - let stderr = String::from_utf8(result.stderr).unwrap_or("Not displayable".into()); - let stdout = String::from_utf8(result.stdout).unwrap_or("Not displayable".into()); - - if command.print_stdout_intermediate() && exit_status.success() { - log::info!("\n{}", stdout); - } - - if print_command_success { - if exit_status.success() { - log::info!("✅ Success.") - } else { - log::error!("❌ Command failed. Run to completion for the summary."); - } - } - - Ok(RunResult { - exit_status, - stdout, - stderr, - }) -} -- cgit v1.2.3