aboutsummaryrefslogtreecommitdiff
path: root/xtask/src
diff options
context:
space:
mode:
authordatdenkikniet <jcdra1@gmail.com>2023-04-14 23:59:23 +0200
committerdatdenkikniet <jcdra1@gmail.com>2023-04-16 13:08:46 +0200
commit63b7024cb98717dd785ae888f419002b9835c6b1 (patch)
treeb65d1bc57d1f10c76cb053542095668b94246f09 /xtask/src
parentcba786529a7e34ed623df5f7cde5aae762a8c55c (diff)
xtask: build usage examples and general improvements
Diffstat (limited to 'xtask/src')
-rw-r--r--xtask/src/argument_parsing.rs53
-rw-r--r--xtask/src/cargo_commands.rs27
-rw-r--r--xtask/src/command.rs92
-rw-r--r--xtask/src/main.rs23
4 files changed, 177 insertions, 18 deletions
diff --git a/xtask/src/argument_parsing.rs b/xtask/src/argument_parsing.rs
index 3ee9e34..d74ba69 100644
--- a/xtask/src/argument_parsing.rs
+++ b/xtask/src/argument_parsing.rs
@@ -190,8 +190,8 @@ pub enum BuildOrCheck {
#[derive(Parser, Clone)]
pub struct Globals {
- /// For which backend to build (defaults to thumbv7)
- #[arg(value_enum, short, long, global = true)]
+ /// For which backend to build.
+ #[arg(value_enum, short, default_value = "thumbv7", long, global = true)]
pub backend: Option<Backends>,
/// List of comma separated examples to include, all others are excluded
@@ -300,6 +300,55 @@ pub enum Commands {
/// Build books with mdbook
Book(Arg),
+
+ /// Check one or more usage examples.
+ ///
+ /// Usage examples are located in ./examples
+ UsageExamplesCheck(UsageExamples),
+
+ /// Build one or more usage examples.
+ ///
+ /// Usage examples are located in ./examples
+ #[clap(alias = "./examples")]
+ UsageExampleBuild(UsageExamples),
+}
+
+#[derive(Args, Clone, Debug)]
+pub struct UsageExamples {
+ /// The usage examples to build. All usage examples are selected if this argument is not provided.
+ ///
+ /// Example: `rp2040_local_i2c_init,stm32f3_blinky`.
+ examples: Option<String>,
+}
+
+impl UsageExamples {
+ pub fn examples(&self) -> anyhow::Result<Vec<String>> {
+ let usage_examples: Vec<_> = std::fs::read_dir("./examples")?
+ .filter_map(Result::ok)
+ .filter(|p| p.metadata().ok().map(|p| p.is_dir()).unwrap_or(false))
+ .filter_map(|p| p.file_name().as_os_str().to_str().map(ToString::to_string))
+ .collect();
+
+ let selected_examples: Option<Vec<String>> = self
+ .examples
+ .clone()
+ .map(|s| s.split(",").map(ToString::to_string).collect());
+
+ if let Some(selected_examples) = selected_examples {
+ if let Some(unfound_example) = selected_examples
+ .iter()
+ .find(|e| !usage_examples.contains(e))
+ {
+ Err(anyhow::anyhow!(
+ "Usage example {unfound_example} does not exist"
+ ))
+ } else {
+ Ok(selected_examples)
+ }
+ } else {
+ Ok(usage_examples)
+ }
+ }
}
#[derive(Args, Debug, Clone)]
diff --git a/xtask/src/cargo_commands.rs b/xtask/src/cargo_commands.rs
index 9cbdaef..9b07088 100644
--- a/xtask/src/cargo_commands.rs
+++ b/xtask/src/cargo_commands.rs
@@ -126,6 +126,33 @@ pub fn cargo<'c>(
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<String>,
+) -> Vec<FinalRunResult<'_>> {
+ examples_iter(&usage_examples)
+ .map(|example| {
+ let path = format!("examples/{example}");
+
+ let command = match operation {
+ BuildOrCheck::Check => CargoCommand::CheckInDir {
+ mode: BuildMode::Release,
+ dir: path.into(),
+ },
+ BuildOrCheck::Build => CargoCommand::BuildInDir {
+ mode: BuildMode::Release,
+ dir: path.into(),
+ },
+ };
+ (globals, command, false)
+ })
+ .run_and_coalesce()
+}
+
/// Cargo command to either build or check all examples
///
/// The examples are in rtic/examples
diff --git a/xtask/src/command.rs b/xtask/src/command.rs
index b62724a..93de9cf 100644
--- a/xtask/src/command.rs
+++ b/xtask/src/command.rs
@@ -8,6 +8,7 @@ use core::fmt;
use std::{
fs::File,
io::Read,
+ path::PathBuf,
process::{Command, Stdio},
};
@@ -111,6 +112,14 @@ pub enum CargoCommand<'a> {
mode: BuildMode,
arguments: Option<ExtraArguments>,
},
+ CheckInDir {
+ mode: BuildMode,
+ dir: PathBuf,
+ },
+ BuildInDir {
+ mode: BuildMode,
+ dir: PathBuf,
+ },
}
impl core::fmt::Display for CargoCommand<'_> {
@@ -211,6 +220,10 @@ impl core::fmt::Display for CargoCommand<'_> {
details(target, mode, features, cargoarg)
)
}
+ CargoCommand::BuildInDir { mode, dir } => {
+ let dir = dir.as_os_str().to_str().unwrap_or("Not displayable");
+ write!(f, "Build {dir} ({mode})")
+ }
CargoCommand::Check {
cargoarg,
package,
@@ -225,6 +238,10 @@ impl core::fmt::Display for CargoCommand<'_> {
details(target, mode, features, cargoarg)
)
}
+ CargoCommand::CheckInDir { mode, dir } => {
+ let dir = dir.as_os_str().to_str().unwrap_or("Not displayable");
+ write!(f, "Check {dir} ({mode})")
+ }
CargoCommand::Clippy {
cargoarg,
package,
@@ -316,11 +333,15 @@ impl<'a> CargoCommand<'a> {
format!("{executable} {args}")
}
- fn command(&self) -> &str {
+ fn command(&self) -> &'static str {
match self {
CargoCommand::Run { .. } | CargoCommand::Qemu { .. } => "run",
- CargoCommand::ExampleCheck { .. } | CargoCommand::Check { .. } => "check",
- CargoCommand::ExampleBuild { .. } | CargoCommand::Build { .. } => "build",
+ CargoCommand::ExampleCheck { .. }
+ | CargoCommand::Check { .. }
+ | CargoCommand::CheckInDir { .. } => "check",
+ CargoCommand::ExampleBuild { .. }
+ | CargoCommand::Build { .. }
+ | CargoCommand::BuildInDir { .. } => "build",
CargoCommand::ExampleSize { .. } => "size",
CargoCommand::Clippy { .. } => "clippy",
CargoCommand::Format { .. } => "fmt",
@@ -329,7 +350,7 @@ impl<'a> CargoCommand<'a> {
CargoCommand::Test { .. } => "test",
}
}
- pub fn executable(&self) -> &str {
+ pub fn executable(&self) -> &'static str {
match self {
CargoCommand::Run { .. }
| CargoCommand::Qemu { .. }
@@ -341,7 +362,9 @@ impl<'a> CargoCommand<'a> {
| CargoCommand::Clippy { .. }
| CargoCommand::Format { .. }
| CargoCommand::Test { .. }
- | CargoCommand::Doc { .. } => "cargo",
+ | CargoCommand::Doc { .. }
+ | CargoCommand::CheckInDir { .. }
+ | CargoCommand::BuildInDir { .. } => "cargo",
CargoCommand::Book { .. } => "mdbook",
}
}
@@ -641,6 +664,34 @@ impl<'a> CargoCommand<'a> {
}
args
}
+ CargoCommand::CheckInDir { mode, dir: _ } => {
+ let mut args = vec!["+nightly"];
+ args.push(self.command());
+
+ if let Some(mode) = mode.to_flag() {
+ args.push(mode);
+ }
+
+ args
+ }
+ CargoCommand::BuildInDir { mode, dir: _ } => {
+ let mut args = vec!["+nightly", self.command()];
+
+ if let Some(mode) = mode.to_flag() {
+ args.push(mode);
+ }
+
+ args
+ }
+ }
+ }
+
+ fn chdir(&self) -> Option<&PathBuf> {
+ match self {
+ CargoCommand::CheckInDir { dir, .. } | CargoCommand::BuildInDir { dir, .. } => {
+ Some(dir)
+ }
+ _ => None,
}
}
}
@@ -669,11 +720,18 @@ impl fmt::Display for BuildMode {
pub fn run_command(command: &CargoCommand, stderr_mode: OutputMode) -> anyhow::Result<RunResult> {
log::info!("👟 {command}");
- let result = Command::new(command.executable())
+ let mut process = Command::new(command.executable());
+
+ process
.args(command.args())
.stdout(Stdio::piped())
- .stderr(stderr_mode)
- .output()?;
+ .stderr(stderr_mode);
+
+ if let Some(dir) = command.chdir() {
+ process.current_dir(dir);
+ }
+
+ let result = process.output()?;
let exit_status = result.status;
let stderr = String::from_utf8(result.stderr).unwrap_or("Not displayable".into());
@@ -759,15 +817,27 @@ pub fn handle_results(globals: &Globals, results: Vec<FinalRunResult>) -> anyhow
errors.clone().for_each(log_stdout_stderr(Level::Error));
successes.for_each(|(cmd, _)| {
+ let path = if let Some(dir) = cmd.chdir() {
+ let path = dir.as_os_str().to_str().unwrap_or("Not displayable");
+ format!(" (in {path}")
+ } else {
+ format!("")
+ };
+
if globals.verbose > 0 {
- info!("✅ Success: {cmd}\n {}", cmd.as_cmd_string());
+ info!("✅ Success:{path} {cmd}\n {}", cmd.as_cmd_string());
} else {
- info!("✅ Success: {cmd}");
+ info!("✅ Success:{path} {cmd}");
}
});
errors.clone().for_each(|(cmd, _)| {
- error!("❌ Failed: {cmd}\n {}", cmd.as_cmd_string());
+ if let Some(dir) = cmd.chdir() {
+ let path = dir.as_os_str().to_str().unwrap_or("Not displayable");
+ error!("❌ Failed: (in {path}) {cmd}\n {}", cmd.as_cmd_string());
+ } else {
+ error!("❌ Failed: {cmd}\n {}", cmd.as_cmd_string());
+ }
});
let ecount = errors.count();
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 2bfe851..2b45f23 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -23,10 +23,7 @@ use log::{error, info, log_enabled, trace, Level};
use crate::{
argument_parsing::{Backends, BuildOrCheck, Cli, Commands},
build::init_build_dir,
- cargo_commands::{
- build_and_check_size, cargo, cargo_book, cargo_clippy, cargo_doc, cargo_example,
- cargo_format, cargo_test, run_test,
- },
+ cargo_commands::*,
command::{handle_results, run_command, run_successful, CargoCommand},
};
@@ -152,6 +149,12 @@ fn main() -> anyhow::Result<()> {
trace!("default logging level: {0}", globals.verbose);
+ log::debug!(
+ "Stderr of child processes is inherited: {}",
+ globals.stderr_inherited
+ );
+ log::debug!("Partial features: {}", globals.partial);
+
let backend = if let Some(backend) = globals.backend {
backend
} else {
@@ -285,6 +288,14 @@ fn main() -> anyhow::Result<()> {
info!("Running mdbook");
cargo_book(globals, &args.arguments)
}
+ Commands::UsageExamplesCheck(examples) => {
+ info!("Checking usage examples");
+ cargo_usage_example(globals, BuildOrCheck::Check, examples.examples()?)
+ }
+ Commands::UsageExampleBuild(examples) => {
+ info!("Building usage examples");
+ cargo_usage_example(globals, BuildOrCheck::Build, examples.examples()?)
+ }
};
handle_results(globals, final_run_results)
@@ -347,7 +358,9 @@ fn command_parser(
| CargoCommand::Doc { .. }
| CargoCommand::Test { .. }
| CargoCommand::Book { .. }
- | CargoCommand::ExampleSize { .. } => {
+ | CargoCommand::ExampleSize { .. }
+ | CargoCommand::BuildInDir { .. }
+ | CargoCommand::CheckInDir { .. } => {
let cargo_result = run_command(command, output_mode)?;
Ok(cargo_result)
}