From cef69644e655b1577bdc59845ae924c77b56c4fc Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 15 Oct 2022 20:34:09 -0400 Subject: [PATCH 1/6] Upgrade to clap4, add extra args and help strings --- Cargo.toml | 4 +- src/command.rs | 133 ++++++++++++++++++++++++++++++++++++++++--------- src/main.rs | 49 ++++++++++-------- 3 files changed, 140 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fbe3b4a..23ebc65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" cargo_metadata = "0.14.0" rustc_version = "0.4.0" semver = "1.0.10" -serde = {version = "1.0.139", features = ['derive']} +serde = { version = "1.0.139", features = ['derive'] } tee = "0.1.0" toml = "0.5.6" -clap = { version = "3.2.12", features = ["derive"]} \ No newline at end of file +clap = { version = "4.0.15", features = ["derive", "wrap_help"] } diff --git a/src/command.rs b/src/command.rs index 54f9eef..df8f989 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,36 +1,123 @@ -use clap::{AppSettings, Args, Parser, ValueEnum}; - -#[derive(Parser)] -#[clap(name = "cargo")] -#[clap(bin_name = "cargo")] -pub enum Cargo { - #[clap(name = "3ds")] - Input(Input), +// TODO: docstrings for everything!!! + +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(name = "cargo")] +#[command(bin_name = "cargo")] +pub enum Command { + #[command(name = "3ds")] + Root(Root), } -#[derive(Args)] -#[clap(about)] -#[clap(global_setting(AppSettings::AllowLeadingHyphen))] -pub struct Input { - #[clap(value_enum)] +#[derive(Args, Debug)] +#[command(version, about)] +pub struct Root { + /// The cargo command to run. This command will be forwarded to the real + /// `cargo` with the appropriate arguments for a 3DS executable. + #[command(subcommand)] pub cmd: CargoCommand, - pub cargo_opts: Vec, + + /// Don't actually run any commands, just echo them to the console. + /// This is mostly intended for testing. + #[arg(hide = true)] + pub dry_run: bool, + + /// Pass additional options through to the `cargo` command. + /// + /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` + /// options from cargo options. All args after `--` will be passed through + /// to cargo unmodified. + /// + /// If one of the arguments is itself `--`, the args following that will be + /// considered as args to pass to the executable, rather than to `cargo` + /// (only works for the `run` or `test` commands). For example, if you want + /// to pass some args to the executable directly it might look like this: + /// + /// > cargo 3ds run -- -- arg1 arg2 + #[arg(trailing_var_arg = true)] + #[arg(global = true)] + cargo_options: Vec, } -#[derive(ValueEnum, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Subcommand, Debug)] pub enum CargoCommand { + /// Builds an executable suitable to run on a 3DS (3dsx). Build, - Run, - Test, + /// Equivalent to `cargo check`. Check, + /// Equivalent to `cargo clippy`. Clippy, + /// Equivalent to `cargo doc`. + Doc, + /// Builds an executable and sends it to a device with `3dslink`. + Run(Run), + /// Builds a test executable and sends it to a device with `3dslink`. + /// + /// This can be used with `--test` for integration tests, or `--lib` for + /// unit tests (which require a custom test runner). + Test(Test), + // TODO: this doesn't seem to work for some reason... + #[command(external_subcommand)] + Other(Vec), } -impl CargoCommand { - pub fn should_build_3dsx(&self) -> bool { - matches!( - self, - CargoCommand::Build | CargoCommand::Run | CargoCommand::Test - ) +#[derive(Args, Debug)] +pub struct Test { + /// If set, the built executable will not be sent to the device to run it. + #[arg(long)] + pub no_run: bool, + #[command(flatten)] + pub run_args: Run, +} + +#[derive(Args, Debug)] +pub struct Run { + /// Specify the IP address of the device to send the executable to. + /// + /// Corresponds to 3dslink's `--address` arg, which defaults to automatically + /// finding the device. + #[arg(long, short = 'a')] + pub address: Option, + + /// Set the 0th argument of the executable when running it. Corresponds to + /// 3dslink's `--argv0` argument. + #[arg(long, short = '0')] + pub argv0: Option, + + /// Start the 3dslink server after sending the executable. Corresponds to + /// 3dslink's `--server` argument. + #[arg(long, short = 's')] + pub server: bool, + + /// Set the number of tries when connecting to the device to send the executable. + /// Corresponds to 3dslink's `--retries` argument. + // Can't use `short = 'r'` because that would conflict with cargo's `--release/-r` + #[arg(long)] + pub retries: Option, +} + +impl Root { + /// Get the args to be passed to the executable itself (not `cargo`). + pub fn cargo_options(&self) -> &[String] { + self.split_args().0 + } + + /// Get the args to be passed to the executable itself (not `cargo`). + pub fn executable_args(&self) -> &[String] { + self.split_args().1 + } + + fn split_args(&self) -> (&[String], &[String]) { + if let Some(split) = self.cargo_options.iter().position(|arg| arg == "--") { + let split = if &self.cargo_options[split] == "--" { + split + 1 + } else { + split + }; + self.cargo_options.split_at(split) + } else { + (&self.cargo_options[..], &[]) + } } } diff --git a/src/main.rs b/src/main.rs index 3edd05a..614f1d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use cargo_3ds::cmd_clap4::Command; use cargo_3ds::command::Cargo; use cargo_3ds::{ build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata, @@ -7,34 +8,40 @@ use clap::Parser; use std::process; fn main() { - check_rust_version(); + let Command::Root(cmd) = cargo_3ds::cmd_clap4::Command::parse(); - let Cargo::Input(mut input) = Cargo::parse(); + dbg!(&cmd); + dbg!(cmd.cargo_options()); + dbg!(cmd.executable_args()); - let should_link = get_should_link(&mut input); - let message_format = get_message_format(&mut input); + // check_rust_version(); - let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts); + // let Cargo::Input(mut input) = Cargo::parse(); - if !status.success() { - process::exit(status.code().unwrap_or(1)); - } + // let should_link = get_should_link(&mut input); + // let message_format = get_message_format(&mut input); - if !input.cmd.should_build_3dsx() { - return; - } + // let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts); - eprintln!("Getting metadata"); - let app_conf = get_metadata(&messages); + // if !status.success() { + // process::exit(status.code().unwrap_or(1)); + // } - eprintln!("Building smdh:{}", app_conf.path_smdh().display()); - build_smdh(&app_conf); + // if !input.cmd.should_build_3dsx() { + // return; + // } - eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display()); - build_3dsx(&app_conf); + // eprintln!("Getting metadata"); + // let app_conf = get_metadata(&messages); - if should_link { - eprintln!("Running 3dslink"); - link(&app_conf); - } + // eprintln!("Building smdh:{}", app_conf.path_smdh().display()); + // build_smdh(&app_conf); + + // eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display()); + // build_3dsx(&app_conf); + + // if should_link { + // eprintln!("Running 3dslink"); + // link(&app_conf); + // } } From c2560622e86cdfcb8f56155dfa11e901e0ffc6cd Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sat, 15 Oct 2022 20:46:28 -0400 Subject: [PATCH 2/6] Fix some build errors for testing --- src/command.rs | 19 ++++++++++--------- src/lib.rs | 27 ++++++++++++++------------- src/main.rs | 13 +++++-------- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/command.rs b/src/command.rs index df8f989..3bd79fa 100644 --- a/src/command.rs +++ b/src/command.rs @@ -5,14 +5,14 @@ use clap::{Args, Parser, Subcommand}; #[derive(Parser, Debug)] #[command(name = "cargo")] #[command(bin_name = "cargo")] -pub enum Command { +pub enum Cargo { #[command(name = "3ds")] - Root(Root), + Input(Input), } #[derive(Args, Debug)] #[command(version, about)] -pub struct Root { +pub struct Input { /// The cargo command to run. This command will be forwarded to the real /// `cargo` with the appropriate arguments for a 3DS executable. #[command(subcommand)] @@ -20,7 +20,7 @@ pub struct Root { /// Don't actually run any commands, just echo them to the console. /// This is mostly intended for testing. - #[arg(hide = true)] + #[arg(long, hide = true)] pub dry_run: bool, /// Pass additional options through to the `cargo` command. @@ -57,9 +57,10 @@ pub enum CargoCommand { /// This can be used with `--test` for integration tests, or `--lib` for /// unit tests (which require a custom test runner). Test(Test), + // // TODO: this doesn't seem to work for some reason... - #[command(external_subcommand)] - Other(Vec), + // #[command(external_subcommand)] + // Other(Vec), } #[derive(Args, Debug)] @@ -97,14 +98,14 @@ pub struct Run { pub retries: Option, } -impl Root { +impl Input { /// Get the args to be passed to the executable itself (not `cargo`). - pub fn cargo_options(&self) -> &[String] { + pub fn cargo_opts(&self) -> &[String] { self.split_args().0 } /// Get the args to be passed to the executable itself (not `cargo`). - pub fn executable_args(&self) -> &[String] { + pub fn exe_args(&self) -> &[String] { self.split_args().1 } diff --git a/src/lib.rs b/src/lib.rs index 9d43156..1fcc0ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,9 +22,9 @@ pub fn get_should_link(input: &mut Input) -> bool { // When running compile only commands, don't link the executable to the 3ds. // Otherwise, link and run on the 3ds but do not run locally. match input.cmd { - CargoCommand::Run => true, - CargoCommand::Test if !input.cargo_opts.contains(&"--no-run".to_string()) => { - input.cargo_opts.push("--no-run".to_string()); + CargoCommand::Run(_) => true, + CargoCommand::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { + // input.cargo_opts().push("--no-run".to_string()); true } _ => false, @@ -36,12 +36,12 @@ pub fn get_should_link(input: &mut Input) -> bool { pub fn get_message_format(input: &mut Input) -> String { // Checks for a position within the args where '--message-format' is located if let Some(pos) = input - .cargo_opts + .cargo_opts() .iter() .position(|s| s.starts_with("--message-format")) { // Remove the arg from list - let arg = input.cargo_opts.remove(pos); + let arg = input.cargo_opts()[pos].clone(); // TODO // Allows for usage of '--message-format=' and also using space separation. // Check for a '=' delimiter and use the second half of the split as the format, @@ -49,15 +49,15 @@ pub fn get_message_format(input: &mut Input) -> String { let format = if let Some((_, format)) = arg.split_once('=') { format.to_string() } else { - input.cargo_opts.remove(pos) + input.cargo_opts()[pos].clone() // TODO }; // Non-json formats are not supported so the executable exits. - if !format.starts_with("json") { + if format.starts_with("json") { + format + } else { eprintln!("error: non-JSON `message-format` is not supported"); process::exit(1); - } else { - format } } else { // Default to 'json-render-diagnostics' @@ -114,10 +114,11 @@ pub fn make_cargo_build_command( let mut command = Command::new(cargo); let cmd = match cmd { - CargoCommand::Build | CargoCommand::Run => "build", - CargoCommand::Test => "test", + CargoCommand::Build | CargoCommand::Run(_) => "build", + CargoCommand::Test(_) => "test", CargoCommand::Check => "check", CargoCommand::Clippy => "clippy", + CargoCommand::Doc => "doc", }; command @@ -200,7 +201,7 @@ pub fn check_rust_version() { /// Parses messages returned by the executed cargo command from [`build_elf`]. /// The returned [`CTRConfig`] is then used for further building in and execution -/// in [`build_smdh`], ['build_3dsx'], and [`link`]. +/// in [`build_smdh`], [`build_3dsx`], and [`link`]. pub fn get_metadata(messages: &[Message]) -> CTRConfig { let metadata = MetadataCommand::new() .exec() @@ -249,7 +250,7 @@ pub fn get_metadata(messages: &[Message]) -> CTRConfig { }; let author = match package.authors.as_slice() { - [name, ..] => name.to_owned(), + [name, ..] => name.clone(), [] => String::from("Unspecified Author"), // as standard with the devkitPRO toolchain }; diff --git a/src/main.rs b/src/main.rs index 614f1d6..dd5501b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -use cargo_3ds::cmd_clap4::Command; use cargo_3ds::command::Cargo; use cargo_3ds::{ build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata, @@ -8,15 +7,13 @@ use clap::Parser; use std::process; fn main() { - let Command::Root(cmd) = cargo_3ds::cmd_clap4::Command::parse(); + check_rust_version(); - dbg!(&cmd); - dbg!(cmd.cargo_options()); - dbg!(cmd.executable_args()); + let Cargo::Input(mut input) = Cargo::parse(); - // check_rust_version(); - - // let Cargo::Input(mut input) = Cargo::parse(); + dbg!(&input); + dbg!(input.cargo_opts()); + dbg!(input.exe_args()); // let should_link = get_should_link(&mut input); // let message_format = get_message_format(&mut input); From 583fc67fc6feb48499cfe7f2ce710236e152ceb9 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 30 Oct 2022 14:02:33 -0400 Subject: [PATCH 3/6] Update docstrings and arg parsing Also add the most basic `debug_assert` test. --- src/command.rs | 112 +++++++++++++++++++++++++++++-------------------- src/lib.rs | 83 ++++++++++++++++++------------------ src/main.rs | 21 +++++++--- 3 files changed, 123 insertions(+), 93 deletions(-) diff --git a/src/command.rs b/src/command.rs index 3bd79fa..00972d3 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,10 +1,7 @@ -// TODO: docstrings for everything!!! - use clap::{Args, Parser, Subcommand}; #[derive(Parser, Debug)] -#[command(name = "cargo")] -#[command(bin_name = "cargo")] +#[command(name = "cargo", bin_name = "cargo")] pub enum Cargo { #[command(name = "3ds")] Input(Input), @@ -13,20 +10,44 @@ pub enum Cargo { #[derive(Args, Debug)] #[command(version, about)] pub struct Input { - /// The cargo command to run. This command will be forwarded to the real - /// `cargo` with the appropriate arguments for a 3DS executable. #[command(subcommand)] - pub cmd: CargoCommand, + pub cmd: CargoCmd, +} + +/// The cargo command to run. This command will be forwarded to the real +/// `cargo` with the appropriate arguments for a 3DS executable. +/// +/// If another command is passed which is not recognized, it will be passed +/// through unmodified to `cargo` with RUSTFLAGS set for the 3DS. +#[derive(Subcommand, Debug)] +#[command(allow_external_subcommands = true)] +pub enum CargoCmd { + /// Builds an executable suitable to run on a 3DS (3dsx). + Build(CargoArgs), + + /// Builds an executable and sends it to a device with `3dslink`. + Run(Run), + + /// Builds a test executable and sends it to a device with `3dslink`. + /// + /// This can be used with `--test` for integration tests, or `--lib` for + /// unit tests (which require a custom test runner). + Test(Test), - /// Don't actually run any commands, just echo them to the console. - /// This is mostly intended for testing. - #[arg(long, hide = true)] - pub dry_run: bool, + // NOTE: it seems docstring + name for external subcommands are not rendered + // in help, but we might as well set them here in case a future version of clap + // does include them in help text. + /// Run any other `cargo` command with RUSTFLAGS set for the 3DS. + #[command(external_subcommand, name = "COMMAND")] + Passthrough(Vec), +} +#[derive(Args, Debug)] +pub struct CargoArgs { /// Pass additional options through to the `cargo` command. /// /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` - /// options from cargo options. All args after `--` will be passed through + /// options from cargo options. Any argument after `--` will be passed through /// to cargo unmodified. /// /// If one of the arguments is itself `--`, the args following that will be @@ -36,50 +57,31 @@ pub struct Input { /// /// > cargo 3ds run -- -- arg1 arg2 #[arg(trailing_var_arg = true)] + #[arg(allow_hyphen_values = true)] #[arg(global = true)] - cargo_options: Vec, + #[arg(name = "CARGO_ARGS")] + args: Vec, } -#[derive(Subcommand, Debug)] -pub enum CargoCommand { - /// Builds an executable suitable to run on a 3DS (3dsx). - Build, - /// Equivalent to `cargo check`. - Check, - /// Equivalent to `cargo clippy`. - Clippy, - /// Equivalent to `cargo doc`. - Doc, - /// Builds an executable and sends it to a device with `3dslink`. - Run(Run), - /// Builds a test executable and sends it to a device with `3dslink`. - /// - /// This can be used with `--test` for integration tests, or `--lib` for - /// unit tests (which require a custom test runner). - Test(Test), - // - // TODO: this doesn't seem to work for some reason... - // #[command(external_subcommand)] - // Other(Vec), -} - -#[derive(Args, Debug)] +#[derive(Parser, Debug)] pub struct Test { /// If set, the built executable will not be sent to the device to run it. #[arg(long)] pub no_run: bool, + + // The test command uses a superset of the same arguments as Run. #[command(flatten)] pub run_args: Run, } -#[derive(Args, Debug)] +#[derive(Parser, Debug)] pub struct Run { /// Specify the IP address of the device to send the executable to. /// /// Corresponds to 3dslink's `--address` arg, which defaults to automatically /// finding the device. #[arg(long, short = 'a')] - pub address: Option, + pub address: Option, /// Set the 0th argument of the executable when running it. Corresponds to /// 3dslink's `--argv0` argument. @@ -88,7 +90,7 @@ pub struct Run { /// Start the 3dslink server after sending the executable. Corresponds to /// 3dslink's `--server` argument. - #[arg(long, short = 's')] + #[arg(long, short = 's', default_value_t = false)] pub server: bool, /// Set the number of tries when connecting to the device to send the executable. @@ -96,9 +98,13 @@ pub struct Run { // Can't use `short = 'r'` because that would conflict with cargo's `--release/-r` #[arg(long)] pub retries: Option, + + // Passthrough cargo options. + #[command(flatten)] + pub cargo_args: CargoArgs, } -impl Input { +impl CargoArgs { /// Get the args to be passed to the executable itself (not `cargo`). pub fn cargo_opts(&self) -> &[String] { self.split_args().0 @@ -110,15 +116,31 @@ impl Input { } fn split_args(&self) -> (&[String], &[String]) { - if let Some(split) = self.cargo_options.iter().position(|arg| arg == "--") { - let split = if &self.cargo_options[split] == "--" { + if let Some(split) = self + .args + .iter() + .position(|s| s == "--" || !s.starts_with('-')) + { + let split = if self.args[split] == "--" { split + 1 } else { split }; - self.cargo_options.split_at(split) + self.args.split_at(split) } else { - (&self.cargo_options[..], &[]) + (&self.args[..], &[]) } } } + +#[cfg(test)] +mod tests { + use super::*; + + use clap::CommandFactory; + + #[test] + fn verify_app() { + Cargo::command().debug_assert(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1fcc0ac..0aa7ddf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ -extern crate core; - pub mod command; -use crate::command::{CargoCommand, Input}; +use crate::command::{CargoCmd, Input}; use cargo_metadata::{Message, MetadataCommand}; use core::fmt; use rustc_version::Channel; @@ -22,11 +20,11 @@ pub fn get_should_link(input: &mut Input) -> bool { // When running compile only commands, don't link the executable to the 3ds. // Otherwise, link and run on the 3ds but do not run locally. match input.cmd { - CargoCommand::Run(_) => true, - CargoCommand::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { - // input.cargo_opts().push("--no-run".to_string()); - true - } + CargoCmd::Run(_) => true, + // CargoCmd::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { + // // input.cargo_opts().push("--no-run".to_string()); + // true + // } _ => false, } } @@ -35,41 +33,42 @@ pub fn get_should_link(input: &mut Input) -> bool { /// default to `json-render-diagnostics`. pub fn get_message_format(input: &mut Input) -> String { // Checks for a position within the args where '--message-format' is located - if let Some(pos) = input - .cargo_opts() - .iter() - .position(|s| s.starts_with("--message-format")) - { - // Remove the arg from list - let arg = input.cargo_opts()[pos].clone(); // TODO - - // Allows for usage of '--message-format=' and also using space separation. - // Check for a '=' delimiter and use the second half of the split as the format, - // otherwise remove next arg which is now at the same position as the original flag. - let format = if let Some((_, format)) = arg.split_once('=') { - format.to_string() - } else { - input.cargo_opts()[pos].clone() // TODO - }; - - // Non-json formats are not supported so the executable exits. - if format.starts_with("json") { - format - } else { - eprintln!("error: non-JSON `message-format` is not supported"); - process::exit(1); - } - } else { - // Default to 'json-render-diagnostics' - DEFAULT_MESSAGE_FORMAT.to_string() - } + todo!(); + // if let Some(pos) = input + // .cargo_opts() + // .iter() + // .position(|s| s.starts_with("--message-format")) + // { + // // Remove the arg from list + // let arg = input.cargo_opts()[pos].clone(); // TODO + + // // Allows for usage of '--message-format=' and also using space separation. + // // Check for a '=' delimiter and use the second half of the split as the format, + // // otherwise remove next arg which is now at the same position as the original flag. + // let format = if let Some((_, format)) = arg.split_once('=') { + // format.to_string() + // } else { + // input.cargo_opts()[pos].clone() // TODO + // }; + + // // Non-json formats are not supported so the executable exits. + // if format.starts_with("json") { + // format + // } else { + // eprintln!("error: non-JSON `message-format` is not supported"); + // process::exit(1); + // } + // } else { + // // Default to 'json-render-diagnostics' + // DEFAULT_MESSAGE_FORMAT.to_string() + // } } /// Build the elf that will be used to create other 3ds files. /// The command built from [`make_cargo_build_command`] is executed /// and the messages from the spawned process are parsed and returned. pub fn build_elf( - cmd: CargoCommand, + cmd: CargoCmd, message_format: &str, args: &Vec, ) -> (ExitStatus, Vec) { @@ -100,7 +99,7 @@ pub fn build_elf( /// Create the cargo build command, but don't execute it. /// If there is no pre-built std detected in the sysroot, `build-std` is used. pub fn make_cargo_build_command( - cmd: CargoCommand, + cmd: CargoCmd, message_format: &str, args: &Vec, ) -> Command { @@ -114,11 +113,9 @@ pub fn make_cargo_build_command( let mut command = Command::new(cargo); let cmd = match cmd { - CargoCommand::Build | CargoCommand::Run(_) => "build", - CargoCommand::Test(_) => "test", - CargoCommand::Check => "check", - CargoCommand::Clippy => "clippy", - CargoCommand::Doc => "doc", + CargoCmd::Build(_) | CargoCmd::Run(_) => "build", + CargoCmd::Test(_) => "test", + CargoCmd::Passthrough(_) => todo!(), }; command diff --git a/src/main.rs b/src/main.rs index dd5501b..a457646 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use cargo_3ds::command::Cargo; +use cargo_3ds::command::{Cargo, CargoCmd, Input, Run, Test}; use cargo_3ds::{ build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata, get_should_link, link, }; -use clap::Parser; +use clap::{CommandFactory, FromArgMatches, Parser}; use std::process; fn main() { @@ -12,10 +12,21 @@ fn main() { let Cargo::Input(mut input) = Cargo::parse(); dbg!(&input); - dbg!(input.cargo_opts()); - dbg!(input.exe_args()); - // let should_link = get_should_link(&mut input); + let cargo_args = match &input.cmd { + CargoCmd::Build(cargo_args) + | CargoCmd::Run(Run { cargo_args, .. }) + | CargoCmd::Test(Test { + run_args: Run { cargo_args, .. }, + .. + }) => cargo_args, + CargoCmd::Passthrough(other) => todo!(), + }; + + dbg!(cargo_args.cargo_opts()); + dbg!(cargo_args.exe_args()); + + // let // let message_format = get_message_format(&mut input); // let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts); From dbf1595defde98395d65c5639dc58605194d41bf Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 30 Oct 2022 18:10:24 -0400 Subject: [PATCH 4/6] Add plumbing for new commands to running cargo --- src/command.rs | 170 ++++++++++++++++++++++++++++++++++++++++++++----- src/lib.rs | 112 +++++++++++--------------------- src/main.rs | 67 +++++++++---------- 3 files changed, 219 insertions(+), 130 deletions(-) diff --git a/src/command.rs b/src/command.rs index 00972d3..0261474 100644 --- a/src/command.rs +++ b/src/command.rs @@ -23,7 +23,7 @@ pub struct Input { #[command(allow_external_subcommands = true)] pub enum CargoCmd { /// Builds an executable suitable to run on a 3DS (3dsx). - Build(CargoArgs), + Build(RemainingArgs), /// Builds an executable and sends it to a device with `3dslink`. Run(Run), @@ -43,7 +43,7 @@ pub enum CargoCmd { } #[derive(Args, Debug)] -pub struct CargoArgs { +pub struct RemainingArgs { /// Pass additional options through to the `cargo` command. /// /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` @@ -101,12 +101,74 @@ pub struct Run { // Passthrough cargo options. #[command(flatten)] - pub cargo_args: CargoArgs, + pub cargo_args: RemainingArgs, } -impl CargoArgs { +impl CargoCmd { + /// Whether or not this command should build a 3DSX executable file. + pub fn should_build_3dsx(&self) -> bool { + matches!(self, Self::Build(_) | Self::Run(_) | Self::Test(_)) + } + + /// Whether or not the resulting executable should be sent to the 3DS with + /// `3dslink`. + pub fn should_link_to_device(&self) -> bool { + match self { + CargoCmd::Test(test) => !test.no_run, + CargoCmd::Run(_) => true, + _ => false, + } + } + + pub const DEFAULT_MESSAGE_FORMAT: &str = "json-render-diagnostics"; + + pub fn extract_message_format(&mut self) -> Result, String> { + Self::extract_message_format_from_args(match self { + CargoCmd::Build(args) => &mut args.args, + CargoCmd::Run(run) => &mut run.cargo_args.args, + CargoCmd::Test(test) => &mut test.run_args.cargo_args.args, + CargoCmd::Passthrough(args) => args, + }) + } + + fn extract_message_format_from_args( + cargo_args: &mut Vec, + ) -> Result, String> { + // Checks for a position within the args where '--message-format' is located + if let Some(pos) = cargo_args + .iter() + .position(|s| s.starts_with("--message-format")) + { + // Remove the arg from list so we don't pass anything twice by accident + let arg = cargo_args.remove(pos); + + // Allows for usage of '--message-format=' and also using space separation. + // Check for a '=' delimiter and use the second half of the split as the format, + // otherwise remove next arg which is now at the same position as the original flag. + let format = if let Some((_, format)) = arg.split_once('=') { + format.to_string() + } else { + // Also need to remove the argument to the --message-format option + cargo_args.remove(pos) + }; + + // Non-json formats are not supported so the executable exits. + if format.starts_with("json") { + Ok(Some(format)) + } else { + Err(String::from( + "error: non-JSON `message-format` is not supported", + )) + } + } else { + Ok(None) + } + } +} + +impl RemainingArgs { /// Get the args to be passed to the executable itself (not `cargo`). - pub fn cargo_opts(&self) -> &[String] { + pub fn cargo_args(&self) -> &[String] { self.split_args().0 } @@ -116,17 +178,8 @@ impl CargoArgs { } fn split_args(&self) -> (&[String], &[String]) { - if let Some(split) = self - .args - .iter() - .position(|s| s == "--" || !s.starts_with('-')) - { - let split = if self.args[split] == "--" { - split + 1 - } else { - split - }; - self.args.split_at(split) + if let Some(split) = self.args.iter().position(|s| s == "--") { + self.args.split_at(split + 1) } else { (&self.args[..], &[]) } @@ -143,4 +196,89 @@ mod tests { fn verify_app() { Cargo::command().debug_assert(); } + + #[test] + fn extract_format() { + for (args, expected) in [ + (&["--foo", "--message-format=json", "bar"][..], Some("json")), + (&["--foo", "--message-format", "json", "bar"], Some("json")), + ( + &[ + "--foo", + "--message-format", + "json-render-diagnostics", + "bar", + ], + Some("json-render-diagnostics"), + ), + ( + &["--foo", "--message-format=json-render-diagnostics", "bar"], + Some("json-render-diagnostics"), + ), + ] { + let mut cmd = CargoCmd::Build(RemainingArgs { + args: args.iter().map(ToString::to_string).collect(), + }); + + assert_eq!( + cmd.extract_message_format().unwrap(), + expected.map(ToString::to_string) + ); + + if let CargoCmd::Build(args) = cmd { + assert_eq!(args.args, vec!["--foo", "bar"]); + } else { + unreachable!(); + } + } + } + + #[test] + fn extract_format_err() { + for args in [&["--message-format=foo"][..], &["--message-format", "foo"]] { + let mut cmd = CargoCmd::Build(RemainingArgs { + args: args.iter().map(ToString::to_string).collect(), + }); + + assert!(cmd.extract_message_format().is_err()); + } + } + + #[test] + fn split_run_args() { + struct TestParam { + input: &'static [&'static str], + expected_cargo: &'static [&'static str], + expected_exe: &'static [&'static str], + } + + for param in [ + TestParam { + input: &["--example", "hello-world", "--no-default-features"], + expected_cargo: &["--example", "hello-world", "--no-default-features"], + expected_exe: &[], + }, + TestParam { + input: &["--example", "hello-world", "--", "--do-stuff", "foo"], + expected_cargo: &["--example", "hello-world", "--"], + expected_exe: &["--do-stuff", "foo"], + }, + TestParam { + input: &["--lib", "--", "foo"], + expected_cargo: &["--lib", "--"], + expected_exe: &["foo"], + }, + TestParam { + input: &["foo", "--", "bar"], + expected_cargo: &["foo", "--"], + expected_exe: &["bar"], + }, + ] { + let Run { cargo_args, .. } = + Run::parse_from(std::iter::once(&"run").chain(param.input)); + + assert_eq!(cargo_args.cargo_args(), param.expected_cargo); + assert_eq!(cargo_args.exe_args(), param.expected_exe); + } + } } diff --git a/src/lib.rs b/src/lib.rs index 0aa7ddf..ba8d41a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,84 +1,32 @@ pub mod command; -use crate::command::{CargoCmd, Input}; +use crate::command::CargoCmd; + use cargo_metadata::{Message, MetadataCommand}; -use core::fmt; use rustc_version::Channel; use semver::Version; use serde::Deserialize; +use tee::TeeReader; + +use core::fmt; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::{env, io, process}; -use tee::TeeReader; - -const DEFAULT_MESSAGE_FORMAT: &str = "json-render-diagnostics"; - -/// Gets whether the executable should link the generated files to a 3ds -/// based on the parsed input. -pub fn get_should_link(input: &mut Input) -> bool { - // When running compile only commands, don't link the executable to the 3ds. - // Otherwise, link and run on the 3ds but do not run locally. - match input.cmd { - CargoCmd::Run(_) => true, - // CargoCmd::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { - // // input.cargo_opts().push("--no-run".to_string()); - // true - // } - _ => false, - } -} - -/// Extracts the user-defined message format and if there is none, -/// default to `json-render-diagnostics`. -pub fn get_message_format(input: &mut Input) -> String { - // Checks for a position within the args where '--message-format' is located - todo!(); - // if let Some(pos) = input - // .cargo_opts() - // .iter() - // .position(|s| s.starts_with("--message-format")) - // { - // // Remove the arg from list - // let arg = input.cargo_opts()[pos].clone(); // TODO - - // // Allows for usage of '--message-format=' and also using space separation. - // // Check for a '=' delimiter and use the second half of the split as the format, - // // otherwise remove next arg which is now at the same position as the original flag. - // let format = if let Some((_, format)) = arg.split_once('=') { - // format.to_string() - // } else { - // input.cargo_opts()[pos].clone() // TODO - // }; - - // // Non-json formats are not supported so the executable exits. - // if format.starts_with("json") { - // format - // } else { - // eprintln!("error: non-JSON `message-format` is not supported"); - // process::exit(1); - // } - // } else { - // // Default to 'json-render-diagnostics' - // DEFAULT_MESSAGE_FORMAT.to_string() - // } -} -/// Build the elf that will be used to create other 3ds files. -/// The command built from [`make_cargo_build_command`] is executed -/// and the messages from the spawned process are parsed and returned. -pub fn build_elf( - cmd: CargoCmd, - message_format: &str, - args: &Vec, -) -> (ExitStatus, Vec) { - let mut command = make_cargo_build_command(cmd, message_format, args); +/// Built a command using [`make_cargo_build_command`] and execute it, +/// parsing and returning the messages from the spawned 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_build_command(cmd, &message_format); let mut process = command.spawn().unwrap(); let command_stdout = process.stdout.take().unwrap(); let mut tee_reader; let mut stdout_reader; - let buf_reader: &mut dyn BufRead = if message_format == DEFAULT_MESSAGE_FORMAT { + let buf_reader: &mut dyn BufRead = if message_format.is_none() { stdout_reader = BufReader::new(command_stdout); &mut stdout_reader } else { @@ -98,11 +46,7 @@ pub fn build_elf( /// Create the cargo build command, but don't execute it. /// 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: &str, - args: &Vec, -) -> Command { +pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) -> Command { let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() + &format!( " -L{}/libctru/lib -lctru", @@ -112,32 +56,48 @@ pub fn make_cargo_build_command( let sysroot = find_sysroot(); let mut command = Command::new(cargo); - let cmd = match cmd { + let cmd_str = match cmd { CargoCmd::Build(_) | CargoCmd::Run(_) => "build", CargoCmd::Test(_) => "test", - CargoCmd::Passthrough(_) => todo!(), + CargoCmd::Passthrough(cmd) => &cmd[0], }; command .env("RUSTFLAGS", rust_flags) - .arg(cmd) + .arg(cmd_str) .arg("--target") .arg("armv6k-nintendo-3ds") .arg("--message-format") - .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-build std found, using build-std"); command.arg("-Z").arg("build-std"); } + let cargo_args = match cmd { + CargoCmd::Build(cargo_args) => cargo_args.cargo_args(), + CargoCmd::Run(run) => run.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 + command.arg("--no-run"); + test.run_args.cargo_args.cargo_args() + } + CargoCmd::Passthrough(other) => &other[1..], + }; + command - .args(args) + .args(cargo_args) .stdout(Stdio::piped()) .stdin(Stdio::inherit()) .stderr(Stdio::inherit()); - command + dbg!(command) } /// Finds the sysroot path of the current toolchain diff --git a/src/main.rs b/src/main.rs index a457646..ea2f47c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ -use cargo_3ds::command::{Cargo, CargoCmd, Input, Run, Test}; -use cargo_3ds::{ - build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata, - get_should_link, link, -}; -use clap::{CommandFactory, FromArgMatches, Parser}; +use cargo_3ds::command::Cargo; +use cargo_3ds::{build_3dsx, build_smdh, check_rust_version, get_metadata, link, run_cargo}; + +use clap::Parser; + use std::process; fn main() { @@ -11,45 +10,37 @@ fn main() { let Cargo::Input(mut input) = Cargo::parse(); - dbg!(&input); - - let cargo_args = match &input.cmd { - CargoCmd::Build(cargo_args) - | CargoCmd::Run(Run { cargo_args, .. }) - | CargoCmd::Test(Test { - run_args: Run { cargo_args, .. }, - .. - }) => cargo_args, - CargoCmd::Passthrough(other) => todo!(), + let message_format = match input.cmd.extract_message_format() { + Ok(fmt) => fmt, + Err(msg) => { + eprintln!("{msg}"); + process::exit(1) + } }; - dbg!(cargo_args.cargo_opts()); - dbg!(cargo_args.exe_args()); - - // let - // let message_format = get_message_format(&mut input); + let (status, messages) = run_cargo(&input.cmd, message_format); - // let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts); + if !status.success() { + process::exit(status.code().unwrap_or(1)); + } - // if !status.success() { - // process::exit(status.code().unwrap_or(1)); - // } + if !input.cmd.should_build_3dsx() { + return; + } - // if !input.cmd.should_build_3dsx() { - // return; - // } + eprintln!("Getting metadata"); + let app_conf = get_metadata(&messages); - // eprintln!("Getting metadata"); - // let app_conf = get_metadata(&messages); + eprintln!("Building smdh:{}", app_conf.path_smdh().display()); + build_smdh(&app_conf); - // eprintln!("Building smdh:{}", app_conf.path_smdh().display()); - // build_smdh(&app_conf); + eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display()); + build_3dsx(&app_conf); - // eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display()); - // build_3dsx(&app_conf); + if input.cmd.should_link_to_device() { + // TODO plumb in exe_args and various 3dslink args - // if should_link { - // eprintln!("Running 3dslink"); - // link(&app_conf); - // } + eprintln!("Running 3dslink"); + link(&app_conf); + } } From dd8ae472966b84f177c7207b9ad019f0722350dc Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Sun, 30 Oct 2022 18:39:00 -0400 Subject: [PATCH 5/6] Plumb through 3dslink args for run + test commands --- src/command.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 11 +++++++++-- src/main.rs | 4 +--- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/command.rs b/src/command.rs index 0261474..c19be40 100644 --- a/src/command.rs +++ b/src/command.rs @@ -186,6 +186,49 @@ impl RemainingArgs { } } +impl Run { + /// Get the args to pass to `3dslink` based on these options. + pub fn get_3dslink_args(&self) -> Vec { + let mut args = Vec::new(); + + if let Some(address) = self.address { + args.extend(["--address".to_string(), address.to_string()]); + } + + if let Some(argv0) = &self.argv0 { + args.extend(["--arg0".to_string(), argv0.clone()]); + } + + if let Some(retries) = self.retries { + args.extend(["--retries".to_string(), retries.to_string()]); + } + + if self.server { + args.push("--server".to_string()); + } + + let exe_args = self.cargo_args.exe_args(); + if !exe_args.is_empty() { + // For some reason 3dslink seems to want 2 instances of `--`, one + // in front of all of the args like this... + args.extend(["--args".to_string(), "--".to_string()]); + + let mut escaped = false; + for arg in exe_args.iter().cloned() { + if arg.starts_with('-') && !escaped { + // And one before the first `-` arg that is passed in. + args.extend(["--".to_string(), arg]); + escaped = true; + } else { + args.push(arg); + } + } + } + + args + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index ba8d41a..ccd36c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) .stdin(Stdio::inherit()) .stderr(Stdio::inherit()); - dbg!(command) + command } /// Finds the sysroot path of the current toolchain @@ -285,9 +285,16 @@ 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) { +pub fn link(config: &CTRConfig, cmd: &CargoCmd) { + let run_args = match cmd { + CargoCmd::Run(run) => run, + CargoCmd::Test(test) => &test.run_args, + _ => unreachable!(), + }; + let mut process = Command::new("3dslink") .arg(config.path_3dsx()) + .args(run_args.get_3dslink_args()) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) diff --git a/src/main.rs b/src/main.rs index ea2f47c..e943262 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,9 +38,7 @@ fn main() { build_3dsx(&app_conf); if input.cmd.should_link_to_device() { - // TODO plumb in exe_args and various 3dslink args - eprintln!("Running 3dslink"); - link(&app_conf); + link(&app_conf, &input.cmd); } } From 329cb00d74323590938a388eb2455948ba2d378d Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Wed, 2 Nov 2022 22:24:00 -0400 Subject: [PATCH 6/6] Documentation improvements and extra test case --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++--------- src/command.rs | 32 +++++++-------- src/lib.rs | 2 +- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 03a5f5a..5ac5dab 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,97 @@ # cargo-3ds + Cargo command to work with Nintendo 3DS project binaries. Based on cargo-psp. -# Usage +## Usage + Use the nightly toolchain to build 3DS apps (either by using `rustup override nightly` for the project directory or by adding `+nightly` in the `cargo` invocation). -Available commands: -``` - build build a 3dsx executable. - run build a 3dsx executable and send it to a device with 3dslink. - test build a 3dsx executable from unit/integration tests and send it to a device. - execute some other Cargo command with 3ds options configured (ex. check or clippy). -``` - -Additional arguments will be passed through to ``. Some that are supported include: +```txt +Commands: + build + Builds an executable suitable to run on a 3DS (3dsx) + run + Builds an executable and sends it to a device with `3dslink` + test + Builds a test executable and sends it to a device with `3dslink` + help + Print this message or the help of the given subcommand(s) + +Options: + -h, --help + Print help information (use `-h` for a summary) + + -V, --version + Print version information ``` - [build | run | test] --release - test --no-run + +Additional arguments will be passed through to the given subcommand. +See [passthrough arguments](#passthrough-arguments) for more details. + +It is also possible to pass any other `cargo` command (e.g. `doc`, `check`), +and all its arguments will be passed through directly to `cargo` unmodified, +with the proper `RUSTFLAGS` and `--target` set for the 3DS target. + +### Basic Examples + +* `cargo 3ds build` +* `cargo 3ds check --verbose` +* `cargo 3ds run --release --example foo` +* `cargo 3ds test --no-run` + +### Running executables + +`cargo 3ds test` and `cargo 3ds run` use the `3dslink` tool to send built +executables to a device, and thus accept specific related arguments that correspond +to `3dslink` arguments: + +```txt +-a, --address
+ Specify the IP address of the device to send the executable to. + + Corresponds to 3dslink's `--address` arg, which defaults to automatically finding the device. + +-0, --argv0 + Set the 0th argument of the executable when running it. Corresponds to 3dslink's `--argv0` argument + +-s, --server + Start the 3dslink server after sending the executable. Corresponds to 3dslink's `--server` argument + + --retries + Set the number of tries when connecting to the device to send the executable. Corresponds to 3dslink's `--retries` argument ``` - -Other flags and commands may work, but haven't been tested. -# Examples -`cargo 3ds build` \ -`cargo 3ds run --release` \ -`cargo 3ds test --no-run` +### Passthrough Arguments + +Due to the way `cargo-3ds`, `cargo`, and `3dslink` parse arguments, there is +unfortunately some complexity required when invoking an executable with arguments. + +All unrecognized arguments beginning with a dash (e.g. `--release`, `--features`, +etc.) will be passed through to `cargo` directly. + +> **NOTE:** arguments for [running executables](#running-executables) must be +> specified *before* other unrecognized `cargo` arguments. Otherwise they will +> be treated as passthrough arguments which will most likely fail the resulting +> `cargo` command. + +An optional `--` may be used to explicitly pass subsequent args to `cargo`, including +arguments to pass to the executable itself. To separate `cargo` arguments from +executable arguments, *another* `--` can be used. For example: + +* `cargo 3ds run -- -- xyz` + + Builds an executable and send it to a device to run it with the argument `xyz`. + +* `cargo 3ds test --address 192.168.0.2 -- -- --test-arg 1` + + Builds a test executable and attempts to send it to a device with the + address `192.168.0.2` and run it using the arguments `["--test-arg", "1"]`. + +* `cargo 3ds test --address 192.168.0.2 --verbose -- --test-arg 1` + + Build a test executable with `cargo test --verbose`, and attempts to send + it to a device with the address `192.168.0.2` and run it using the arguments + `["--test-arg", "1"]`. + + This works without two `--` instances because `--verbose` begins the set of + `cargo` arguments and ends the set of 3DS-specific arguments. diff --git a/src/command.rs b/src/command.rs index c19be40..a014b31 100644 --- a/src/command.rs +++ b/src/command.rs @@ -14,11 +14,11 @@ pub struct Input { pub cmd: CargoCmd, } -/// The cargo command to run. This command will be forwarded to the real -/// `cargo` with the appropriate arguments for a 3DS executable. +/// Run a cargo command. COMMAND will be forwarded to the real +/// `cargo` with the appropriate arguments for the 3DS target. /// -/// If another command is passed which is not recognized, it will be passed -/// through unmodified to `cargo` with RUSTFLAGS set for the 3DS. +/// If an unrecognized COMMAND is used, it will be passed through unmodified +/// to `cargo` with the appropriate flags set for the 3DS target. #[derive(Subcommand, Debug)] #[command(allow_external_subcommands = true)] pub enum CargoCmd { @@ -46,16 +46,13 @@ pub enum CargoCmd { pub struct RemainingArgs { /// Pass additional options through to the `cargo` command. /// - /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` - /// options from cargo options. Any argument after `--` will be passed through - /// to cargo unmodified. + /// All arguments after the first `--`, or starting with the first unrecognized + /// option, will be passed through to `cargo` unmodified. /// - /// If one of the arguments is itself `--`, the args following that will be - /// considered as args to pass to the executable, rather than to `cargo` - /// (only works for the `run` or `test` commands). For example, if you want - /// to pass some args to the executable directly it might look like this: - /// - /// > cargo 3ds run -- -- arg1 arg2 + /// To pass arguments to an executable being run, a *second* `--` must be + /// used to disambiguate cargo arguments from executable arguments. + /// For example, `cargo 3ds run -- -- xyz` runs an executable with the argument + /// `xyz`. #[arg(trailing_var_arg = true)] #[arg(allow_hyphen_values = true)] #[arg(global = true)] @@ -242,8 +239,8 @@ mod tests { #[test] fn extract_format() { - for (args, expected) in [ - (&["--foo", "--message-format=json", "bar"][..], Some("json")), + const CASES: &[(&[&str], Option<&str>)] = &[ + (&["--foo", "--message-format=json", "bar"], Some("json")), (&["--foo", "--message-format", "json", "bar"], Some("json")), ( &[ @@ -258,7 +255,10 @@ mod tests { &["--foo", "--message-format=json-render-diagnostics", "bar"], Some("json-render-diagnostics"), ), - ] { + (&["--foo", "bar"], None), + ]; + + for (args, expected) in CASES { let mut cmd = CargoCmd::Build(RemainingArgs { args: args.iter().map(ToString::to_string).collect(), }); diff --git a/src/lib.rs b/src/lib.rs index ccd36c4..aeb1bfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use std::path::{Path, PathBuf}; use std::process::{Command, ExitStatus, Stdio}; use std::{env, io, process}; -/// Built a command using [`make_cargo_build_command`] and execute it, +/// Build a command using [`make_cargo_build_command`] and execute it, /// parsing and returning the messages from the spawned process. /// /// For commands that produce an executable output, this function will build the