diff --git a/Cargo.toml b/Cargo.toml index 142d01e..6a8f244 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ serde = { version = "1.0.139", features = ["derive"] } tee = "0.1.0" toml = "0.5.6" clap = { version = "4.0.15", features = ["derive", "wrap_help"] } +shlex = "1.1.0" diff --git a/src/command.rs b/src/command.rs index 7580419..3b91f61 100644 --- a/src/command.rs +++ b/src/command.rs @@ -18,6 +18,11 @@ pub enum Cargo { pub struct Input { #[command(subcommand)] pub cmd: CargoCmd, + + /// Print the exact commands `cargo-3ds` is running. Note that this does not + /// set the verbose flag for cargo itself. + #[arg(long, short = 'v')] + pub verbose: bool, } /// Run a cargo command. COMMAND will be forwarded to the real @@ -153,6 +158,10 @@ impl CargoCmd { "--doc".to_string(), "-Z".to_string(), "doctest-xcompile".to_string(), + // doctests don't automatically build the `test` crate, + // so we manually specify it on the command line + "-Z".to_string(), + "build-std=std,test".to_string(), ]); } else { cargo_args.push("--no-run".to_string()); @@ -278,7 +287,7 @@ impl CargoCmd { /// /// - `cargo 3ds build` and other "build" commands will use their callbacks to build the final `.3dsx` file and link it. /// - `cargo 3ds new` and other generic commands will use their callbacks to make 3ds-specific changes to the environment. - pub fn run_callback(&self, messages: &[Message]) { + pub fn run_callback(&self, messages: &[Message], verbose: bool) { // Process the metadata only for commands that have it/use it let config = if self.should_build_3dsx() { eprintln!("Getting metadata"); @@ -290,9 +299,9 @@ impl CargoCmd { // Run callback only for commands that use it match self { - Self::Build(cmd) => cmd.callback(&config), - Self::Run(cmd) => cmd.callback(&config), - Self::Test(cmd) => cmd.callback(&config), + Self::Build(cmd) => cmd.callback(&config, verbose), + Self::Run(cmd) => cmd.callback(&config, verbose), + Self::Test(cmd) => cmd.callback(&config, verbose), Self::New(cmd) => cmd.callback(), _ => (), } @@ -327,12 +336,12 @@ impl Build { /// Callback for `cargo 3ds build`. /// /// This callback handles building the application as a `.3dsx` file. - fn callback(&self, config: &CTRConfig) { - eprintln!("Building smdh:{}", config.path_smdh().display()); - build_smdh(config); + fn callback(&self, config: &CTRConfig, verbose: bool) { + eprintln!("Building smdh: {}", config.path_smdh().display()); + build_smdh(config, verbose); eprintln!("Building 3dsx: {}", config.path_3dsx().display()); - build_3dsx(config); + build_3dsx(config, verbose); } } @@ -381,12 +390,12 @@ impl Run { /// Callback for `cargo 3ds run`. /// /// This callback handles launching the application via `3dslink`. - fn callback(&self, config: &CTRConfig) { + fn callback(&self, config: &CTRConfig, verbose: bool) { // Run the normal "build" callback - self.build_args.callback(config); + self.build_args.callback(config, verbose); eprintln!("Running 3dslink"); - link(config, self); + link(config, self, verbose); } } @@ -394,13 +403,13 @@ impl Test { /// Callback for `cargo 3ds test`. /// /// This callback handles launching the application via `3dslink`. - fn callback(&self, config: &CTRConfig) { + fn callback(&self, config: &CTRConfig, verbose: bool) { if self.no_run { // If the tests don't have to run, use the "build" callback - self.run_args.build_args.callback(config) + self.run_args.build_args.callback(config, verbose) } else { // If the tests have to run, use the "run" callback - self.run_args.callback(config) + self.run_args.callback(config, verbose) } } } diff --git a/src/lib.rs b/src/lib.rs index d376b6d..5d9f7cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ pub mod command; use crate::command::{CargoCmd, Run}; use cargo_metadata::{Message, MetadataCommand}; -use command::Test; +use command::{Input, Test}; use rustc_version::Channel; use semver::Version; use serde::Deserialize; @@ -20,8 +20,12 @@ use std::{env, io, process}; /// /// For commands that produce an executable output, this function will build the /// `.elf` binary that can be used to create other 3ds files. -pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, Vec) { - let mut command = make_cargo_command(cmd, &message_format); +pub fn run_cargo(input: &Input, message_format: Option) -> (ExitStatus, Vec) { + let mut command = make_cargo_command(&input.cmd, &message_format); + + if input.verbose { + print_command(&command); + } let mut process = command.spawn().unwrap(); let command_stdout = process.stdout.take().unwrap(); @@ -29,7 +33,7 @@ pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, let mut tee_reader; let mut stdout_reader; - let buf_reader: &mut dyn BufRead = match (message_format, cmd) { + let buf_reader: &mut dyn BufRead = match (message_format, &input.cmd) { // The user presumably cares about the message format if set, so we should // copy stuff to stdout like they expect. We can still extract the executable // information out of it that we need for 3dsxtool etc. @@ -112,6 +116,23 @@ pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option) -> Co command } +fn print_command(command: &Command) { + let mut cmd_str = vec![command.get_program().to_string_lossy().to_string()]; + cmd_str.extend(command.get_args().map(|s| s.to_string_lossy().to_string())); + + eprintln!("Running command:"); + for (k, v) in command.get_envs() { + let v = v.map(|v| v.to_string_lossy().to_string()); + eprintln!( + " {}={} \\", + k.to_string_lossy(), + v.map_or_else(String::new, |s| shlex::quote(&s).to_string()) + ); + } + eprintln!(" {}", shlex::join(cmd_str.iter().map(String::as_str))); + eprintln!(); +} + /// Finds the sysroot path of the current toolchain pub fn find_sysroot() -> PathBuf { let sysroot = env::var("SYSROOT").ok().unwrap_or_else(|| { @@ -235,8 +256,9 @@ pub fn get_metadata(messages: &[Message]) -> CTRConfig { /// Builds the smdh using `smdhtool`. /// This will fail if `smdhtool` is not within the running directory or in a directory found in $PATH -pub fn build_smdh(config: &CTRConfig) { - let mut process = Command::new("smdhtool") +pub fn build_smdh(config: &CTRConfig, verbose: bool) { + let mut command = Command::new("smdhtool"); + command .arg("--create") .arg(&config.name) .arg(&config.description) @@ -245,7 +267,13 @@ pub fn build_smdh(config: &CTRConfig) { .arg(config.path_smdh()) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) + .stderr(Stdio::inherit()); + + if verbose { + print_command(&command); + } + + let mut process = command .spawn() .expect("smdhtool command failed, most likely due to 'smdhtool' not being in $PATH"); @@ -258,9 +286,9 @@ pub fn build_smdh(config: &CTRConfig) { /// Builds the 3dsx using `3dsxtool`. /// This will fail if `3dsxtool` is not within the running directory or in a directory found in $PATH -pub fn build_3dsx(config: &CTRConfig) { +pub fn build_3dsx(config: &CTRConfig, verbose: bool) { let mut command = Command::new("3dsxtool"); - let mut process = command + command .arg(&config.target_path) .arg(config.path_3dsx()) .arg(format!("--smdh={}", config.path_smdh().to_string_lossy())); @@ -269,7 +297,7 @@ pub fn build_3dsx(config: &CTRConfig) { let (romfs_path, is_default_romfs) = get_romfs_path(config); if romfs_path.is_dir() { eprintln!("Adding RomFS from {}", romfs_path.display()); - process = process.arg(format!("--romfs={}", romfs_path.to_string_lossy())); + command.arg(format!("--romfs={}", romfs_path.to_string_lossy())); } else if !is_default_romfs { eprintln!( "Could not find configured RomFS dir: {}", @@ -278,7 +306,11 @@ pub fn build_3dsx(config: &CTRConfig) { process::exit(1); } - let mut process = process + if verbose { + print_command(&command); + } + + let mut process = command .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) @@ -294,17 +326,20 @@ pub fn build_3dsx(config: &CTRConfig) { /// Link the generated 3dsx to a 3ds to execute and test using `3dslink`. /// This will fail if `3dslink` is not within the running directory or in a directory found in $PATH -pub fn link(config: &CTRConfig, run_args: &Run) { - let mut process = Command::new("3dslink") +pub fn link(config: &CTRConfig, run_args: &Run, verbose: bool) { + let mut command = Command::new("3dslink"); + command .arg(config.path_3dsx()) .args(run_args.get_3dslink_args()) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn() - .unwrap(); + .stderr(Stdio::inherit()); - let status = process.wait().unwrap(); + if verbose { + print_command(&command); + } + + let status = command.spawn().unwrap().wait().unwrap(); if !status.success() { process::exit(status.code().unwrap_or(1)); diff --git a/src/main.rs b/src/main.rs index bac5948..13b1d14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,11 +18,11 @@ fn main() { } }; - let (status, messages) = run_cargo(&input.cmd, message_format); + let (status, messages) = run_cargo(&input, message_format); if !status.success() { process::exit(status.code().unwrap_or(1)); } - input.cmd.run_callback(&messages); + input.cmd.run_callback(&messages, input.verbose); }