rtic/xtask/src/cargo_command.rs

663 lines
20 KiB
Rust
Raw Normal View History

2023-04-16 11:05:41 +02:00
use crate::{ExtraArguments, Target};
2021-08-26 10:58:59 +02:00
use core::fmt;
2023-04-16 11:05:41 +02:00
use std::path::PathBuf;
2021-08-26 10:58:59 +02:00
2021-09-22 13:22:45 +02:00
#[allow(dead_code)]
2021-08-26 10:58:59 +02:00
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BuildMode {
Release,
Debug,
}
2021-09-22 13:22:45 +02:00
#[derive(Debug)]
2021-08-26 10:58:59 +02:00
pub enum CargoCommand<'a> {
// For future embedded-ci
#[allow(dead_code)]
2021-08-26 10:58:59 +02:00
Run {
cargoarg: &'a Option<&'a str>,
example: &'a str,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
mode: BuildMode,
dir: Option<PathBuf>,
},
Qemu {
cargoarg: &'a Option<&'a str>,
2021-08-26 10:58:59 +02:00
example: &'a str,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
2021-08-26 10:58:59 +02:00
mode: BuildMode,
dir: Option<PathBuf>,
2021-08-26 10:58:59 +02:00
},
ExampleBuild {
cargoarg: &'a Option<&'a str>,
example: &'a str,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
2021-08-26 10:58:59 +02:00
mode: BuildMode,
dir: Option<PathBuf>,
2021-08-26 10:58:59 +02:00
},
ExampleCheck {
cargoarg: &'a Option<&'a str>,
example: &'a str,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
mode: BuildMode,
},
Build {
cargoarg: &'a Option<&'a str>,
2023-04-16 09:44:30 +02:00
package: Option<String>,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
mode: BuildMode,
dir: Option<PathBuf>,
},
Check {
cargoarg: &'a Option<&'a str>,
2023-04-16 09:44:30 +02:00
package: Option<String>,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
2023-02-24 23:14:11 +01:00
mode: BuildMode,
dir: Option<PathBuf>,
},
Clippy {
cargoarg: &'a Option<&'a str>,
2023-04-16 09:44:30 +02:00
package: Option<String>,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
deny_warnings: bool,
},
2023-02-24 00:10:01 +01:00
Format {
cargoarg: &'a Option<&'a str>,
2023-04-16 09:44:30 +02:00
package: Option<String>,
2023-02-24 00:10:01 +01:00
check_only: bool,
},
2023-02-24 22:56:36 +01:00
Doc {
cargoarg: &'a Option<&'a str>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
arguments: Option<ExtraArguments>,
2023-02-28 23:55:02 +01:00
},
Test {
2023-04-16 09:44:30 +02:00
package: Option<String>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
test: Option<String>,
2023-02-24 22:56:36 +01:00
},
2023-02-25 00:28:45 +01:00
Book {
arguments: Option<ExtraArguments>,
2023-02-25 00:28:45 +01:00
},
ExampleSize {
cargoarg: &'a Option<&'a str>,
example: &'a str,
target: Option<Target<'a>>,
2023-02-28 23:55:02 +01:00
features: Option<String>,
mode: BuildMode,
arguments: Option<ExtraArguments>,
dir: Option<PathBuf>,
},
2021-08-26 10:58:59 +02:00
}
impl core::fmt::Display for CargoCommand<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn p(p: &Option<String>) -> String {
if let Some(package) = p {
format!("package {package}")
} else {
format!("default package")
}
}
fn feat(f: &Option<String>) -> String {
if let Some(features) = f {
format!("\"{features}\"")
} else {
format!("no features")
}
}
fn carg(f: &&Option<&str>) -> String {
if let Some(cargoarg) = f {
format!("{cargoarg}")
} else {
format!("no cargo args")
}
}
fn details(
target: &Option<Target>,
mode: Option<&BuildMode>,
features: &Option<String>,
cargoarg: &&Option<&str>,
path: Option<&PathBuf>,
) -> String {
let feat = feat(features);
let carg = carg(cargoarg);
let in_dir = if let Some(path) = path {
let path = path.to_str().unwrap_or("<can't display>");
format!("in {path}")
} else {
format!("")
};
let target = if let Some(target) = target {
format!("{target}")
} else {
format!("<host target>")
};
let mode = if let Some(mode) = mode {
format!("{mode}")
} else {
format!("debug")
};
if cargoarg.is_some() && path.is_some() {
format!("({target}, {mode}, {feat}, {carg}, {in_dir})")
} else if cargoarg.is_some() {
format!("({target}, {mode}, {feat}, {carg})")
} else if path.is_some() {
format!("({target}, {mode}, {feat}, {in_dir})")
} else {
format!("({target}, {mode}, {feat})")
}
}
match self {
CargoCommand::Run {
cargoarg,
example,
target,
features,
mode,
dir,
} => {
write!(
f,
"Run example {example} {}",
details(target, Some(mode), features, cargoarg, dir.as_ref())
)
}
CargoCommand::Qemu {
cargoarg,
example,
target,
features,
mode,
dir,
} => {
let details = details(target, Some(mode), features, cargoarg, dir.as_ref());
write!(f, "Run example {example} in QEMU {details}",)
}
CargoCommand::ExampleBuild {
cargoarg,
example,
target,
features,
mode,
dir,
} => {
let details = details(target, Some(mode), features, cargoarg, dir.as_ref());
write!(f, "Build example {example} {details}",)
}
CargoCommand::ExampleCheck {
cargoarg,
example,
target,
features,
mode,
} => write!(
f,
"Check example {example} {}",
details(target, Some(mode), features, cargoarg, None)
),
CargoCommand::Build {
cargoarg,
package,
target,
features,
mode,
dir,
} => {
let package = p(package);
write!(
f,
"Build {package} {}",
details(target, Some(mode), features, cargoarg, dir.as_ref())
)
}
CargoCommand::Check {
cargoarg,
package,
target,
features,
mode,
dir,
} => {
let package = p(package);
write!(
f,
"Check {package} {}",
details(target, Some(mode), features, cargoarg, dir.as_ref())
)
}
CargoCommand::Clippy {
cargoarg,
package,
target,
features,
deny_warnings,
} => {
let details = details(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}")
}
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}"))
2023-04-15 15:16:43 +02:00
.unwrap_or_else(|| "no extra arguments".into());
if cargoarg.is_some() {
2023-04-15 15:16:43 +02:00
write!(f, "Document ({feat}, {carg}, {arguments})")
} else {
2023-04-15 15:16:43 +02:00
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: _,
dir,
} => {
let details = details(target, Some(mode), features, cargoarg, dir.as_ref());
write!(f, "Compute size of example {example} {details}")
}
}
}
}
2021-08-26 10:58:59 +02:00
impl<'a> CargoCommand<'a> {
2023-04-15 13:55:56 +02:00
pub fn as_cmd_string(&self) -> String {
let cd = if let Some(Some(chdir)) = self.chdir().map(|p| p.to_str()) {
format!("cd {chdir} && ")
} else {
format!("")
};
2023-04-15 13:55:56 +02:00
let executable = self.executable();
let args = self.args().join(" ");
format!("{cd}{executable} {args}")
2023-04-15 13:55:56 +02:00
}
fn command(&self) -> &'static str {
2021-08-26 10:58:59 +02:00
match self {
CargoCommand::Run { .. } | CargoCommand::Qemu { .. } => "run",
CargoCommand::ExampleCheck { .. } | CargoCommand::Check { .. } => "check",
CargoCommand::ExampleBuild { .. } | CargoCommand::Build { .. } => "build",
CargoCommand::ExampleSize { .. } => "size",
CargoCommand::Clippy { .. } => "clippy",
2023-02-24 00:10:01 +01:00
CargoCommand::Format { .. } => "fmt",
2023-02-24 22:56:36 +01:00
CargoCommand::Doc { .. } => "doc",
2023-02-25 00:28:45 +01:00
CargoCommand::Book { .. } => "build",
2023-02-28 23:55:02 +01:00
CargoCommand::Test { .. } => "test",
2023-02-25 00:28:45 +01:00
}
}
pub fn executable(&self) -> &'static str {
2023-02-25 00:28:45 +01:00
match self {
CargoCommand::Run { .. }
| CargoCommand::Qemu { .. }
| CargoCommand::ExampleCheck { .. }
| CargoCommand::Check { .. }
| CargoCommand::ExampleBuild { .. }
| CargoCommand::Build { .. }
| CargoCommand::ExampleSize { .. }
| CargoCommand::Clippy { .. }
| CargoCommand::Format { .. }
2023-02-28 23:55:02 +01:00
| CargoCommand::Test { .. }
| CargoCommand::Doc { .. } => "cargo",
2023-02-25 00:28:45 +01:00
CargoCommand::Book { .. } => "mdbook",
2021-08-26 10:58:59 +02:00
}
}
/// Build args using common arguments for all commands, and the
/// specific information provided
fn build_args<'i, T: Iterator<Item = &'i str>>(
&'i self,
nightly: bool,
cargoarg: &'i Option<&'i str>,
features: &'i Option<String>,
mode: Option<&'i BuildMode>,
extra: T,
) -> Vec<&str> {
let mut args: Vec<&str> = Vec::new();
if nightly {
args.push("+nightly");
}
if let Some(cargoarg) = cargoarg.as_deref() {
args.push(cargoarg);
}
args.push(self.command());
if let Some(target) = self.target() {
args.extend_from_slice(&["--target", target.triple()])
}
if let Some(features) = features.as_ref() {
args.extend_from_slice(&["--features", features]);
}
if let Some(mode) = mode.map(|m| m.to_flag()).flatten() {
args.push(mode);
}
args.extend(extra);
args
}
/// Turn the ExtraArguments into an interator that contains the separating dashes
/// and the rest of the arguments.
///
/// NOTE: you _must_ chain this iterator at the _end_ of the extra arguments.
fn extra_args(args: Option<&ExtraArguments>) -> impl Iterator<Item = &str> {
#[allow(irrefutable_let_patterns)]
let args = if let Some(ExtraArguments::Other(arguments)) = args {
// Extra arguments must be passed after "--"
["--"]
.into_iter()
.chain(arguments.iter().map(String::as_str))
.collect()
} else {
vec![]
};
args.into_iter()
}
2021-08-26 10:58:59 +02:00
pub fn args(&self) -> Vec<&str> {
fn p(package: &Option<String>) -> impl Iterator<Item = &str> {
if let Some(package) = package {
vec!["--package", &package].into_iter()
} else {
vec![].into_iter()
}
}
2021-08-26 10:58:59 +02:00
match self {
// For future embedded-ci, for now the same as Qemu
2021-08-26 10:58:59 +02:00
CargoCommand::Run {
cargoarg,
2021-08-26 10:58:59 +02:00
example,
features,
mode,
// dir is exposed through `chdir`
dir: _,
// Target is added by build_args
target: _,
} => self.build_args(
true,
cargoarg,
features,
Some(mode),
["--example", example].into_iter(),
),
CargoCommand::Qemu {
cargoarg,
example,
2021-08-26 10:58:59 +02:00
features,
2021-12-26 10:43:57 +01:00
mode,
// dir is exposed through `chdir`
dir: _,
// Target is added by build_args
target: _,
} => self.build_args(
true,
cargoarg,
features,
Some(mode),
["--example", example].into_iter(),
),
CargoCommand::Build {
cargoarg,
package,
features,
mode,
// Dir is exposed through `chdir`
dir: _,
// Target is added by build_args
target: _,
} => self.build_args(true, cargoarg, features, Some(mode), p(package)),
CargoCommand::Check {
cargoarg,
package,
features,
2023-02-24 23:14:11 +01:00
mode,
// Dir is exposed through `chdir`
dir: _,
// Target is added by build_args
target: _,
} => self.build_args(true, cargoarg, features, Some(mode), p(package)),
CargoCommand::Clippy {
cargoarg,
package,
features,
deny_warnings,
// Target is added by build_args
target: _,
} => {
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,
arguments,
} => {
let extra = Self::extra_args(arguments.as_ref());
self.build_args(true, cargoarg, features, None, extra)
2023-02-24 22:56:36 +01:00
}
2023-02-28 23:55:02 +01:00
CargoCommand::Test {
package,
features,
test,
} => {
let extra = if let Some(test) = test {
vec!["--test", test]
} else {
vec![]
};
let package = p(package);
let extra = extra.into_iter().chain(package);
self.build_args(true, &None, features, None, extra)
2023-02-28 23:55:02 +01:00
}
CargoCommand::Book { arguments } => {
2023-02-25 00:28:45 +01:00
let mut args = vec![];
if let Some(ExtraArguments::Other(arguments)) = arguments {
for arg in arguments {
args.extend_from_slice(&[arg.as_str()]);
}
} else {
// If no argument given, run mdbook build
// with default path to book
args.extend_from_slice(&[self.command()]);
args.extend_from_slice(&["book/en"]);
2023-02-25 00:28:45 +01:00
}
args
}
2023-02-24 00:10:01 +01:00
CargoCommand::Format {
cargoarg,
package,
check_only,
} => {
let extra = if *check_only { Some("--check") } else { None };
let package = p(package);
self.build_args(
true,
cargoarg,
&None,
None,
extra.into_iter().chain(package),
)
2023-02-24 00:10:01 +01:00
}
CargoCommand::ExampleBuild {
cargoarg,
example,
features,
mode,
// dir is exposed through `chdir`
dir: _,
// Target is added by build_args
target: _,
} => self.build_args(
true,
cargoarg,
features,
Some(mode),
["--example", example].into_iter(),
),
CargoCommand::ExampleCheck {
cargoarg,
example,
features,
mode,
// Target is added by build_args
target: _,
} => self.build_args(
true,
cargoarg,
features,
Some(mode),
["--example", example].into_iter(),
),
CargoCommand::ExampleSize {
cargoarg,
example,
features,
mode,
arguments,
// Target is added by build_args
target: _,
// dir is exposed through `chdir`
dir: _,
} => {
let extra = ["--example", example]
.into_iter()
.chain(Self::extra_args(arguments.as_ref()));
self.build_args(true, cargoarg, features, Some(mode), extra)
}
}
}
/// TODO: integrate this into `args` once `-C` becomes stable.
2023-04-16 11:05:41 +02:00
pub fn chdir(&self) -> Option<&PathBuf> {
match self {
CargoCommand::Qemu { dir, .. }
| CargoCommand::ExampleBuild { dir, .. }
| CargoCommand::ExampleSize { dir, .. }
| CargoCommand::Build { dir, .. }
| CargoCommand::Run { dir, .. }
| CargoCommand::Check { dir, .. } => dir.as_ref(),
_ => None,
}
}
fn target(&self) -> Option<&Target> {
match self {
CargoCommand::Run { target, .. }
| CargoCommand::Qemu { target, .. }
| CargoCommand::ExampleBuild { target, .. }
| CargoCommand::ExampleCheck { target, .. }
| CargoCommand::Build { target, .. }
| CargoCommand::Check { target, .. }
| CargoCommand::Clippy { target, .. }
| CargoCommand::ExampleSize { target, .. } => target.as_ref(),
_ => None,
2021-08-26 10:58:59 +02:00
}
}
pub fn print_stdout_intermediate(&self) -> bool {
match self {
Self::ExampleSize { .. } => true,
_ => false,
}
}
2021-08-26 10:58:59 +02:00
}
impl BuildMode {
#[allow(clippy::wrong_self_convention)]
2021-08-26 10:58:59 +02:00
pub fn to_flag(&self) -> Option<&str> {
match self {
BuildMode::Release => Some("--release"),
BuildMode::Debug => None,
}
}
}
impl fmt::Display for BuildMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let cmd = match self {
BuildMode::Release => "release",
BuildMode::Debug => "debug",
};
write!(f, "{cmd}")
2021-08-26 10:58:59 +02:00
}
}