diff --git a/src/command.rs b/src/command.rs index 26b9a9d..484d9c8 100644 --- a/src/command.rs +++ b/src/command.rs @@ -129,6 +129,45 @@ pub struct New { } impl CargoCmd { + /// Returns the additional arguments run by the "official" cargo subcommand. + pub fn cargo_args(&self) -> Vec { + match self { + CargoCmd::Build(build) => build.cargo_args.cargo_args(), + CargoCmd::Run(run) => run.build_args.cargo_args.cargo_args(), + CargoCmd::Test(test) => { + // We can't run 3DS executables on the host, so pass "--no-run" here and + // send the executable with 3dslink later, if the user wants + let mut cargo_args = test.run_args.build_args.cargo_args.cargo_args(); + cargo_args.push("--no-run".to_string()); + + cargo_args + } + CargoCmd::New(new) => { + // We push the original path in the new command (we captured it in [`New`] to learn about the context) + let mut cargo_args = new.cargo_args.cargo_args(); + cargo_args.push(new.path.clone()); + + cargo_args + } + CargoCmd::Passthrough(other) => other.clone().split_off(1), + } + } + + /// Returns the cargo subcommand run by `cargo-3ds` when handling a [`CargoCmd`]. + /// + /// # Notes + /// + /// This is not equivalent to the lowercase name of the [`CargoCmd`] variant. + /// Commands may use different commands under the hood to function (e.g. [`CargoCmd::Run`] uses `build`). + pub fn subcommand_name(&self) -> &str { + match self { + CargoCmd::Build(_) | CargoCmd::Run(_) => "build", + CargoCmd::Test(_) => "test", + CargoCmd::New(_) => "new", + CargoCmd::Passthrough(cmd) => &cmd[0], + } + } + /// Whether or not this command should compile any code, and thus needs import the custom environment configuration (e.g. target spec). pub fn should_compile(&self) -> bool { matches!( @@ -221,6 +260,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]) { + // Process the metadata only for commands that have it/use it let config = if self.should_build_3dsx() { eprintln!("Getting metadata"); @@ -242,20 +282,24 @@ impl CargoCmd { impl RemainingArgs { /// Get the args to be passed to the executable itself (not `cargo`). - pub fn cargo_args(&self) -> &[String] { + pub fn cargo_args(&self) -> Vec { self.split_args().0 } /// Get the args to be passed to the executable itself (not `cargo`). - pub fn exe_args(&self) -> &[String] { + pub fn exe_args(&self) -> Vec { self.split_args().1 } - fn split_args(&self) -> (&[String], &[String]) { - if let Some(split) = self.args.iter().position(|s| s == "--") { - self.args.split_at(split + 1) + fn split_args(&self) -> (Vec, Vec) { + let mut args = self.args.clone(); + + if let Some(split) = args.iter().position(|s| s == "--") { + let second_half = args.split_off(split + 1); + + (args, second_half) } else { - (&self.args[..], &[]) + (args, Vec::new()) } } } diff --git a/src/lib.rs b/src/lib.rs index 156e01f..acdb129 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,11 +21,7 @@ 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 = if cmd.should_compile() { - make_cargo_build_command(cmd, &message_format) - } else { - make_cargo_generic_command(cmd) - }; + let mut command = make_cargo_command(cmd, &message_format); let mut process = command.spawn().unwrap(); let command_stdout = process.stdout.take().unwrap(); @@ -58,98 +54,37 @@ pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, (process.wait().unwrap(), messages) } -/// Create a cargo command used for building based on the context. -/// If there is no pre-built std detected in the sysroot, `build-std` is used. -pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) -> Command { +/// Create a cargo command based on the context. +/// +/// For "build" commands (which compile code, such as `cargo 3ds build` or `cargo 3ds clippy`), +/// if there is no pre-built std detected in the sysroot, `build-std` will be used instead. +pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option) -> Command { let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); - let sysroot = find_sysroot(); let mut command = Command::new(cargo); - let cmd_str = match cmd { - CargoCmd::Build(_) | CargoCmd::Run(_) => "build", - CargoCmd::Test(_) => "test", - CargoCmd::Passthrough(cmd) => &cmd[0], - _ => panic!("tried to build an executable using an unsupported cargo subcommand"), - }; - - command - .arg(cmd_str) - .arg("--target") - .arg("armv6k-nintendo-3ds") - .arg("--message-format") - .arg( - message_format - .as_deref() - .unwrap_or(CargoCmd::DEFAULT_MESSAGE_FORMAT), - ); - - if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { - eprintln!("No pre-built std found, using build-std"); - command.arg("-Z").arg("build-std"); - } - - let cargo_args = match cmd { - CargoCmd::Build(build) => build.cargo_args.cargo_args(), - CargoCmd::Run(run) => run.build_args.cargo_args.cargo_args(), - CargoCmd::Test(test) => { - // We can't run 3DS executables on the host, so unconditionally pass - // --no-run here and send the executable with 3dslink later, if the - // user wants - - if test.doc { - eprintln!("Documentation tests requested, no 3dsx will be built or run"); - - // https://github.com/rust-lang/cargo/issues/7040 - command.args(["--doc", "-Z", "doctest-xcompile"]); - - // Cargo doesn't like --no-run for doctests: - // https://github.com/rust-lang/rust/issues/87022 - let rustdoc_flags = std::env::var("RUSTDOCFLAGS").unwrap_or_default() - // TODO: should we make this output directory depend on profile etc? - + " --no-run --persist-doctests target/doctests"; - - command.env("RUSTDOCFLAGS", rustdoc_flags); - } else { - command.arg("--no-run"); - } - - test.run_args.cargo_args.cargo_args() + command.arg(cmd.subcommand_name()); + + // Any command that needs to compile code will run under this environment. + // Even `clippy` and `check` need this kind of context, so we'll just assume any other `Passthrough` command uses it too. + if cmd.should_compile() { + command + .arg("--target") + .arg("armv6k-nintendo-3ds") + .arg("--message-format") + .arg( + message_format + .as_deref() + .unwrap_or(CargoCmd::DEFAULT_MESSAGE_FORMAT), + ); + + let sysroot = find_sysroot(); + if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { + eprintln!("No pre-build std found, using build-std"); + command.arg("-Z").arg("build-std"); } - CargoCmd::Passthrough(other) => &other[1..], - _ => panic!("tried to build an executable using an unsupported cargo subcommand"), - }; - - command - .args(cargo_args) - .stdout(Stdio::piped()) - .stdin(Stdio::inherit()) - .stderr(Stdio::inherit()); - - command -} - -/// Create a cargo command used for generic purposes based on the context. -pub fn make_cargo_generic_command(cmd: &CargoCmd) -> Command { - let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); - let mut command = Command::new(cargo); - - let cmd_str = match cmd { - CargoCmd::New(_) => "new", - _ => panic!("tried to run an unsupported generic cargo subcommand"), - }; - - command.arg(cmd_str); - - let cargo_args = match cmd { - CargoCmd::New(new) => { - println!("{}", new.path); - - command.arg(&new.path); + } - new.cargo_args.cargo_args() - } - _ => panic!("tried to build an executable using an unsupported cargo subcommand"), - }; + let cargo_args = cmd.cargo_args(); command .args(cargo_args)