From ab30da9ee75dc1d46a8d2527b9d14a22babc7056 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Mon, 28 Aug 2023 21:27:59 -0400 Subject: [PATCH 1/3] Check in Cargo.lock and install with `--locked` This is the recommended best practice for binaries, we should do it for `cargo-3ds` as well. A side effect of this is that we should test build and installation with the exact Cargo.lock dependencies with `--locked`. --- .github/workflows/ci.yml | 6 +- .gitignore | 3 +- Cargo.lock | 440 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 Cargo.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c8a272..b2f73ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,16 +56,16 @@ jobs: - uses: ./.github/actions/setup with: toolchain: ${{ matrix.toolchain }} - + - name: Install cargo-3ds uses: actions-rs/cargo@v1 with: command: install - args: --path . + args: --locked --path . - name: Create new project run: cargo 3ds new app --bin - + - name: Build project working-directory: ./app run: cargo 3ds build --release diff --git a/.gitignore b/.gitignore index 2da6cde..c507849 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ target -Cargo.lock -.idea \ No newline at end of file +.idea diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1b20b84 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,440 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea" + +[[package]] +name = "anstyle-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-3ds" +version = "0.1.1" +dependencies = [ + "cargo_metadata", + "clap", + "rustc_version", + "semver", + "serde", + "shlex", + "tee", + "toml", +] + +[[package]] +name = "cargo-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "clap" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tee" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c12559dba7383625faaff75be24becf35bfc885044375bcab931111799a3da" + +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix", + "windows-sys", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" From 0db0ad3b963e6877327bd6738e3519062777b275 Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Tue, 29 Aug 2023 00:12:48 -0400 Subject: [PATCH 2/3] Add support for custom runner Use `cargo config get` to check if the runner is configured, and if so use it like a normal `cargo run` invocation would. Otherwise, fall back to the default `3dslink` behavior we had before. --- src/command.rs | 198 +++++++++++++++++++++++++++++++++++-------------- src/lib.rs | 65 ++++++++++------ src/main.rs | 7 +- 3 files changed, 188 insertions(+), 82 deletions(-) diff --git a/src/command.rs b/src/command.rs index 0b0804f..68617e4 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,10 +1,12 @@ use std::fs; use std::io::Read; +use std::process::Stdio; +use std::sync::OnceLock; use cargo_metadata::Message; use clap::{Args, Parser, Subcommand}; -use crate::{build_3dsx, build_smdh, get_metadata, link, CTRConfig}; +use crate::{build_3dsx, build_smdh, find_cargo, get_metadata, link, print_command, CTRConfig}; #[derive(Parser, Debug)] #[command(name = "cargo", bin_name = "cargo")] @@ -20,9 +22,15 @@ pub struct Input { 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')] + /// set the verbose flag for cargo itself. To set cargo's verbose flag, add + /// `-- -v` to the end of the command line. + #[arg(long, short = 'v', global = true)] pub verbose: bool, + + /// Set cargo configuration on the command line. This is equivalent to + /// cargo's `--config` option. + #[arg(long, global = true)] + pub config: Vec, } /// Run a cargo command. COMMAND will be forwarded to the real @@ -67,21 +75,25 @@ pub struct RemainingArgs { /// 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)] - #[arg(name = "CARGO_ARGS")] + #[arg( + trailing_var_arg = true, + allow_hyphen_values = true, + value_name = "CARGO_ARGS" + )] args: Vec, } -#[derive(Parser, Debug)] +#[derive(Args, Debug)] pub struct Build { + #[arg(from_global)] + pub verbose: bool, + // Passthrough cargo options. #[command(flatten)] - pub cargo_args: RemainingArgs, + pub passthrough: RemainingArgs, } -#[derive(Parser, Debug)] +#[derive(Args, Debug)] pub struct Run { /// Specify the IP address of the device to send the executable to. /// @@ -109,16 +121,20 @@ pub struct Run { // Passthrough `cargo build` options. #[command(flatten)] pub build_args: Build, + + #[arg(from_global)] + config: Vec, } -#[derive(Parser, Debug)] +#[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, /// If set, documentation tests will be built instead of unit tests. - /// This implies `--no-run`. + /// This implies `--no-run`, unless Cargo's `target.armv6k-nintendo-3ds.runner` + /// is configured. #[arg(long)] pub doc: bool, @@ -127,7 +143,7 @@ pub struct Test { pub run_args: Run, } -#[derive(Parser, Debug)] +#[derive(Args, Debug)] pub struct New { /// Path of the new project. #[arg(required = true)] @@ -142,16 +158,15 @@ 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::Build(build) => build.passthrough.cargo_args(), + CargoCmd::Run(run) => run.build_args.passthrough.cargo_args(), CargoCmd::Test(test) => { - let mut cargo_args = test.run_args.build_args.cargo_args.cargo_args(); + let mut cargo_args = test.run_args.build_args.passthrough.cargo_args(); - // 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 + // We can't run 3DS executables on the host, but we want to respect + // the user's "runner" configuration if set. if test.doc { - eprintln!("Documentation tests requested, no 3dsx will be built or run"); + eprintln!("Documentation tests requested, no 3dsx will be built"); // https://github.com/rust-lang/cargo/issues/7040 cargo_args.append(&mut vec![ @@ -163,7 +178,7 @@ impl CargoCmd { "-Z".to_string(), "build-std=std,test".to_string(), ]); - } else { + } else if !test.run_args.is_runner_configured() { cargo_args.push("--no-run".to_string()); } @@ -185,10 +200,18 @@ impl 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`). + /// Commands may use different commands under the hood to function (e.g. [`CargoCmd::Run`] uses `build` + /// if no custom runner is configured). pub fn subcommand_name(&self) -> &str { match self { - CargoCmd::Build(_) | CargoCmd::Run(_) => "build", + CargoCmd::Build(_) => "build", + CargoCmd::Run(run) => { + if run.is_runner_configured() { + "run" + } else { + "build" + } + } CargoCmd::Test(_) => "test", CargoCmd::New(_) => "new", CargoCmd::Passthrough(cmd) => &cmd[0], @@ -215,8 +238,8 @@ impl CargoCmd { /// `3dslink`. pub fn should_link_to_device(&self) -> bool { match self { - Self::Test(test) => !test.no_run, - Self::Run(_) => true, + Self::Test(test) => !(test.no_run || test.run_args.is_runner_configured()), + Self::Run(run) => !run.is_runner_configured(), _ => false, } } @@ -225,10 +248,10 @@ impl CargoCmd { pub fn extract_message_format(&mut self) -> Result, String> { let cargo_args = match self { - Self::Build(build) => &mut build.cargo_args.args, - Self::Run(run) => &mut run.build_args.cargo_args.args, + Self::Build(build) => &mut build.passthrough.args, + Self::Run(run) => &mut run.build_args.passthrough.args, Self::New(new) => &mut new.cargo_args.args, - Self::Test(test) => &mut test.run_args.build_args.cargo_args.args, + Self::Test(test) => &mut test.run_args.build_args.passthrough.args, Self::Passthrough(args) => args, }; @@ -287,7 +310,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], verbose: bool) { + 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"); @@ -299,9 +322,9 @@ impl CargoCmd { // Run callback only for commands that use it match self { - Self::Build(cmd) => cmd.callback(&config, verbose), - Self::Run(cmd) => cmd.callback(&config, verbose), - Self::Test(cmd) => cmd.callback(&config, verbose), + Self::Build(cmd) => cmd.callback(&config), + Self::Run(cmd) => cmd.callback(&config), + Self::Test(cmd) => cmd.callback(&config), Self::New(cmd) => cmd.callback(), _ => (), } @@ -309,7 +332,7 @@ impl CargoCmd { } impl RemainingArgs { - /// Get the args to be passed to the executable itself (not `cargo`). + /// Get the args to be passed to `cargo`. pub fn cargo_args(&self) -> Vec { self.split_args().0 } @@ -324,6 +347,8 @@ impl RemainingArgs { if let Some(split) = args.iter().position(|s| s == "--") { let second_half = args.split_off(split + 1); + // take off the "--" arg we found + args.pop(); (args, second_half) } else { @@ -336,13 +361,13 @@ impl Build { /// Callback for `cargo 3ds build`. /// /// This callback handles building the application as a `.3dsx` file. - fn callback(&self, config: &Option, verbose: bool) { + fn callback(&self, config: &Option) { if let Some(config) = config { eprintln!("Building smdh: {}", config.path_smdh().display()); - build_smdh(config, verbose); + build_smdh(config, self.verbose); eprintln!("Building 3dsx: {}", config.path_3dsx().display()); - build_3dsx(config, verbose); + build_3dsx(config, self.verbose); } } } @@ -368,7 +393,7 @@ impl Run { args.push("--server".to_string()); } - let exe_args = self.build_args.cargo_args.exe_args(); + let exe_args = self.build_args.passthrough.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... @@ -392,14 +417,59 @@ impl Run { /// Callback for `cargo 3ds run`. /// /// This callback handles launching the application via `3dslink`. - fn callback(&self, config: &Option, verbose: bool) { + fn callback(&self, config: &Option) { // Run the normal "build" callback - self.build_args.callback(config, verbose); + self.build_args.callback(config); + + if !self.is_runner_configured() { + if let Some(cfg) = config { + eprintln!("Running 3dslink"); + link(cfg, self, self.build_args.verbose); + } + } + } + + /// Returns whether the cargo environment has `target.armv6k-nintendo-3ds.runner` + /// configured. This will only be checked once during the lifetime of the program, + /// and takes into account the usual ways Cargo looks for + /// [configuration](https://doc.rust-lang.org/cargo/reference/config.html): + /// + /// - `.cargo/config.toml` + /// - Environment variables + /// - Command-line `--config` overrides + pub fn is_runner_configured(&self) -> bool { + static HAS_RUNNER: OnceLock = OnceLock::new(); + + let has_runner = HAS_RUNNER.get_or_init(|| { + let mut cmd = find_cargo(); + + let config_args = self.config.iter().map(|cfg| format!("--config={cfg}")); + + cmd.args(config_args) + .args([ + "config", + "-Zunstable-options", + "get", + "target.armv6k-nintendo-3ds.runner", + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + + if self.build_args.verbose { + print_command(&cmd); + } - if let Some(cfg) = config { - eprintln!("Running 3dslink"); - link(cfg, self, verbose); + cmd.status().map_or(false, |status| status.success()) + }); + + if self.build_args.verbose { + eprintln!( + "Custom runner is {}configured", + if *has_runner { "" } else { "not " } + ); } + + *has_runner } } @@ -407,13 +477,13 @@ impl Test { /// Callback for `cargo 3ds test`. /// /// This callback handles launching the application via `3dslink`. - fn callback(&self, config: &Option, verbose: bool) { + fn callback(&self, config: &Option) { if self.no_run { // If the tests don't have to run, use the "build" callback - self.run_args.build_args.callback(config, verbose); + self.run_args.build_args.callback(config); } else { // If the tests have to run, use the "run" callback - self.run_args.callback(config, verbose); + self.run_args.callback(config); } } } @@ -485,10 +555,10 @@ impl New { #[cfg(test)] mod tests { - use super::*; - use clap::CommandFactory; + use super::*; + #[test] fn verify_app() { Cargo::command().debug_assert(); @@ -517,9 +587,10 @@ mod tests { for (args, expected) in CASES { let mut cmd = CargoCmd::Build(Build { - cargo_args: RemainingArgs { + passthrough: RemainingArgs { args: args.iter().map(ToString::to_string).collect(), }, + verbose: false, }); assert_eq!( @@ -528,7 +599,7 @@ mod tests { ); if let CargoCmd::Build(build) = cmd { - assert_eq!(build.cargo_args.args, vec!["--foo", "bar"]); + assert_eq!(build.passthrough.args, vec!["--foo", "bar"]); } else { unreachable!(); } @@ -539,9 +610,10 @@ mod tests { fn extract_format_err() { for args in [&["--message-format=foo"][..], &["--message-format", "foo"]] { let mut cmd = CargoCmd::Build(Build { - cargo_args: RemainingArgs { + passthrough: RemainingArgs { args: args.iter().map(ToString::to_string).collect(), }, + verbose: false, }); assert!(cmd.extract_message_format().is_err()); @@ -564,25 +636,37 @@ mod tests { }, TestParam { input: &["--example", "hello-world", "--", "--do-stuff", "foo"], - expected_cargo: &["--example", "hello-world", "--"], + expected_cargo: &["--example", "hello-world"], expected_exe: &["--do-stuff", "foo"], }, TestParam { input: &["--lib", "--", "foo"], - expected_cargo: &["--lib", "--"], + expected_cargo: &["--lib"], expected_exe: &["foo"], }, TestParam { input: &["foo", "--", "bar"], - expected_cargo: &["foo", "--"], + expected_cargo: &["foo"], expected_exe: &["bar"], }, ] { - let Run { build_args, .. } = - Run::parse_from(std::iter::once(&"run").chain(param.input)); + let input: Vec<&str> = ["cargo", "3ds", "run"] + .iter() + .chain(param.input) + .copied() + .collect(); + + dbg!(&input); + let Cargo::Input(Input { + cmd: CargoCmd::Run(Run { build_args, .. }), + .. + }) = Cargo::try_parse_from(input).unwrap_or_else(|e| panic!("{e}")) + else { + panic!("parsed as something other than `run` subcommand") + }; - assert_eq!(build_args.cargo_args.cargo_args(), param.expected_cargo); - assert_eq!(build_args.cargo_args.exe_args(), param.expected_exe); + assert_eq!(build_args.passthrough.cargo_args(), param.expected_cargo); + assert_eq!(build_args.passthrough.exe_args(), param.expected_exe); } } } diff --git a/src/lib.rs b/src/lib.rs index 5d9f7cc..55291b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,10 @@ pub mod command; -use crate::command::{CargoCmd, Run}; +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 cargo_metadata::{Message, MetadataCommand}; use command::{Input, Test}; @@ -9,11 +13,7 @@ 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 crate::command::{CargoCmd, Run}; /// Build a command using [`make_cargo_build_command`] and execute it, /// parsing and returning the messages from the spawned process. @@ -21,7 +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(input: &Input, message_format: Option) -> (ExitStatus, Vec) { - let mut command = make_cargo_command(&input.cmd, &message_format); + let mut command = make_cargo_command(input, &message_format); if input.verbose { print_command(&command); @@ -62,11 +62,13 @@ pub fn run_cargo(input: &Input, message_format: Option) -> (ExitStatus, /// /// 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()); +pub fn make_cargo_command(input: &Input, message_format: &Option) -> Command { + let cmd = &input.cmd; - let mut command = Command::new(cargo); - command.arg(cmd.subcommand_name()); + let mut command = find_cargo(); + command + .args(input.config.iter().map(|cfg| format!("--config={cfg}"))) + .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. @@ -91,24 +93,40 @@ pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option) -> Co let sysroot = find_sysroot(); if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { eprintln!("No pre-build std found, using build-std"); + // TODO: should we consider always building `test` ? It's always needed + // if e.g. `test-runner` is a dependency, but not necessarily needed for + // production code. command.arg("-Z").arg("build-std"); } } - if matches!(cmd, CargoCmd::Test(_)) { - // 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"; + let cargo_args = cmd.cargo_args(); + command.args(cargo_args); + + if let CargoCmd::Test(test) = cmd { + let no_run_flag = if test.run_args.is_runner_configured() { + // TODO: should we persist here as well? Or maybe just let the user + // add that to RUSTDOCFLAGS if they want it... + "" + } else { + " --no-run" + }; + // Cargo doesn't like --no-run for doctests, so we have to plumb it in here + // https://github.com/rust-lang/rust/issues/87022 + let rustdoc_flags = std::env::var("RUSTDOCFLAGS").unwrap_or_default() + no_run_flag; command.env("RUSTDOCFLAGS", rustdoc_flags); } - let cargo_args = cmd.cargo_args(); + if let CargoCmd::Run(run) | CargoCmd::Test(Test { run_args: run, .. }) = &cmd { + if run.is_runner_configured() { + command + .arg("--") + .args(run.build_args.passthrough.exe_args()); + } + } command - .args(cargo_args) .stdout(Stdio::piped()) .stdin(Stdio::inherit()) .stderr(Stdio::inherit()); @@ -116,6 +134,12 @@ pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option) -> Co command } +/// Get the environment's version of cargo +fn find_cargo() -> Command { + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + Command::new(cargo) +} + 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())); @@ -129,8 +153,7 @@ fn print_command(command: &Command) { v.map_or_else(String::new, |s| shlex::quote(&s).to_string()) ); } - eprintln!(" {}", shlex::join(cmd_str.iter().map(String::as_str))); - eprintln!(); + eprintln!(" {}\n", shlex::join(cmd_str.iter().map(String::as_str))); } /// Finds the sysroot path of the current toolchain diff --git a/src/main.rs b/src/main.rs index 13b1d14..022e635 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,9 @@ +use std::process; + use cargo_3ds::command::Cargo; use cargo_3ds::{check_rust_version, run_cargo}; - use clap::Parser; -use std::process; - fn main() { check_rust_version(); @@ -24,5 +23,5 @@ fn main() { process::exit(status.code().unwrap_or(1)); } - input.cmd.run_callback(&messages, input.verbose); + input.cmd.run_callback(&messages); } From 6b4fd53f2086ea5ba846fe2fda60637f137fafbf Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Wed, 30 Aug 2023 23:02:55 -0400 Subject: [PATCH 3/3] Cleanup and add some more comments Minor tweaks like fn names, refactoring a little etc. Also update README to include an install command, with `--locked` as well. --- README.md | 14 +++++++ src/command.rs | 107 ++++++++++++++++++++++++++----------------------- src/lib.rs | 48 +++++++++++----------- 3 files changed, 94 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 15dc49c..71b3aa8 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,20 @@ Cargo command to work with Nintendo 3DS project binaries. Based on cargo-psp. +## Installation + +To install the latest release on : + +```sh +cargo install --locked cargo-3ds +``` + +To install the current `master` version of `cargo-3ds`: + +```sh +cargo install --locked --git https://github.com/rust3ds/cargo-3ds +``` + ## 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). diff --git a/src/command.rs b/src/command.rs index 68617e4..08a086c 100644 --- a/src/command.rs +++ b/src/command.rs @@ -6,7 +6,7 @@ use std::sync::OnceLock; use cargo_metadata::Message; use clap::{Args, Parser, Subcommand}; -use crate::{build_3dsx, build_smdh, find_cargo, get_metadata, link, print_command, CTRConfig}; +use crate::{build_3dsx, build_smdh, cargo, get_metadata, link, print_command, CTRConfig}; #[derive(Parser, Debug)] #[command(name = "cargo", bin_name = "cargo")] @@ -22,7 +22,7 @@ pub struct Input { pub cmd: CargoCmd, /// Print the exact commands `cargo-3ds` is running. Note that this does not - /// set the verbose flag for cargo itself. To set cargo's verbose flag, add + /// set the verbose flag for cargo itself. To set cargo's verbosity flag, add /// `-- -v` to the end of the command line. #[arg(long, short = 'v', global = true)] pub verbose: bool, @@ -165,21 +165,21 @@ impl CargoCmd { // We can't run 3DS executables on the host, but we want to respect // the user's "runner" configuration if set. - if test.doc { - eprintln!("Documentation tests requested, no 3dsx will be built"); + // + // If doctests were requested, `--no-run` will be rejected on the + // command line and must be set with RUSTDOCFLAGS instead: + // https://github.com/rust-lang/rust/issues/87022 + if !test.run_args.use_custom_runner() && !test.doc { + cargo_args.push("--no-run".to_string()); + } - // https://github.com/rust-lang/cargo/issues/7040 - cargo_args.append(&mut vec![ - "--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(), + if test.doc { + cargo_args.extend([ + "--doc".into(), + // https://github.com/rust-lang/cargo/issues/7040 + "-Z".into(), + "doctest-xcompile".into(), ]); - } else if !test.run_args.is_runner_configured() { - cargo_args.push("--no-run".to_string()); } cargo_args @@ -206,7 +206,7 @@ impl CargoCmd { match self { CargoCmd::Build(_) => "build", CargoCmd::Run(run) => { - if run.is_runner_configured() { + if run.use_custom_runner() { "run" } else { "build" @@ -228,23 +228,31 @@ 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(Test { doc: false, .. }) - ) + match self { + Self::Build(_) | CargoCmd::Run(_) => true, + &Self::Test(Test { doc, .. }) => { + if doc { + eprintln!("Documentation tests requested, no 3dsx will be built"); + false + } else { + true + } + } + _ => false, + } } /// Whether or not the resulting executable should be sent to the 3DS with /// `3dslink`. pub fn should_link_to_device(&self) -> bool { match self { - Self::Test(test) => !(test.no_run || test.run_args.is_runner_configured()), - Self::Run(run) => !run.is_runner_configured(), + Self::Test(Test { no_run: true, .. }) => false, + Self::Run(run) | Self::Test(Test { run_args: run, .. }) => !run.use_custom_runner(), _ => false, } } - pub const DEFAULT_MESSAGE_FORMAT: &str = "json-render-diagnostics"; + pub const DEFAULT_MESSAGE_FORMAT: &'static str = "json-render-diagnostics"; pub fn extract_message_format(&mut self) -> Result, String> { let cargo_args = match self { @@ -347,7 +355,7 @@ impl RemainingArgs { if let Some(split) = args.iter().position(|s| s == "--") { let second_half = args.split_off(split + 1); - // take off the "--" arg we found + // take off the "--" arg we found, we'll add one later if needed args.pop(); (args, second_half) @@ -421,7 +429,7 @@ impl Run { // Run the normal "build" callback self.build_args.callback(config); - if !self.is_runner_configured() { + if !self.use_custom_runner() { if let Some(cfg) = config { eprintln!("Running 3dslink"); link(cfg, self, self.build_args.verbose); @@ -431,45 +439,44 @@ impl Run { /// Returns whether the cargo environment has `target.armv6k-nintendo-3ds.runner` /// configured. This will only be checked once during the lifetime of the program, - /// and takes into account the usual ways Cargo looks for + /// and takes into account the usual ways Cargo looks for its /// [configuration](https://doc.rust-lang.org/cargo/reference/config.html): /// /// - `.cargo/config.toml` /// - Environment variables /// - Command-line `--config` overrides - pub fn is_runner_configured(&self) -> bool { + pub fn use_custom_runner(&self) -> bool { static HAS_RUNNER: OnceLock = OnceLock::new(); - let has_runner = HAS_RUNNER.get_or_init(|| { - let mut cmd = find_cargo(); - - let config_args = self.config.iter().map(|cfg| format!("--config={cfg}")); - - cmd.args(config_args) - .args([ - "config", - "-Zunstable-options", - "get", - "target.armv6k-nintendo-3ds.runner", - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()); + let &custom_runner_configured = HAS_RUNNER.get_or_init(|| { + let mut cmd = cargo(&self.config); + cmd.args([ + // https://github.com/rust-lang/cargo/issues/9301 + "-Z", + "unstable-options", + "config", + "get", + "target.armv6k-nintendo-3ds.runner", + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()); if self.build_args.verbose { print_command(&cmd); } + // `cargo config get` exits zero if the config exists, or nonzero otherwise cmd.status().map_or(false, |status| status.success()) }); if self.build_args.verbose { eprintln!( "Custom runner is {}configured", - if *has_runner { "" } else { "not " } + if custom_runner_configured { "" } else { "not " } ); } - *has_runner + custom_runner_configured } } @@ -488,13 +495,13 @@ impl Test { } } -const TOML_CHANGES: &str = "ctru-rs = { git = \"https://github.com/rust3ds/ctru-rs\" } +const TOML_CHANGES: &str = r#"ctru-rs = { git = "https://github.com/rust3ds/ctru-rs" } [package.metadata.cargo-3ds] -romfs_dir = \"romfs\" -"; +romfs_dir = "romfs" +"#; -const CUSTOM_MAIN_RS: &str = "use ctru::prelude::*; +const CUSTOM_MAIN_RS: &str = r#"use ctru::prelude::*; fn main() { ctru::use_panic_handler(); @@ -504,8 +511,8 @@ fn main() { let gfx = Gfx::new().unwrap(); let _console = Console::new(gfx.top_screen.borrow_mut()); - println!(\"Hello, World!\"); - println!(\"\\x1b[29;16HPress Start to exit\"); + println!("Hello, World!"); + println!("\x1b[29;16HPress Start to exit"); while apt.main_loop() { gfx.wait_for_vblank(); @@ -516,7 +523,7 @@ fn main() { } } } -"; +"#; impl New { /// Callback for `cargo 3ds new`. diff --git a/src/lib.rs b/src/lib.rs index 55291b2..f3b70ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,16 +63,14 @@ pub fn run_cargo(input: &Input, message_format: Option) -> (ExitStatus, /// 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(input: &Input, message_format: &Option) -> Command { - let cmd = &input.cmd; + let cargo_cmd = &input.cmd; - let mut command = find_cargo(); - command - .args(input.config.iter().map(|cfg| format!("--config={cfg}"))) - .arg(cmd.subcommand_name()); + let mut command = cargo(&input.config); + command.arg(cargo_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() { + if cargo_cmd.should_compile() { let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() + &format!( " -L{}/libctru/lib -lctru", @@ -93,33 +91,30 @@ pub fn make_cargo_command(input: &Input, message_format: &Option) -> Com let sysroot = find_sysroot(); if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { eprintln!("No pre-build std found, using build-std"); - // TODO: should we consider always building `test` ? It's always needed - // if e.g. `test-runner` is a dependency, but not necessarily needed for - // production code. - command.arg("-Z").arg("build-std"); + // Always building the test crate is not ideal, but we don't know if the + // crate being built uses #![feature(test)], so we build it just in case. + command.arg("-Z").arg("build-std=std,test"); } } - let cargo_args = cmd.cargo_args(); - command.args(cargo_args); - - if let CargoCmd::Test(test) = cmd { - let no_run_flag = if test.run_args.is_runner_configured() { - // TODO: should we persist here as well? Or maybe just let the user - // add that to RUSTDOCFLAGS if they want it... + if let CargoCmd::Test(test) = cargo_cmd { + let no_run_flag = if test.run_args.use_custom_runner() { "" } else { + // We don't support running doctests by default, but cargo doesn't like + // --no-run for doctests, so we have to plumb it in via RUSTDOCFLAGS " --no-run" }; - // Cargo doesn't like --no-run for doctests, so we have to plumb it in here - // https://github.com/rust-lang/rust/issues/87022 + // RUSTDOCFLAGS is simply ignored if --doc wasn't passed, so we always set it. let rustdoc_flags = std::env::var("RUSTDOCFLAGS").unwrap_or_default() + no_run_flag; command.env("RUSTDOCFLAGS", rustdoc_flags); } - if let CargoCmd::Run(run) | CargoCmd::Test(Test { run_args: run, .. }) = &cmd { - if run.is_runner_configured() { + command.args(cargo_cmd.cargo_args()); + + if let CargoCmd::Run(run) | CargoCmd::Test(Test { run_args: run, .. }) = &cargo_cmd { + if run.use_custom_runner() { command .arg("--") .args(run.build_args.passthrough.exe_args()); @@ -134,10 +129,12 @@ pub fn make_cargo_command(input: &Input, message_format: &Option) -> Com command } -/// Get the environment's version of cargo -fn find_cargo() -> Command { +/// Build a `cargo` command with the given `--config` flags. +fn cargo(config: &[String]) -> Command { let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); - Command::new(cargo) + let mut cmd = Command::new(cargo); + cmd.args(config.iter().map(|cfg| format!("--config={cfg}"))); + cmd } fn print_command(command: &Command) { @@ -181,7 +178,8 @@ pub fn check_rust_version() { eprintln!("cargo-3ds requires a nightly rustc version."); eprintln!( "Please run `rustup override set nightly` to use nightly in the \ - current directory." + current directory, or use `cargo +nightly 3ds` to use it for a \ + single invocation." ); process::exit(1); }