From 9b6b79dff273cb8aca1926a8675c7695c8a296b4 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Thu, 29 Jun 2023 18:56:03 +0200 Subject: [PATCH 1/7] Callback refactor and cargo-new --- src/command.rs | 211 +++++++++++++++++++++++++++++++++++++++++++------ src/lib.rs | 67 ++++++++++++---- src/main.rs | 20 +---- 3 files changed, 239 insertions(+), 59 deletions(-) diff --git a/src/command.rs b/src/command.rs index 7877819..847cd33 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,11 @@ +use std::fs; +use std::io::Read; + +use cargo_metadata::Message; use clap::{Args, Parser, Subcommand}; +use crate::{CTRConfig, build_3dsx, build_smdh, get_metadata, link}; + #[derive(Parser, Debug)] #[command(name = "cargo", bin_name = "cargo")] pub enum Cargo { @@ -23,7 +29,7 @@ pub struct Input { #[command(allow_external_subcommands = true)] pub enum CargoCmd { /// Builds an executable suitable to run on a 3DS (3dsx). - Build(RemainingArgs), + Build(Build), /// Builds an executable and sends it to a device with `3dslink`. Run(Run), @@ -34,6 +40,9 @@ pub enum CargoCmd { /// unit tests (which require a custom test runner). Test(Test), + /// Sets up a new cargo project suitable to run on a 3DS. + New(New), + // 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. @@ -61,19 +70,10 @@ pub struct RemainingArgs { } #[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, - - /// If set, documentation tests will be built instead of unit tests. - /// This implies `--no-run`. - #[arg(long)] - pub doc: bool, - - // The test command uses a superset of the same arguments as Run. +pub struct Build { + // Passthrough cargo options. #[command(flatten)] - pub run_args: Run, + pub cargo_args: RemainingArgs, } #[derive(Parser, Debug)] @@ -101,12 +101,42 @@ pub struct Run { #[arg(long)] pub retries: Option, - // Passthrough cargo options. + // Passthrough `cargo build` options. + #[command(flatten)] + pub build_args: Build, +} + +#[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(Parser, Debug)] +pub struct New { + /// If set, the built executable will not be sent to the device to run it. + #[arg(required = true)] + pub path: String, + + // The test command uses a superset of the same arguments as Run. #[command(flatten)] pub cargo_args: RemainingArgs, } impl CargoCmd { + /// Whether or not this command should compile any code, and thus needs import the custom environment configuration (e.g. RUSTFLAGS, target, std). + pub fn should_compile(&self) -> bool { + matches!( + self, + Self::Build(_) | Self::Run(_) | Self::Test(_) | Self::Passthrough(_) + ) + } + /// Whether or not this command should build a 3DSX executable file. pub fn should_build_3dsx(&self) -> bool { matches!( @@ -183,6 +213,31 @@ impl CargoCmd { Ok(None) } } + + /// Runs the custom callback *after* the cargo command, depending on the type of command launched. + /// + /// # Examples + /// + /// - `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]) { + let config = if self.should_build_3dsx() { + eprintln!("Getting metadata"); + + get_metadata(messages) + } else { + CTRConfig::default() + }; + + // Run callback only for commands that use it + match self { + Self::Build(cmd) => cmd.callback(&config), + Self::Run(cmd) => cmd.callback(&config), + Self::Test(cmd) => cmd.callback(&config), + Self::New(cmd) => cmd.callback(), + _ => (), + } + } } impl RemainingArgs { @@ -205,6 +260,19 @@ impl RemainingArgs { } } +impl Build { + /// Callback for `cargo 3ds build`. + /// + /// This callback handles building the application as a `.3dsx` file. + fn callback(&self, config: &CTRConfig) { + eprintln!("Building smdh:{}", config.path_smdh().display()); + build_smdh(config); + + eprintln!("Building 3dsx: {}", config.path_3dsx().display()); + build_3dsx(config); + } +} + impl Run { /// Get the args to pass to `3dslink` based on these options. pub fn get_3dslink_args(&self) -> Vec { @@ -226,7 +294,7 @@ impl Run { args.push("--server".to_string()); } - let exe_args = self.cargo_args.exe_args(); + let exe_args = self.build_args.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... @@ -246,6 +314,97 @@ impl Run { args } + + /// Callback for `cargo 3ds run`. + /// + /// This callback handles launching the application via `3dslink`. + fn callback(&self, config: &CTRConfig) { + // Run the normal "build" callback + self.build_args.callback(config); + + eprintln!("Running 3dslink"); + link(config, self); + } +} + +impl Test { + /// Callback for `cargo 3ds test`. + /// + /// This callback handles launching the application via `3dslink`. + fn callback(&self, config: &CTRConfig) { + if self.no_run { + // If the tests don't have to run, use the "build" callback + self.run_args.build_args.callback(config) + } else { + // If the tests have to run, use the "run" callback + self.run_args.callback(config) + } + } +} + +const TOML_CHANGES: &str = "ctru-rs = { git = \"https://github.com/rust3ds/ctru-rs\"} + +[package.metadata.cargo-3ds] +romfs_dir = \"romfs\" +"; + +const CUSTOM_MAIN_RS: &str = "use ctru::prelude::*; + +fn main() { + ctru::use_panic_handler(); + + let apt = Apt::new().unwrap(); + let mut hid = Hid::new().unwrap(); + let gfx = Gfx::new().unwrap(); + let _console = Console::new(gfx.top_screen.borrow_mut()); + + println!(\"Hello, World!\"); + println!(\"\\x1b[29;16HPress Start to exit\"); + + while apt.main_loop() { + gfx.wait_for_vblank(); + + hid.scan_input(); + if hid.keys_down().contains(KeyPad::START) { + break; + } + } +} +"; + +impl New { + /// Callback for `cargo 3ds new`. + /// + /// This callback handles the custom environment modifications when creating a new 3DS project. + fn callback(&self) { + // Commmit changes to the project only if is meant to be a binary + if self.cargo_args.args.contains(&"--lib".to_string()) { + return; + } + + // Attain a canonicalised path for the new project and it's TOML manifest + let project_path = fs::canonicalize(&self.path).unwrap(); + let toml_path = project_path.join("Cargo.toml"); + let romfs_path = project_path.join("romfs"); + let main_rs_path = project_path.join("src/main.rs"); + + // Create the "romfs" directory + fs::create_dir(romfs_path).unwrap(); + + // Read the contents of `Cargo.toml` to a string + let mut buf = String::new(); + fs::File::open(&toml_path) + .unwrap() + .read_to_string(&mut buf) + .unwrap(); + + // Add the custom changes to the TOML + let buf = buf + TOML_CHANGES; + fs::write(&toml_path, buf).unwrap(); + + // Add the custom changes to the main.rs file + fs::write(main_rs_path, CUSTOM_MAIN_RS).unwrap(); + } } #[cfg(test)] @@ -281,8 +440,10 @@ mod tests { ]; for (args, expected) in CASES { - let mut cmd = CargoCmd::Build(RemainingArgs { - args: args.iter().map(ToString::to_string).collect(), + let mut cmd = CargoCmd::Build(Build { + cargo_args: RemainingArgs { + args: args.iter().map(ToString::to_string).collect(), + }, }); assert_eq!( @@ -290,8 +451,8 @@ mod tests { expected.map(ToString::to_string) ); - if let CargoCmd::Build(args) = cmd { - assert_eq!(args.args, vec!["--foo", "bar"]); + if let CargoCmd::Build(build) = cmd { + assert_eq!(build.cargo_args.args, vec!["--foo", "bar"]); } else { unreachable!(); } @@ -301,8 +462,10 @@ mod tests { #[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(), + let mut cmd = CargoCmd::Build(Build { + cargo_args: RemainingArgs { + args: args.iter().map(ToString::to_string).collect(), + }, }); assert!(cmd.extract_message_format().is_err()); @@ -339,11 +502,11 @@ mod tests { expected_exe: &["bar"], }, ] { - let Run { cargo_args, .. } = + let Run { build_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); + assert_eq!(build_args.cargo_args.cargo_args(), param.expected_cargo); + assert_eq!(build_args.cargo_args.exe_args(), param.expected_exe); } } } diff --git a/src/lib.rs b/src/lib.rs index 8fde305..13a3f91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ pub mod command; -use crate::command::CargoCmd; +use crate::command::{CargoCmd, Run}; use cargo_metadata::{Message, MetadataCommand}; use command::Test; @@ -21,7 +21,12 @@ use std::{env, io, process}; /// For commands that produce an executable output, this function will build the /// `.elf` binary that can be used to create other 3ds files. pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, Vec) { - let mut command = make_cargo_build_command(cmd, &message_format); + let mut command = if cmd.should_compile() { + make_cargo_build_command(cmd, &message_format) + } else { + make_cargo_generic_command(cmd) + }; + let mut process = command.spawn().unwrap(); let command_stdout = process.stdout.take().unwrap(); @@ -53,7 +58,7 @@ pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, (process.wait().unwrap(), messages) } -/// Create the cargo build command, but don't execute it. +/// 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 { let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() @@ -69,6 +74,7 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) 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 @@ -89,8 +95,8 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) } let cargo_args = match cmd { - CargoCmd::Build(cargo_args) => cargo_args.cargo_args(), - CargoCmd::Run(run) => run.cargo_args.cargo_args(), + 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 @@ -116,6 +122,39 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) test.run_args.cargo_args.cargo_args() } 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"), }; command @@ -180,7 +219,7 @@ pub fn check_rust_version() { } } -/// Parses messages returned by the executed cargo command from [`build_elf`]. +/// Parses messages returned by "build" cargo commands (such as `cargo 3ds build` or `cargo 3ds run`). /// The returned [`CTRConfig`] is then used for further building in and execution /// in [`build_smdh`], [`build_3dsx`], and [`link`]. pub fn get_metadata(messages: &[Message]) -> CTRConfig { @@ -309,13 +348,7 @@ 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, cmd: &CargoCmd) { - let run_args = match cmd { - CargoCmd::Run(run) => run, - CargoCmd::Test(test) => &test.run_args, - _ => unreachable!(), - }; - +pub fn link(config: &CTRConfig, run_args: &Run) { let mut process = Command::new("3dslink") .arg(config.path_3dsx()) .args(run_args.get_3dslink_args()) @@ -410,8 +443,8 @@ impl fmt::Display for CommitDate { } const MINIMUM_COMMIT_DATE: CommitDate = CommitDate { - year: 2022, - month: 6, - day: 15, + year: 2023, + month: 5, + day: 31, }; -const MINIMUM_RUSTC_VERSION: Version = Version::new(1, 63, 0); +const MINIMUM_RUSTC_VERSION: Version = Version::new(1, 70, 0); diff --git a/src/main.rs b/src/main.rs index e943262..bac5948 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use cargo_3ds::command::Cargo; -use cargo_3ds::{build_3dsx, build_smdh, check_rust_version, get_metadata, link, run_cargo}; +use cargo_3ds::{check_rust_version, run_cargo}; use clap::Parser; @@ -24,21 +24,5 @@ fn main() { process::exit(status.code().unwrap_or(1)); } - if !input.cmd.should_build_3dsx() { - return; - } - - eprintln!("Getting metadata"); - let app_conf = get_metadata(&messages); - - 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 input.cmd.should_link_to_device() { - eprintln!("Running 3dslink"); - link(&app_conf, &input.cmd); - } + input.cmd.run_callback(&messages); } From b899ff9d7ee65eb19b179b765b9e6bad00076ed9 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Thu, 29 Jun 2023 19:02:45 +0200 Subject: [PATCH 2/7] Remove useless RUSTFLAGS changes --- README.md | 2 +- src/command.rs | 6 +++--- src/lib.rs | 6 ------ 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 92dc166..88c68bb 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ 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. +with the proper `--target armv6k-nintendo-3ds` set. ### Basic Examples diff --git a/src/command.rs b/src/command.rs index 847cd33..26b9a9d 100644 --- a/src/command.rs +++ b/src/command.rs @@ -4,7 +4,7 @@ use std::io::Read; use cargo_metadata::Message; use clap::{Args, Parser, Subcommand}; -use crate::{CTRConfig, build_3dsx, build_smdh, get_metadata, link}; +use crate::{build_3dsx, build_smdh, get_metadata, link, CTRConfig}; #[derive(Parser, Debug)] #[command(name = "cargo", bin_name = "cargo")] @@ -46,7 +46,7 @@ pub enum CargoCmd { // 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. + /// Run any other `cargo` command with custom building tailored for the 3DS. #[command(external_subcommand, name = "COMMAND")] Passthrough(Vec), } @@ -129,7 +129,7 @@ pub struct New { } impl CargoCmd { - /// Whether or not this command should compile any code, and thus needs import the custom environment configuration (e.g. RUSTFLAGS, target, std). + /// 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!( self, diff --git a/src/lib.rs b/src/lib.rs index 13a3f91..156e01f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,11 +61,6 @@ pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, /// 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 { - let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() - + &format!( - " -L{}/libctru/lib -lctru", - env::var("DEVKITPRO").expect("DEVKITPRO is not defined as an environment variable") - ); let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let sysroot = find_sysroot(); let mut command = Command::new(cargo); @@ -78,7 +73,6 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) }; command - .env("RUSTFLAGS", rust_flags) .arg(cmd_str) .arg("--target") .arg("armv6k-nintendo-3ds") From eaa93126f4cd750d02a64e5007efa1663dc54e84 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Fri, 30 Jun 2023 10:45:48 +0200 Subject: [PATCH 3/7] Merge different commands under a common infrastructure --- src/command.rs | 56 ++++++++++++++++++++--- src/lib.rs | 119 +++++++++++-------------------------------------- 2 files changed, 77 insertions(+), 98 deletions(-) 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) From 9b483a8589f5071e70067652d51bed493ef018bd Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Fri, 30 Jun 2023 11:16:57 +0200 Subject: [PATCH 4/7] Rebased changes --- src/command.rs | 31 +++++++++++++++++++++++++------ src/lib.rs | 10 ++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/command.rs b/src/command.rs index 484d9c8..d8bc288 100644 --- a/src/command.rs +++ b/src/command.rs @@ -112,6 +112,11 @@ pub struct Test { #[arg(long)] pub no_run: bool, + /// If set, documentation tests will be built instead of unit tests. + /// This implies `--no-run`. + #[arg(long)] + pub doc: bool, + // The test command uses a superset of the same arguments as Run. #[command(flatten)] pub run_args: Run, @@ -135,10 +140,23 @@ impl CargoCmd { 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()); + + // 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 + cargo_args.append(&mut vec![ + "--doc".to_string(), + "-Z".to_string(), + "doctest-xcompile".to_string(), + ]); + } else { + cargo_args.push("--no-run".to_string()); + } cargo_args } @@ -198,10 +216,11 @@ impl CargoCmd { pub fn extract_message_format(&mut self) -> Result, String> { let cargo_args = match self { - Self::Build(args) => &mut args.args, - Self::Run(run) => &mut run.cargo_args.args, + Self::Build(build) => &mut build.cargo_args.args, + Self::Run(run) => &mut run.build_args.cargo_args.args, + Self::New(new) => &mut new.cargo_args.args, + Self::Test(test) => &mut test.run_args.build_args.cargo_args.args, Self::Passthrough(args) => args, - Self::Test(test) => &mut test.run_args.cargo_args.args, }; let format = Self::extract_message_format_from_args(cargo_args)?; diff --git a/src/lib.rs b/src/lib.rs index acdb129..0af6431 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,16 @@ pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option) -> Co } } + 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"; + + command.env("RUSTDOCFLAGS", rustdoc_flags); + } + let cargo_args = cmd.cargo_args(); command From a0fcbbc57e3302ddf889b614fd37357702bb6f03 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Fri, 30 Jun 2023 11:22:15 +0200 Subject: [PATCH 5/7] Fixed --help docs --- src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.rs b/src/command.rs index d8bc288..c311992 100644 --- a/src/command.rs +++ b/src/command.rs @@ -124,7 +124,7 @@ pub struct Test { #[derive(Parser, Debug)] pub struct New { - /// If set, the built executable will not be sent to the device to run it. + /// Path of the new project. #[arg(required = true)] pub path: String, From abe088a78c5bc10bdfda88083944e87d1a4f2aac Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Fri, 30 Jun 2023 11:33:35 +0200 Subject: [PATCH 6/7] Update README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 88c68bb..15dc49c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Commands: Builds an executable and sends it to a device with `3dslink` test Builds a test executable and sends it to a device with `3dslink` + new + Sets up a new cargo project suitable to run on a 3DS help Print this message or the help of the given subcommand(s) @@ -38,6 +40,7 @@ with the proper `--target armv6k-nintendo-3ds` set. * `cargo 3ds check --verbose` * `cargo 3ds run --release --example foo` * `cargo 3ds test --no-run` +* `cargo 3ds new my-new-project --edition 2021` ### Running executables From dd5ed8a480d4cb370e0816064ad3e6a504313e84 Mon Sep 17 00:00:00 2001 From: Andrea Ciliberti Date: Tue, 11 Jul 2023 17:00:38 +0200 Subject: [PATCH 7/7] Fix new Cargo.toml --- src/command.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.rs b/src/command.rs index c311992..7580419 100644 --- a/src/command.rs +++ b/src/command.rs @@ -405,7 +405,7 @@ impl Test { } } -const TOML_CHANGES: &str = "ctru-rs = { git = \"https://github.com/rust3ds/ctru-rs\"} +const TOML_CHANGES: &str = "ctru-rs = { git = \"https://github.com/rust3ds/ctru-rs\" } [package.metadata.cargo-3ds] romfs_dir = \"romfs\"