734: QoL + fixes for `xtasks` r=korken89 a=datdenkikniet

Preferably merge this before #732

Draft till I get around to fixing "no package" -> "all packages" on `xtask build` (and probably others)

Co-authored-by: datdenkikniet <jcdra1@gmail.com>
This commit is contained in:
bors[bot] 2023-04-15 16:08:33 +00:00 committed by GitHub
commit ef8046b060
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 858 additions and 348 deletions

View file

@ -1,5 +1,6 @@
[alias] [alias]
xtask = "run --package xtask --" xtask = "run --package xtask --"
pxtask = "run --package xtask --features rayon --"
[target.thumbv6m-none-eabi] [target.thumbv6m-none-eabi]
runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel"

View file

@ -31,8 +31,8 @@ jobs:
- name: Cache Dependencies - name: Cache Dependencies
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: cargo xtask format-check - name: cargo xtask fmt
run: cargo xtask --verbose format-check run: cargo xtask --verbose fmt -c
# Compilation check # Compilation check
check: check:
@ -251,7 +251,7 @@ jobs:
tool: lychee tool: lychee
- name: Remove cargo-config - name: Remove cargo-config
run: rm -f .cargo/config run: rm -f .cargo/config.toml
- name: Build docs - name: Build docs
# TODO: Any difference between backends? # TODO: Any difference between backends?

View file

@ -1,4 +1,12 @@
[workspace] [workspace]
default-members = [
"rtic",
"rtic-sync",
"rtic-common",
"rtic-macros",
"rtic-monotonics",
"rtic-time",
]
members = [ members = [
"rtic", "rtic",
"rtic-sync", "rtic-sync",

View file

@ -6,10 +6,8 @@ publish = false
[dependencies] [dependencies]
anyhow = "1.0.43" anyhow = "1.0.43"
os_pipe = "1.1.2"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
env_logger = "0.10.0" pretty_env_logger = "0.4.0"
log = "0.4.17" log = "0.4.17"
rayon = "1.6.1" rayon = { version = "1.6.1", optional = true }
diffy = "0.3.0" diffy = "0.3.0"
exitcode = "1.1.2"

View file

@ -1,4 +1,4 @@
use crate::{command::CargoCommand, ARMV6M, ARMV7M, ARMV8MBASE, ARMV8MMAIN, DEFAULT_FEATURES}; use crate::{command::CargoCommand, Target, ARMV6M, ARMV7M, ARMV8MBASE, ARMV8MMAIN};
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use core::fmt; use core::fmt;
@ -29,6 +29,64 @@ impl Package {
Package::RticTime => "rtic-time", Package::RticTime => "rtic-time",
} }
} }
pub fn all() -> Vec<Self> {
vec![
Self::Rtic,
Self::RticCommon,
Self::RticMacros,
Self::RticMonotonics,
Self::RticSync,
Self::RticTime,
]
}
/// Get the features needed given the selected package
///
/// Without package specified the features for RTIC are required
/// With only a single package which is not RTIC, no special
/// features are needed
pub fn features(
&self,
target: Target,
backend: Backends,
partial: bool,
) -> Vec<Option<String>> {
match self {
Package::Rtic => vec![Some(target.and_features(backend.to_rtic_feature()))],
Package::RticMacros => {
vec![Some(backend.to_rtic_macros_feature().to_string())]
}
Package::RticMonotonics => {
let features = if partial {
&["cortex-m-systick", "rp2040", "nrf52840"][..]
} else {
&[
"cortex-m-systick",
"cortex-m-systick,systick-100hz",
"cortex-m-systick,systick-10khz",
"rp2040",
"nrf52810",
"nrf52811",
"nrf52832",
"nrf52833",
"nrf52840",
"nrf5340-app",
"nrf5340-net",
"nrf9160",
][..]
};
features
.into_iter()
.map(ToString::to_string)
.map(Some)
.chain(std::iter::once(None))
.collect()
}
_ => vec![None],
}
}
} }
pub struct TestMetadata {} pub struct TestMetadata {}
@ -37,12 +95,12 @@ impl TestMetadata {
pub fn match_package(package: Package, backend: Backends) -> CargoCommand<'static> { pub fn match_package(package: Package, backend: Backends) -> CargoCommand<'static> {
match package { match package {
Package::Rtic => { Package::Rtic => {
let features = Some(format!( let features = format!(
"{},{},{}", "{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature(), backend.to_rtic_feature(),
backend.to_rtic_uitest_feature(), backend.to_rtic_uitest_feature()
)); );
let features = Some(backend.to_target().and_features(&features));
CargoCommand::Test { CargoCommand::Test {
package: Some(package), package: Some(package),
features, features,
@ -89,7 +147,7 @@ pub enum Backends {
impl Backends { impl Backends {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_target(&self) -> &str { pub fn to_target(&self) -> Target<'static> {
match self { match self {
Backends::Thumbv6 => ARMV6M, Backends::Thumbv6 => ARMV6M,
Backends::Thumbv7 => ARMV7M, Backends::Thumbv7 => ARMV7M,
@ -99,7 +157,7 @@ impl Backends {
} }
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_rtic_feature(&self) -> &str { pub fn to_rtic_feature(&self) -> &'static str {
match self { match self {
Backends::Thumbv6 => "thumbv6-backend", Backends::Thumbv6 => "thumbv6-backend",
Backends::Thumbv7 => "thumbv7-backend", Backends::Thumbv7 => "thumbv7-backend",
@ -108,14 +166,14 @@ impl Backends {
} }
} }
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_rtic_macros_feature(&self) -> &str { pub fn to_rtic_macros_feature(&self) -> &'static str {
match self { match self {
Backends::Thumbv6 | Backends::Thumbv8Base => "cortex-m-source-masking", Backends::Thumbv6 | Backends::Thumbv8Base => "cortex-m-source-masking",
Backends::Thumbv7 | Backends::Thumbv8Main => "cortex-m-basepri", Backends::Thumbv7 | Backends::Thumbv8Main => "cortex-m-basepri",
} }
} }
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub fn to_rtic_uitest_feature(&self) -> &str { pub fn to_rtic_uitest_feature(&self) -> &'static str {
match self { match self {
Backends::Thumbv6 | Backends::Thumbv8Base => "rtic-uitestv6", Backends::Thumbv6 | Backends::Thumbv8Base => "rtic-uitestv6",
Backends::Thumbv7 | Backends::Thumbv8Main => "rtic-uitestv7", Backends::Thumbv7 | Backends::Thumbv8Main => "rtic-uitestv7",
@ -130,12 +188,10 @@ pub enum BuildOrCheck {
Build, Build,
} }
#[derive(Parser)] #[derive(Parser, Clone)]
#[command(author, version, about, long_about = None)] pub struct Globals {
/// RTIC xtask powered testing toolbox
pub struct Cli {
/// For which backend to build (defaults to thumbv7) /// For which backend to build (defaults to thumbv7)
#[arg(value_enum, short, long)] #[arg(value_enum, short, long, global = true)]
pub backend: Option<Backends>, pub backend: Option<Backends>,
/// List of comma separated examples to include, all others are excluded /// List of comma separated examples to include, all others are excluded
@ -144,7 +200,7 @@ pub struct Cli {
/// ///
/// Example: `cargo xtask --example complex,spawn,init` /// Example: `cargo xtask --example complex,spawn,init`
/// would include complex, spawn and init /// would include complex, spawn and init
#[arg(short, long, group = "example_group")] #[arg(short, long, group = "example_group", global = true)]
pub example: Option<String>, pub example: Option<String>,
/// List of comma separated examples to exclude, all others are included /// List of comma separated examples to exclude, all others are included
@ -153,25 +209,44 @@ pub struct Cli {
/// ///
/// Example: `cargo xtask --excludeexample complex,spawn,init` /// Example: `cargo xtask --excludeexample complex,spawn,init`
/// would exclude complex, spawn and init /// would exclude complex, spawn and init
#[arg(long, group = "example_group")] #[arg(long, group = "example_group", global = true)]
pub exampleexclude: Option<String>, pub exampleexclude: Option<String>,
/// Enable more verbose output, repeat up to `-vvv` for even more /// Enable more verbose output, repeat up to `-vvv` for even more
#[arg(short, long, action = clap::ArgAction::Count)] #[arg(short, long, action = clap::ArgAction::Count, global = true)]
pub verbose: u8, pub verbose: u8,
/// Enable `stderr` inheritance on child processes.
///
/// If this flag is enabled, the output of `stderr` produced by child
/// processes is printed directly to `stderr`. This will cause a lot of
/// clutter, but can make debugging long-running processes a lot easier.
#[arg(short, long, global = true)]
pub stderr_inherited: bool,
/// Don't build/check/test all feature combinations that are available, only
/// a necessary subset.
#[arg(long, global = true)]
pub partial: bool,
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
/// RTIC xtask powered testing toolbox
pub struct Cli {
#[clap(flatten)]
pub globals: Globals,
/// Subcommand selecting operation /// Subcommand selecting operation
#[command(subcommand)] #[command(subcommand)]
pub command: Commands, pub command: Commands,
} }
#[derive(Debug, Subcommand)] #[derive(Debug, Clone, Subcommand)]
pub enum Commands { pub enum Commands {
/// Check formatting
FormatCheck(PackageOpt),
/// Format code /// Format code
Format(PackageOpt), #[clap(alias = "fmt")]
Format(FormatOpt),
/// Run clippy /// Run clippy
Clippy(PackageOpt), Clippy(PackageOpt),
@ -227,16 +302,44 @@ pub enum Commands {
Book(Arg), Book(Arg),
} }
#[derive(Args, Debug)] #[derive(Args, Debug, Clone)]
pub struct FormatOpt {
#[clap(flatten)]
pub package: PackageOpt,
/// Check-only, do not apply formatting fixes.
#[clap(short, long)]
pub check: bool,
}
#[derive(Args, Debug, Clone)]
/// Restrict to package, or run on whole workspace /// Restrict to package, or run on whole workspace
pub struct PackageOpt { pub struct PackageOpt {
/// For which package/workspace member to operate /// For which package/workspace member to operate
/// ///
/// If omitted, work on all /// If omitted, work on all
pub package: Option<Package>, package: Option<Package>,
} }
#[derive(Args, Debug)] impl PackageOpt {
#[cfg(not(feature = "rayon"))]
pub fn packages(&self) -> impl Iterator<Item = Package> {
self.package
.map(|p| vec![p])
.unwrap_or(Package::all())
.into_iter()
}
#[cfg(feature = "rayon")]
pub fn packages(&self) -> impl rayon::prelude::ParallelIterator<Item = Package> {
use rayon::prelude::*;
self.package
.map(|p| vec![p])
.unwrap_or(Package::all())
.into_par_iter()
}
}
#[derive(Args, Debug, Clone)]
pub struct QemuAndRun { pub struct QemuAndRun {
/// If expected output is missing or mismatching, recreate the file /// If expected output is missing or mismatching, recreate the file
/// ///
@ -245,7 +348,7 @@ pub struct QemuAndRun {
pub overwrite_expected: bool, pub overwrite_expected: bool,
} }
#[derive(Debug, Parser)] #[derive(Debug, Parser, Clone)]
pub struct Arg { pub struct Arg {
/// Options to pass to `cargo size` /// Options to pass to `cargo size`
#[command(subcommand)] #[command(subcommand)]
@ -258,3 +361,13 @@ pub enum ExtraArguments {
#[command(external_subcommand)] #[command(external_subcommand)]
Other(Vec<String>), Other(Vec<String>),
} }
impl core::fmt::Display for ExtraArguments {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExtraArguments::Other(args) => {
write!(f, "{}", args.join(" "))
}
}
}
}

View file

@ -1,55 +1,143 @@
use crate::{ use crate::{
argument_parsing::{Backends, BuildOrCheck, ExtraArguments, Package, PackageOpt, TestMetadata}, argument_parsing::{Backends, BuildOrCheck, ExtraArguments, Globals, PackageOpt, TestMetadata},
command::{BuildMode, CargoCommand}, command::{BuildMode, CargoCommand},
command_parser, package_feature_extractor, DEFAULT_FEATURES, command_parser, RunResult,
}; };
use log::error; use log::error;
#[cfg(feature = "rayon")]
use rayon::prelude::*; use rayon::prelude::*;
/// Cargo command to either build or check use iters::*;
pub fn cargo(
operation: BuildOrCheck,
cargoarg: &Option<&str>,
package: &PackageOpt,
backend: Backends,
) -> anyhow::Result<()> {
let features = package_feature_extractor(package, backend);
pub enum FinalRunResult<'c> {
Success(CargoCommand<'c>, RunResult),
Failed(CargoCommand<'c>, RunResult),
CommandError(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);
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(e),
}
}
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<FinalRunResult<'c>>;
}
#[cfg(not(feature = "rayon"))]
mod iters {
use super::*;
pub fn examples_iter(examples: &[String]) -> impl Iterator<Item = &String> {
examples.into_iter()
}
impl<'g, 'c, I> CoalescingRunner<'c> for I
where
I: Iterator<Item = (&'g Globals, CargoCommand<'c>, bool)>,
{
fn run_and_coalesce(self) -> Vec<FinalRunResult<'c>> {
self.map(run_and_convert).collect()
}
}
}
#[cfg(feature = "rayon")]
mod iters {
use super::*;
pub fn examples_iter(examples: &[String]) -> impl ParallelIterator<Item = &String> {
examples.into_par_iter()
}
impl<'g, 'c, I> CoalescingRunner<'c> for I
where
I: ParallelIterator<Item = (&'g Globals, CargoCommand<'c>, bool)>,
{
fn run_and_coalesce(self) -> Vec<FinalRunResult<'c>> {
self.map(run_and_convert).collect()
}
}
}
/// 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<FinalRunResult<'c>> {
let runner = package
.packages()
.flat_map(|package| {
let target = backend.to_target();
let features = package.features(target, backend, globals.partial);
#[cfg(feature = "rayon")]
{
features.into_par_iter().map(move |f| (package, target, f))
}
#[cfg(not(feature = "rayon"))]
{
features.into_iter().map(move |f| (package, target, f))
}
})
.map(move |(package, target, features)| {
let command = match operation { let command = match operation {
BuildOrCheck::Check => CargoCommand::Check { BuildOrCheck::Check => CargoCommand::Check {
cargoarg, cargoarg,
package: package.package, package: Some(package),
target: backend.to_target(), target,
features, features,
mode: BuildMode::Release, mode: BuildMode::Release,
}, },
BuildOrCheck::Build => CargoCommand::Build { BuildOrCheck::Build => CargoCommand::Build {
cargoarg, cargoarg,
package: package.package, package: Some(package),
target: backend.to_target(), target,
features, features,
mode: BuildMode::Release, mode: BuildMode::Release,
}, },
}; };
command_parser(&command, false)?;
Ok(()) (globals, command, false)
});
runner.run_and_coalesce()
} }
/// Cargo command to either build or check all examples /// Cargo command to either build or check all examples
/// ///
/// The examples are in rtic/examples /// The examples are in rtic/examples
pub fn cargo_example( pub fn cargo_example<'c>(
globals: &Globals,
operation: BuildOrCheck, operation: BuildOrCheck,
cargoarg: &Option<&str>, cargoarg: &'c Option<&'c str>,
backend: Backends, backend: Backends,
examples: &[String], examples: &'c [String],
) -> anyhow::Result<()> { ) -> Vec<FinalRunResult<'c>> {
examples.into_par_iter().for_each(|example| { let runner = examples_iter(examples).map(|example| {
let features = Some(format!( let features = Some(backend.to_target().and_features(backend.to_rtic_feature()));
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
));
let command = match operation { let command = match operation {
BuildOrCheck::Check => CargoCommand::ExampleCheck { BuildOrCheck::Check => CargoCommand::ExampleCheck {
@ -67,184 +155,178 @@ pub fn cargo_example(
mode: BuildMode::Release, mode: BuildMode::Release,
}, },
}; };
(globals, command, false)
if let Err(err) = command_parser(&command, false) {
error!("{err}");
}
}); });
runner.run_and_coalesce()
Ok(())
} }
/// Run cargo clippy on selected package /// Run cargo clippy on selected package
pub fn cargo_clippy( pub fn cargo_clippy<'c>(
cargoarg: &Option<&str>, globals: &Globals,
package: &PackageOpt, cargoarg: &'c Option<&'c str>,
package: &'c PackageOpt,
backend: Backends, backend: Backends,
) -> anyhow::Result<()> { ) -> Vec<FinalRunResult<'c>> {
let features = package_feature_extractor(package, backend); let runner = package
command_parser( .packages()
&CargoCommand::Clippy { .flat_map(|package| {
let target = backend.to_target();
let features = package.features(target, backend, globals.partial);
#[cfg(feature = "rayon")]
{
features.into_par_iter().map(move |f| (package, target, f))
}
#[cfg(not(feature = "rayon"))]
{
features.into_iter().map(move |f| (package, target, f))
}
})
.map(move |(package, target, features)| {
(
globals,
CargoCommand::Clippy {
cargoarg, cargoarg,
package: package.package, package: Some(package),
target: backend.to_target(), target,
features, features,
}, },
false, false,
)?; )
Ok(()) });
runner.run_and_coalesce()
} }
/// Run cargo fmt on selected package /// Run cargo fmt on selected package
pub fn cargo_format( pub fn cargo_format<'c>(
cargoarg: &Option<&str>, globals: &Globals,
package: &PackageOpt, cargoarg: &'c Option<&'c str>,
package: &'c PackageOpt,
check_only: bool, check_only: bool,
) -> anyhow::Result<()> { ) -> Vec<FinalRunResult<'c>> {
command_parser( let runner = package.packages().map(|p| {
&CargoCommand::Format { (
globals,
CargoCommand::Format {
cargoarg, cargoarg,
package: package.package, package: Some(p),
check_only, check_only,
}, },
false, false,
)?; )
Ok(()) });
runner.run_and_coalesce()
} }
/// Run cargo doc /// Run cargo doc
pub fn cargo_doc( pub fn cargo_doc<'c>(
cargoarg: &Option<&str>, globals: &Globals,
cargoarg: &'c Option<&'c str>,
backend: Backends, backend: Backends,
arguments: &Option<ExtraArguments>, arguments: &'c Option<ExtraArguments>,
) -> anyhow::Result<()> { ) -> Vec<FinalRunResult<'c>> {
let features = Some(format!( let features = Some(backend.to_target().and_features(backend.to_rtic_feature()));
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
));
command_parser( let command = CargoCommand::Doc {
&CargoCommand::Doc {
cargoarg, cargoarg,
features, features,
arguments: arguments.clone(), arguments: arguments.clone(),
}, };
false,
)?; vec![run_and_convert((globals, command, false))]
Ok(())
} }
/// Run cargo test on the selcted package or all packages /// Run cargo test on the selected package or all packages
/// ///
/// If no package is specified, loop through all packages /// If no package is specified, loop through all packages
pub fn cargo_test(package: &PackageOpt, backend: Backends) -> anyhow::Result<()> { pub fn cargo_test<'c>(
if let Some(package) = package.package { globals: &Globals,
let cmd = TestMetadata::match_package(package, backend); package: &'c PackageOpt,
command_parser(&cmd, false)?; backend: Backends,
} else { ) -> Vec<FinalRunResult<'c>> {
// Iterate over all workspace packages package
for package in [ .packages()
Package::Rtic, .map(|p| (globals, TestMetadata::match_package(p, backend), false))
Package::RticCommon, .run_and_coalesce()
Package::RticMacros,
Package::RticMonotonics,
Package::RticSync,
Package::RticTime,
] {
let mut error_messages = vec![];
let cmd = &TestMetadata::match_package(package, backend);
if let Err(err) = command_parser(cmd, false) {
error_messages.push(err);
}
if !error_messages.is_empty() {
for err in error_messages {
error!("{err}");
}
}
}
}
Ok(())
} }
/// Use mdbook to build the book /// Use mdbook to build the book
pub fn cargo_book(arguments: &Option<ExtraArguments>) -> anyhow::Result<()> { pub fn cargo_book<'c>(
command_parser( globals: &Globals,
&CargoCommand::Book { arguments: &'c Option<ExtraArguments>,
) -> Vec<FinalRunResult<'c>> {
vec![run_and_convert((
globals,
CargoCommand::Book {
arguments: arguments.clone(), arguments: arguments.clone(),
}, },
false, false,
)?; ))]
Ok(())
} }
/// Run examples /// Run examples
/// ///
/// Supports updating the expected output via the overwrite argument /// Supports updating the expected output via the overwrite argument
pub fn run_test( pub fn run_test<'c>(
cargoarg: &Option<&str>, globals: &Globals,
cargoarg: &'c Option<&'c str>,
backend: Backends, backend: Backends,
examples: &[String], examples: &'c [String],
overwrite: bool, overwrite: bool,
) -> anyhow::Result<()> { ) -> Vec<FinalRunResult<'c>> {
examples.into_par_iter().for_each(|example| { let target = backend.to_target();
let features = Some(target.and_features(backend.to_rtic_feature()));
examples_iter(examples)
.map(|example| {
let cmd = CargoCommand::ExampleBuild { let cmd = CargoCommand::ExampleBuild {
cargoarg: &Some("--quiet"), cargoarg: &Some("--quiet"),
example, example,
target: backend.to_target(), target,
features: Some(format!( features: features.clone(),
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
)),
mode: BuildMode::Release, mode: BuildMode::Release,
}; };
if let Err(err) = command_parser(&cmd, false) {
if let Err(err) = command_parser(globals, &cmd, false) {
error!("{err}"); error!("{err}");
} }
let cmd = CargoCommand::Qemu { let cmd = CargoCommand::Qemu {
cargoarg, cargoarg,
example, example,
target: backend.to_target(), target,
features: Some(format!( features: features.clone(),
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
)),
mode: BuildMode::Release, mode: BuildMode::Release,
}; };
if let Err(err) = command_parser(&cmd, overwrite) { (globals, cmd, overwrite)
error!("{err}"); })
} .run_and_coalesce()
});
Ok(())
} }
/// Check the binary sizes of examples /// Check the binary sizes of examples
pub fn build_and_check_size( pub fn build_and_check_size<'c>(
cargoarg: &Option<&str>, globals: &Globals,
cargoarg: &'c Option<&'c str>,
backend: Backends, backend: Backends,
examples: &[String], examples: &'c [String],
arguments: &Option<ExtraArguments>, arguments: &'c Option<ExtraArguments>,
) -> anyhow::Result<()> { ) -> Vec<FinalRunResult<'c>> {
examples.into_par_iter().for_each(|example| { let target = backend.to_target();
let features = Some(target.and_features(backend.to_rtic_feature()));
let runner = examples_iter(examples).map(|example| {
// Make sure the requested example(s) are built // Make sure the requested example(s) are built
let cmd = CargoCommand::ExampleBuild { let cmd = CargoCommand::ExampleBuild {
cargoarg: &Some("--quiet"), cargoarg: &Some("--quiet"),
example, example,
target: backend.to_target(), target,
features: Some(format!( features: features.clone(),
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
)),
mode: BuildMode::Release, mode: BuildMode::Release,
}; };
if let Err(err) = command_parser(&cmd, false) { if let Err(err) = command_parser(globals, &cmd, false) {
error!("{err}"); error!("{err}");
} }
@ -252,18 +334,12 @@ pub fn build_and_check_size(
cargoarg, cargoarg,
example, example,
target: backend.to_target(), target: backend.to_target(),
features: Some(format!( features: features.clone(),
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
)),
mode: BuildMode::Release, mode: BuildMode::Release,
arguments: arguments.clone(), arguments: arguments.clone(),
}; };
if let Err(err) = command_parser(&cmd, false) { (globals, cmd, false)
error!("{err}");
}
}); });
Ok(()) runner.run_and_coalesce()
} }

View file

@ -1,7 +1,15 @@
use crate::{debug, ExtraArguments, Package, RunResult, TestRunError}; use log::{error, info, Level};
use crate::{
argument_parsing::Globals, cargo_commands::FinalRunResult, ExtraArguments, Package, RunResult,
Target, TestRunError,
};
use core::fmt; use core::fmt;
use os_pipe::pipe; use std::{
use std::{fs::File, io::Read, process::Command}; fs::File,
io::Read,
process::{Command, Stdio},
};
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
@ -10,6 +18,21 @@ pub enum BuildMode {
Debug, Debug,
} }
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OutputMode {
PipedAndCollected,
Inherited,
}
impl From<OutputMode> for Stdio {
fn from(value: OutputMode) -> Self {
match value {
OutputMode::PipedAndCollected => Stdio::piped(),
OutputMode::Inherited => Stdio::inherit(),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum CargoCommand<'a> { pub enum CargoCommand<'a> {
// For future embedded-ci // For future embedded-ci
@ -17,49 +40,49 @@ pub enum CargoCommand<'a> {
Run { Run {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
example: &'a str, example: &'a str,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
}, },
Qemu { Qemu {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
example: &'a str, example: &'a str,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
}, },
ExampleBuild { ExampleBuild {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
example: &'a str, example: &'a str,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
}, },
ExampleCheck { ExampleCheck {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
example: &'a str, example: &'a str,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
}, },
Build { Build {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
package: Option<Package>, package: Option<Package>,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
}, },
Check { Check {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
package: Option<Package>, package: Option<Package>,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
}, },
Clippy { Clippy {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
package: Option<Package>, package: Option<Package>,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
}, },
Format { Format {
@ -83,14 +106,216 @@ pub enum CargoCommand<'a> {
ExampleSize { ExampleSize {
cargoarg: &'a Option<&'a str>, cargoarg: &'a Option<&'a str>,
example: &'a str, example: &'a str,
target: &'a str, target: Target<'a>,
features: Option<String>, features: Option<String>,
mode: BuildMode, mode: BuildMode,
arguments: Option<ExtraArguments>, arguments: Option<ExtraArguments>,
}, },
} }
impl core::fmt::Display for CargoCommand<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let p = |p: &Option<Package>| {
if let Some(package) = p {
format!("package {package}")
} else {
format!("default package")
}
};
let feat = |f: &Option<String>| {
if let Some(features) = f {
format!("\"{features}\"")
} else {
format!("no features")
}
};
let carg = |f: &&Option<&str>| {
if let Some(cargoarg) = f {
format!("{cargoarg}")
} else {
format!("no cargo args")
}
};
let details = |target: &Target,
mode: &BuildMode,
features: &Option<String>,
cargoarg: &&Option<&str>| {
let feat = feat(features);
let carg = carg(cargoarg);
if cargoarg.is_some() {
format!("({target}, {mode}, {feat}, {carg})")
} else {
format!("({target}, {mode}, {feat})")
}
};
match self {
CargoCommand::Run {
cargoarg,
example,
target,
features,
mode,
} => write!(
f,
"Run example {example} {}",
details(target, mode, features, cargoarg)
),
CargoCommand::Qemu {
cargoarg,
example,
target,
features,
mode,
} => write!(
f,
"Run example {example} in QEMU {}",
details(target, mode, features, cargoarg)
),
CargoCommand::ExampleBuild {
cargoarg,
example,
target,
features,
mode,
} => write!(
f,
"Build example {example} {}",
details(target, mode, features, cargoarg)
),
CargoCommand::ExampleCheck {
cargoarg,
example,
target,
features,
mode,
} => write!(
f,
"Check example {example} {}",
details(target, mode, features, cargoarg)
),
CargoCommand::Build {
cargoarg,
package,
target,
features,
mode,
} => {
let package = p(package);
write!(
f,
"Build {package} {}",
details(target, mode, features, cargoarg)
)
}
CargoCommand::Check {
cargoarg,
package,
target,
features,
mode,
} => {
let package = p(package);
write!(
f,
"Check {package} {}",
details(target, mode, features, cargoarg)
)
}
CargoCommand::Clippy {
cargoarg,
package,
target,
features,
} => {
let package = p(package);
let features = feat(features);
let carg = carg(cargoarg);
if cargoarg.is_some() {
write!(f, "Clippy {package} ({target}, {features}, {carg})")
} else {
write!(f, "Clippy {package} ({target}, {features})")
}
}
CargoCommand::Format {
cargoarg,
package,
check_only,
} => {
let package = p(package);
let carg = carg(cargoarg);
let carg = if cargoarg.is_some() {
format!("(cargo args: {carg})")
} else {
format!("")
};
if *check_only {
write!(f, "Check format for {package} {carg}")
} else {
write!(f, "Format {package} {carg}")
}
}
CargoCommand::Doc {
cargoarg,
features,
arguments,
} => {
let feat = feat(features);
let carg = carg(cargoarg);
let arguments = arguments
.clone()
.map(|a| format!("{a}"))
.unwrap_or_else(|| "no extra arguments".into());
if cargoarg.is_some() {
write!(f, "Document ({feat}, {carg}, {arguments})")
} else {
write!(f, "Document ({feat}, {arguments})")
}
}
CargoCommand::Test {
package,
features,
test,
} => {
let p = p(package);
let test = test
.clone()
.map(|t| format!("test {t}"))
.unwrap_or("all tests".into());
let feat = feat(features);
write!(f, "Run {test} in {p} (features: {feat})")
}
CargoCommand::Book { arguments: _ } => write!(f, "Build the book"),
CargoCommand::ExampleSize {
cargoarg,
example,
target,
features,
mode,
arguments: _,
} => {
write!(
f,
"Compute size of example {example} {}",
details(target, mode, features, cargoarg)
)
}
}
}
}
impl<'a> CargoCommand<'a> { impl<'a> CargoCommand<'a> {
pub fn as_cmd_string(&self) -> String {
let executable = self.executable();
let args = self.args().join(" ");
format!("{executable} {args}")
}
fn command(&self) -> &str { fn command(&self) -> &str {
match self { match self {
CargoCommand::Run { .. } | CargoCommand::Qemu { .. } => "run", CargoCommand::Run { .. } | CargoCommand::Qemu { .. } => "run",
@ -135,7 +360,13 @@ impl<'a> CargoCommand<'a> {
if let Some(cargoarg) = cargoarg { if let Some(cargoarg) = cargoarg {
args.extend_from_slice(&[cargoarg]); args.extend_from_slice(&[cargoarg]);
} }
args.extend_from_slice(&[self.command(), "--example", example, "--target", target]); args.extend_from_slice(&[
self.command(),
"--example",
example,
"--target",
target.triple(),
]);
if let Some(feature) = features { if let Some(feature) = features {
args.extend_from_slice(&["--features", feature]); args.extend_from_slice(&["--features", feature]);
@ -156,7 +387,13 @@ impl<'a> CargoCommand<'a> {
if let Some(cargoarg) = cargoarg { if let Some(cargoarg) = cargoarg {
args.extend_from_slice(&[cargoarg]); args.extend_from_slice(&[cargoarg]);
} }
args.extend_from_slice(&[self.command(), "--example", example, "--target", target]); args.extend_from_slice(&[
self.command(),
"--example",
example,
"--target",
target.triple(),
]);
if let Some(feature) = features { if let Some(feature) = features {
args.extend_from_slice(&["--features", feature]); args.extend_from_slice(&["--features", feature]);
@ -178,7 +415,7 @@ impl<'a> CargoCommand<'a> {
args.extend_from_slice(&[cargoarg]); args.extend_from_slice(&[cargoarg]);
} }
args.extend_from_slice(&[self.command(), "--target", target]); args.extend_from_slice(&[self.command(), "--target", target.triple()]);
if let Some(package) = package { if let Some(package) = package {
args.extend_from_slice(&["--package", package.name()]); args.extend_from_slice(&["--package", package.name()]);
@ -326,7 +563,13 @@ impl<'a> CargoCommand<'a> {
if let Some(cargoarg) = cargoarg { if let Some(cargoarg) = cargoarg {
args.extend_from_slice(&[cargoarg]); args.extend_from_slice(&[cargoarg]);
} }
args.extend_from_slice(&[self.command(), "--example", example, "--target", target]); args.extend_from_slice(&[
self.command(),
"--example",
example,
"--target",
target.triple(),
]);
if let Some(feature) = features { if let Some(feature) = features {
args.extend_from_slice(&["--features", feature]); args.extend_from_slice(&["--features", feature]);
@ -347,7 +590,13 @@ impl<'a> CargoCommand<'a> {
if let Some(cargoarg) = cargoarg { if let Some(cargoarg) = cargoarg {
args.extend_from_slice(&[cargoarg]); args.extend_from_slice(&[cargoarg]);
} }
args.extend_from_slice(&[self.command(), "--example", example, "--target", target]); args.extend_from_slice(&[
self.command(),
"--example",
example,
"--target",
target.triple(),
]);
if let Some(feature) = features { if let Some(feature) = features {
args.extend_from_slice(&["--features", feature]); args.extend_from_slice(&["--features", feature]);
@ -369,7 +618,13 @@ impl<'a> CargoCommand<'a> {
if let Some(cargoarg) = cargoarg { if let Some(cargoarg) = cargoarg {
args.extend_from_slice(&[cargoarg]); args.extend_from_slice(&[cargoarg]);
} }
args.extend_from_slice(&[self.command(), "--example", example, "--target", target]); args.extend_from_slice(&[
self.command(),
"--example",
example,
"--target",
target.triple(),
]);
if let Some(feature_name) = features { if let Some(feature_name) = features {
args.extend_from_slice(&["--features", feature_name]); args.extend_from_slice(&["--features", feature_name]);
@ -411,24 +666,18 @@ impl fmt::Display for BuildMode {
} }
} }
pub fn run_command(command: &CargoCommand) -> anyhow::Result<RunResult> { pub fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Result<RunResult> {
let (mut reader, writer) = pipe()?; log::info!("👟 {command}");
let (mut error_reader, error_writer) = pipe()?;
debug!("👟 {} {}", command.executable(), command.args().join(" "));
let mut handle = Command::new(command.executable()) let result = Command::new(command.executable())
.args(command.args()) .args(command.args())
.stdout(writer) .stdout(Stdio::piped())
.stderr(error_writer) .stderr(stderr_mode)
.spawn()?; .output()?;
// retrieve output and clean up let exit_status = result.status;
let mut stdout = String::new(); let stderr = String::from_utf8(result.stderr).unwrap_or("Not displayable".into());
reader.read_to_string(&mut stdout)?; let stdout = String::from_utf8(result.stdout).unwrap_or("Not displayable".into());
let exit_status = handle.wait()?;
let mut stderr = String::new();
error_reader.read_to_string(&mut stderr)?;
Ok(RunResult { Ok(RunResult {
exit_status, exit_status,
@ -463,3 +712,68 @@ pub fn run_successful(run: &RunResult, expected_output_file: &str) -> Result<(),
Ok(()) Ok(())
} }
} }
pub fn handle_results(globals: &Globals, results: Vec<FinalRunResult>) -> anyhow::Result<()> {
let errors = results.iter().filter_map(|r| {
if let FinalRunResult::Failed(c, r) = r {
Some((c, r))
} else {
None
}
});
let successes = results.iter().filter_map(|r| {
if let FinalRunResult::Success(c, r) = r {
Some((c, r))
} else {
None
}
});
let log_stdout_stderr = |level: Level| {
move |(command, result): (&CargoCommand, &RunResult)| {
let stdout = &result.stdout;
let stderr = &result.stderr;
if !stdout.is_empty() && !stderr.is_empty() {
log::log!(
level,
"Output for \"{command}\"\nStdout:\n{stdout}\nStderr:\n{stderr}"
);
} else if !stdout.is_empty() {
log::log!(
level,
"Output for \"{command}\":\nStdout:\n{}",
stdout.trim_end()
);
} else if !stderr.is_empty() {
log::log!(
level,
"Output for \"{command}\"\nStderr:\n{}",
stderr.trim_end()
);
}
}
};
successes.clone().for_each(log_stdout_stderr(Level::Debug));
errors.clone().for_each(log_stdout_stderr(Level::Error));
successes.for_each(|(cmd, _)| {
if globals.verbose > 0 {
info!("✅ Success: {cmd}\n {}", cmd.as_cmd_string());
} else {
info!("✅ Success: {cmd}");
}
});
errors.clone().for_each(|(cmd, _)| {
error!("❌ Failed: {cmd}\n {}", cmd.as_cmd_string());
});
let ecount = errors.count();
if ecount != 0 {
Err(anyhow::anyhow!("{ecount} commands failed."))
} else {
Ok(())
}
}

View file

@ -3,9 +3,9 @@ mod build;
mod cargo_commands; mod cargo_commands;
mod command; mod command;
use anyhow::bail; use argument_parsing::{ExtraArguments, Globals, Package};
use argument_parsing::{ExtraArguments, Package};
use clap::Parser; use clap::Parser;
use command::OutputMode;
use core::fmt; use core::fmt;
use diffy::{create_patch, PatchFormatter}; use diffy::{create_patch, PatchFormatter};
use std::{ use std::{
@ -14,32 +14,60 @@ use std::{
fs::File, fs::File,
io::prelude::*, io::prelude::*,
path::{Path, PathBuf}, path::{Path, PathBuf},
process,
process::ExitStatus, process::ExitStatus,
str, str,
}; };
use env_logger::Env; use log::{error, info, log_enabled, trace, Level};
use log::{debug, error, info, log_enabled, trace, Level};
use crate::{ use crate::{
argument_parsing::{Backends, BuildOrCheck, Cli, Commands, PackageOpt}, argument_parsing::{Backends, BuildOrCheck, Cli, Commands},
build::init_build_dir, build::init_build_dir,
cargo_commands::{ cargo_commands::{
build_and_check_size, cargo, cargo_book, cargo_clippy, cargo_doc, cargo_example, build_and_check_size, cargo, cargo_book, cargo_clippy, cargo_doc, cargo_example,
cargo_format, cargo_test, run_test, cargo_format, cargo_test, run_test,
}, },
command::{run_command, run_successful, CargoCommand}, command::{handle_results, run_command, run_successful, CargoCommand},
}; };
// x86_64-unknown-linux-gnu #[derive(Debug, Clone, Copy)]
const _X86_64: &str = "x86_64-unknown-linux-gnu"; pub struct Target<'a> {
const ARMV6M: &str = "thumbv6m-none-eabi"; triple: &'a str,
const ARMV7M: &str = "thumbv7m-none-eabi"; has_std: bool,
const ARMV8MBASE: &str = "thumbv8m.base-none-eabi"; }
const ARMV8MMAIN: &str = "thumbv8m.main-none-eabi";
const DEFAULT_FEATURES: &str = "test-critical-section"; impl<'a> Target<'a> {
const DEFAULT_FEATURES: &str = "test-critical-section";
pub const fn new(triple: &'a str, has_std: bool) -> Self {
Self { triple, has_std }
}
pub fn triple(&self) -> &str {
self.triple
}
pub fn has_std(&self) -> bool {
self.has_std
}
pub fn and_features(&self, features: &str) -> String {
format!("{},{}", Self::DEFAULT_FEATURES, features)
}
}
impl core::fmt::Display for Target<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.triple)
}
}
// x86_64-unknown-linux-gnu
const _X86_64: Target = Target::new("x86_64-unknown-linux-gnu", true);
const ARMV6M: Target = Target::new("thumbv6m-none-eabi", false);
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)] #[derive(Debug, Clone)]
pub struct RunResult { pub struct RunResult {
@ -96,7 +124,9 @@ fn main() -> anyhow::Result<()> {
// check the name of `env::current_dir()` because people might clone it into a different name) // check the name of `env::current_dir()` because people might clone it into a different name)
let probably_running_from_repo_root = Path::new("./xtask").exists(); let probably_running_from_repo_root = Path::new("./xtask").exists();
if !probably_running_from_repo_root { if !probably_running_from_repo_root {
bail!("xtasks can only be executed from the root of the `rtic` repository"); return Err(anyhow::anyhow!(
"xtasks can only be executed from the root of the `rtic` repository"
));
} }
let examples: Vec<_> = std::fs::read_dir("./rtic/examples")? let examples: Vec<_> = std::fs::read_dir("./rtic/examples")?
@ -108,26 +138,28 @@ fn main() -> anyhow::Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
let env_logger_default_level = match cli.verbose { let globals = &cli.globals;
0 => Env::default().default_filter_or("info"),
1 => Env::default().default_filter_or("debug"), let env_logger_default_level = match globals.verbose {
_ => Env::default().default_filter_or("trace"), 0 => "info",
1 => "debug",
_ => "trace",
}; };
env_logger::Builder::from_env(env_logger_default_level)
.format_module_path(false) pretty_env_logger::formatted_builder()
.format_timestamp(None) .parse_filters(&std::env::var("RUST_LOG").unwrap_or(env_logger_default_level.into()))
.init(); .init();
trace!("default logging level: {0}", cli.verbose); trace!("default logging level: {0}", globals.verbose);
let backend = if let Some(backend) = cli.backend { let backend = if let Some(backend) = globals.backend {
backend backend
} else { } else {
Backends::default() Backends::default()
}; };
let example = cli.example; let example = globals.example.clone();
let exampleexclude = cli.exampleexclude; let exampleexclude = globals.exampleexclude.clone();
let examples_to_run = { let examples_to_run = {
let mut examples_to_run = examples.clone(); let mut examples_to_run = examples.clone();
@ -164,10 +196,10 @@ fn main() -> anyhow::Result<()> {
\n{examples:#?}\n\ \n{examples:#?}\n\
By default if example flag is emitted, all examples are tested.", By default if example flag is emitted, all examples are tested.",
); );
process::exit(exitcode::USAGE); return Err(anyhow::anyhow!("Incorrect usage"));
} else { } else {
}
examples_to_run examples_to_run
}
}; };
init_build_dir()?; init_build_dir()?;
@ -185,104 +217,91 @@ fn main() -> anyhow::Result<()> {
Some("--quiet") Some("--quiet")
}; };
match cli.command { let final_run_results = match &cli.command {
Commands::FormatCheck(args) => { Commands::Format(args) => cargo_format(globals, &cargologlevel, &args.package, args.check),
info!("Running cargo fmt --check: {args:?}");
let check_only = true;
cargo_format(&cargologlevel, &args, check_only)?;
}
Commands::Format(args) => {
info!("Running cargo fmt: {args:?}");
let check_only = false;
cargo_format(&cargologlevel, &args, check_only)?;
}
Commands::Clippy(args) => { Commands::Clippy(args) => {
info!("Running clippy on backend: {backend:?}"); info!("Running clippy on backend: {backend:?}");
cargo_clippy(&cargologlevel, &args, backend)?; cargo_clippy(globals, &cargologlevel, &args, backend)
} }
Commands::Check(args) => { Commands::Check(args) => {
info!("Checking on backend: {backend:?}"); info!("Checking on backend: {backend:?}");
cargo(BuildOrCheck::Check, &cargologlevel, &args, backend)?; cargo(globals, BuildOrCheck::Check, &cargologlevel, &args, backend)
} }
Commands::Build(args) => { Commands::Build(args) => {
info!("Building for backend: {backend:?}"); info!("Building for backend: {backend:?}");
cargo(BuildOrCheck::Build, &cargologlevel, &args, backend)?; cargo(globals, BuildOrCheck::Build, &cargologlevel, &args, backend)
} }
Commands::ExampleCheck => { Commands::ExampleCheck => {
info!("Checking on backend: {backend:?}"); info!("Checking on backend: {backend:?}");
cargo_example( cargo_example(
globals,
BuildOrCheck::Check, BuildOrCheck::Check,
&cargologlevel, &cargologlevel,
backend, backend,
&examples_to_run, &examples_to_run,
)?; )
} }
Commands::ExampleBuild => { Commands::ExampleBuild => {
info!("Building for backend: {backend:?}"); info!("Building for backend: {backend:?}");
cargo_example( cargo_example(
globals,
BuildOrCheck::Build, BuildOrCheck::Build,
&cargologlevel, &cargologlevel,
backend, backend,
&examples_to_run, &examples_to_run,
)?; )
} }
Commands::Size(args) => { Commands::Size(args) => {
// x86_64 target not valid // x86_64 target not valid
info!("Measuring for backend: {backend:?}"); info!("Measuring for backend: {backend:?}");
build_and_check_size(&cargologlevel, backend, &examples_to_run, &args.arguments)?; build_and_check_size(
globals,
&cargologlevel,
backend,
&examples_to_run,
&args.arguments,
)
} }
Commands::Qemu(args) | Commands::Run(args) => { Commands::Qemu(args) | Commands::Run(args) => {
// x86_64 target not valid // x86_64 target not valid
info!("Testing for backend: {backend:?}"); info!("Testing for backend: {backend:?}");
run_test( run_test(
globals,
&cargologlevel, &cargologlevel,
backend, backend,
&examples_to_run, &examples_to_run,
args.overwrite_expected, args.overwrite_expected,
)?; )
} }
Commands::Doc(args) => { Commands::Doc(args) => {
info!("Running cargo doc on backend: {backend:?}"); info!("Running cargo doc on backend: {backend:?}");
cargo_doc(&cargologlevel, backend, &args.arguments)?; cargo_doc(globals, &cargologlevel, backend, &args.arguments)
} }
Commands::Test(args) => { Commands::Test(args) => {
info!("Running cargo test on backend: {backend:?}"); info!("Running cargo test on backend: {backend:?}");
cargo_test(&args, backend)?; cargo_test(globals, &args, backend)
} }
Commands::Book(args) => { Commands::Book(args) => {
info!("Running mdbook"); info!("Running mdbook");
cargo_book(&args.arguments)?; cargo_book(globals, &args.arguments)
}
} }
};
Ok(()) handle_results(globals, final_run_results)
}
/// Get the features needed given the selected package
///
/// Without package specified the features for RTIC are required
/// With only a single package which is not RTIC, no special
/// features are needed
fn package_feature_extractor(package: &PackageOpt, backend: Backends) -> Option<String> {
let default_features = Some(format!(
"{},{}",
DEFAULT_FEATURES,
backend.to_rtic_feature()
));
if let Some(package) = package.package {
debug!("\nTesting package: {package}");
match package {
Package::Rtic => default_features,
Package::RticMacros => Some(backend.to_rtic_macros_feature().to_owned()),
_ => None,
}
} else {
default_features
}
} }
// run example binary `example` // run example binary `example`
fn command_parser(command: &CargoCommand, overwrite: bool) -> anyhow::Result<()> { fn command_parser(
glob: &Globals,
command: &CargoCommand,
overwrite: bool,
) -> anyhow::Result<RunResult> {
let output_mode = if glob.stderr_inherited {
OutputMode::Inherited
} else {
OutputMode::PipedAndCollected
};
match *command { match *command {
CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => { CargoCommand::Qemu { example, .. } | CargoCommand::Run { example, .. } => {
let run_file = format!("{example}.run"); let run_file = format!("{example}.run");
@ -295,7 +314,7 @@ fn command_parser(command: &CargoCommand, overwrite: bool) -> anyhow::Result<()>
// cargo run <..> // cargo run <..>
info!("Running example: {example}"); info!("Running example: {example}");
let cargo_run_result = run_command(command)?; let cargo_run_result = run_command(command, output_mode)?;
info!("{}", cargo_run_result.stdout); info!("{}", cargo_run_result.stdout);
// Create a file for the expected output if it does not exist or mismatches // Create a file for the expected output if it does not exist or mismatches
@ -315,8 +334,9 @@ fn command_parser(command: &CargoCommand, overwrite: bool) -> anyhow::Result<()>
}; };
} else { } else {
run_successful(&cargo_run_result, &expected_output_file)?; run_successful(&cargo_run_result, &expected_output_file)?;
} };
Ok(())
Ok(cargo_run_result)
} }
CargoCommand::Format { .. } CargoCommand::Format { .. }
| CargoCommand::ExampleCheck { .. } | CargoCommand::ExampleCheck { .. }
@ -328,28 +348,8 @@ fn command_parser(command: &CargoCommand, overwrite: bool) -> anyhow::Result<()>
| CargoCommand::Test { .. } | CargoCommand::Test { .. }
| CargoCommand::Book { .. } | CargoCommand::Book { .. }
| CargoCommand::ExampleSize { .. } => { | CargoCommand::ExampleSize { .. } => {
let cargo_result = run_command(command)?; let cargo_result = run_command(command, output_mode)?;
if let Some(exit_code) = cargo_result.exit_status.code() { Ok(cargo_result)
if exit_code != exitcode::OK {
error!("Exit code from command: {exit_code}");
if !cargo_result.stdout.is_empty() {
info!("{}", cargo_result.stdout);
}
if !cargo_result.stderr.is_empty() {
error!("{}", cargo_result.stderr);
}
process::exit(exit_code);
} else {
if !cargo_result.stdout.is_empty() {
info!("{}", cargo_result.stdout);
}
if !cargo_result.stderr.is_empty() {
info!("{}", cargo_result.stderr);
}
}
}
Ok(())
} }
} }
} }