diff options
Diffstat (limited to 'xtask/src/cargo_command.rs')
| -rw-r--r-- | xtask/src/cargo_command.rs | 750 |
1 files changed, 750 insertions, 0 deletions
diff --git a/xtask/src/cargo_command.rs b/xtask/src/cargo_command.rs new file mode 100644 index 0000000..1d5f3c5 --- /dev/null +++ b/xtask/src/cargo_command.rs @@ -0,0 +1,750 @@ +use crate::{ExtraArguments, Target}; +use core::fmt; +use std::path::PathBuf; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BuildMode { + Release, + Debug, +} + +#[derive(Debug)] +pub enum CargoCommand<'a> { + // For future embedded-ci + #[allow(dead_code)] + Run { + cargoarg: &'a Option<&'a str>, + example: &'a str, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + dir: Option<PathBuf>, + }, + Qemu { + cargoarg: &'a Option<&'a str>, + example: &'a str, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + dir: Option<PathBuf>, + deny_warnings: bool, + }, + ExampleBuild { + cargoarg: &'a Option<&'a str>, + example: &'a str, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + dir: Option<PathBuf>, + deny_warnings: bool, + }, + ExampleCheck { + cargoarg: &'a Option<&'a str>, + example: &'a str, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + deny_warnings: bool, + }, + Build { + cargoarg: &'a Option<&'a str>, + package: Option<String>, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + dir: Option<PathBuf>, + deny_warnings: bool, + }, + Check { + cargoarg: &'a Option<&'a str>, + package: Option<String>, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + dir: Option<PathBuf>, + deny_warnings: bool, + }, + Clippy { + cargoarg: &'a Option<&'a str>, + package: Option<String>, + target: Option<Target<'a>>, + features: Option<String>, + deny_warnings: bool, + }, + Format { + cargoarg: &'a Option<&'a str>, + package: Option<String>, + check_only: bool, + }, + Doc { + cargoarg: &'a Option<&'a str>, + features: Option<String>, + arguments: Option<ExtraArguments>, + deny_warnings: bool, + }, + Test { + package: Option<String>, + features: Option<String>, + test: Option<String>, + deny_warnings: bool, + }, + Book { + arguments: Option<ExtraArguments>, + }, + ExampleSize { + cargoarg: &'a Option<&'a str>, + example: &'a str, + target: Option<Target<'a>>, + features: Option<String>, + mode: BuildMode, + arguments: Option<ExtraArguments>, + dir: Option<PathBuf>, + deny_warnings: bool, + }, +} + +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( + deny_warnings: bool, + 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!("<no explicit target>") + }; + + let mode = if let Some(mode) = mode { + format!("{mode}") + } else { + format!("debug") + }; + + let deny_warnings = if deny_warnings { + format!("deny warnings, ") + } else { + format!("") + }; + + if cargoarg.is_some() && path.is_some() { + format!("({deny_warnings}{target}, {mode}, {feat}, {carg}, {in_dir})") + } else if cargoarg.is_some() { + format!("({deny_warnings}{target}, {mode}, {feat}, {carg})") + } else if path.is_some() { + format!("({deny_warnings}{target}, {mode}, {feat}, {in_dir})") + } else { + format!("({deny_warnings}{target}, {mode}, {feat})") + } + } + + match self { + CargoCommand::Run { + cargoarg, + example, + target, + features, + mode, + dir, + } => { + write!( + f, + "Run example {example} {}", + details(false, target, Some(mode), features, cargoarg, dir.as_ref()) + ) + } + CargoCommand::Qemu { + cargoarg, + example, + target, + features, + mode, + dir, + deny_warnings, + } => { + 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 { + cargoarg, + example, + target, + features, + mode, + dir, + deny_warnings, + } => { + let warns = *deny_warnings; + let details = details(warns, target, Some(mode), features, cargoarg, dir.as_ref()); + write!(f, "Build example {example} {details}",) + } + CargoCommand::ExampleCheck { + cargoarg, + example, + target, + features, + mode, + deny_warnings, + } => write!( + f, + "Check example {example} {}", + details(*deny_warnings, target, Some(mode), features, cargoarg, None) + ), + CargoCommand::Build { + cargoarg, + package, + target, + features, + mode, + dir, + deny_warnings, + } => { + let package = p(package); + let warns = *deny_warnings; + write!( + f, + "Build {package} {}", + details(warns, target, Some(mode), features, cargoarg, dir.as_ref()) + ) + } + + CargoCommand::Check { + cargoarg, + package, + target, + features, + mode, + dir, + deny_warnings, + } => { + let package = p(package); + let warns = *deny_warnings; + write!( + f, + "Check {package} {}", + details(warns, target, Some(mode), features, cargoarg, dir.as_ref()) + ) + } + CargoCommand::Clippy { + cargoarg, + package, + target, + features, + deny_warnings, + } => { + let details = details(*deny_warnings, target, None, features, cargoarg, None); + let package = p(package); + write!(f, "Clippy {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, + deny_warnings, + } => { + let feat = feat(features); + let carg = carg(cargoarg); + let arguments = arguments + .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 ({deny_warnings}{feat}, {carg}, {arguments})") + } else { + write!(f, "Document ({deny_warnings}{feat}, {arguments})") + } + } + CargoCommand::Test { + 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} ({deny_warnings}features: {feat})") + } + CargoCommand::Book { arguments: _ } => write!(f, "Build the book"), + CargoCommand::ExampleSize { + cargoarg, + example, + target, + features, + mode, + arguments: _, + dir, + deny_warnings, + } => { + let warns = *deny_warnings; + let details = details(warns, target, Some(mode), features, cargoarg, dir.as_ref()); + write!(f, "Compute size of example {example} {details}") + } + } + } +} + +impl<'a> CargoCommand<'a> { + pub fn as_cmd_string(&self) -> String { + let env = if let Some((key, value)) = self.extra_env() { + format!("{key}=\"{value}\" ") + } else { + format!("") + }; + + let cd = if let Some(Some(chdir)) = self.chdir().map(|p| p.to_str()) { + format!("cd {chdir} && ") + } else { + format!("") + }; + + let executable = self.executable(); + let args = self.args().join(" "); + format!("{env}{cd}{executable} {args}") + } + + fn command(&self) -> &'static str { + match self { + CargoCommand::Run { .. } | CargoCommand::Qemu { .. } => "run", + CargoCommand::ExampleCheck { .. } | CargoCommand::Check { .. } => "check", + CargoCommand::ExampleBuild { .. } | CargoCommand::Build { .. } => "build", + CargoCommand::ExampleSize { .. } => "size", + CargoCommand::Clippy { .. } => "clippy", + CargoCommand::Format { .. } => "fmt", + CargoCommand::Doc { .. } => "doc", + CargoCommand::Book { .. } => "build", + CargoCommand::Test { .. } => "test", + } + } + pub fn executable(&self) -> &'static str { + match self { + CargoCommand::Run { .. } + | CargoCommand::Qemu { .. } + | CargoCommand::ExampleCheck { .. } + | CargoCommand::Check { .. } + | CargoCommand::ExampleBuild { .. } + | CargoCommand::Build { .. } + | CargoCommand::ExampleSize { .. } + | CargoCommand::Clippy { .. } + | CargoCommand::Format { .. } + | CargoCommand::Test { .. } + | CargoCommand::Doc { .. } => "cargo", + CargoCommand::Book { .. } => "mdbook", + } + } + + /// 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() + } + + 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() + } + } + + match self { + // For future embedded-ci, for now the same as Qemu + CargoCommand::Run { + 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::Qemu { + cargoarg, + example, + features, + mode, + // dir is exposed through `chdir` + dir: _, + // Target is added by build_args + target: _, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => self.build_args( + true, + cargoarg, + features, + Some(mode), + ["--example", example].into_iter(), + ), + CargoCommand::Build { + cargoarg, + package, + features, + mode, + // Target is added by build_args + target: _, + // Dir is exposed through `chdir` + dir: _, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => self.build_args(true, cargoarg, features, Some(mode), p(package)), + CargoCommand::Check { + cargoarg, + package, + features, + mode, + // Dir is exposed through `chdir` + dir: _, + // Target is added by build_args + target: _, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => self.build_args(true, cargoarg, features, Some(mode), p(package)), + CargoCommand::Clippy { + cargoarg, + package, + features, + // Target is added by build_args + target: _, + deny_warnings, + } => { + let deny_warnings = if *deny_warnings { + vec!["--", "-D", "warnings"] + } else { + vec![] + }; + + let extra = p(package).chain(deny_warnings); + self.build_args(true, cargoarg, features, None, extra) + } + CargoCommand::Doc { + 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) + } + CargoCommand::Test { + package, + features, + test, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => { + 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) + } + CargoCommand::Book { arguments } => { + 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"]); + } + args + } + 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), + ) + } + CargoCommand::ExampleBuild { + cargoarg, + example, + features, + mode, + // dir is exposed through `chdir` + dir: _, + // Target is added by build_args + target: _, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => 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: _, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => 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: _, + // deny_warnings is exposed through `extra_env` + deny_warnings: _, + } => { + 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. + 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, + } + } + + 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::Qemu { deny_warnings, .. } + | CargoCommand::ExampleBuild { deny_warnings, .. } + | CargoCommand::ExampleSize { deny_warnings, .. } => { + if *deny_warnings { + // NOTE: this also needs the link-arg because .cargo/config.toml + // is ignored if you set the RUSTFLAGS env variable. + Some(("RUSTFLAGS", "-D warnings -C link-arg=-Tlink.x")) + } else { + None + } + } + + CargoCommand::Check { deny_warnings, .. } + | CargoCommand::ExampleCheck { deny_warnings, .. } + | CargoCommand::Build { deny_warnings, .. } + | CargoCommand::Test { deny_warnings, .. } => { + if *deny_warnings { + Some(("RUSTFLAGS", "-D warnings")) + } else { + None + } + } + _ => None, + } + } + + pub fn print_stdout_intermediate(&self) -> bool { + match self { + Self::ExampleSize { .. } => true, + _ => false, + } + } +} + +impl BuildMode { + #[allow(clippy::wrong_self_convention)] + 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}") + } +} |
