Browse Source

Merge pull request #43 from rust3ds/feature/support-custom-runner

Add support for custom cargo runner
pull/46/head
Meziu 1 year ago committed by GitHub
parent
commit
aa0d754d79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/ci.yml
  2. 1
      .gitignore
  3. 440
      Cargo.lock
  4. 14
      README.md
  5. 251
      src/command.rs
  6. 69
      src/lib.rs
  7. 7
      src/main.rs

2
.github/workflows/ci.yml

@ -61,7 +61,7 @@ jobs:
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: install command: install
args: --path . args: --locked --path .
- name: Create new project - name: Create new project
run: cargo 3ds new app --bin run: cargo 3ds new app --bin

1
.gitignore vendored

@ -1,3 +1,2 @@
target target
Cargo.lock
.idea .idea

440
Cargo.lock generated

@ -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"

14
README.md

@ -2,6 +2,20 @@
Cargo command to work with Nintendo 3DS project binaries. Based on cargo-psp. Cargo command to work with Nintendo 3DS project binaries. Based on cargo-psp.
## Installation
To install the latest release on <https://crates.io>:
```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 ## 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). 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).

251
src/command.rs

@ -1,10 +1,12 @@
use std::fs; use std::fs;
use std::io::Read; use std::io::Read;
use std::process::Stdio;
use std::sync::OnceLock;
use cargo_metadata::Message; use cargo_metadata::Message;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use crate::{build_3dsx, build_smdh, get_metadata, link, CTRConfig}; use crate::{build_3dsx, build_smdh, cargo, get_metadata, link, print_command, CTRConfig};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "cargo", bin_name = "cargo")] #[command(name = "cargo", bin_name = "cargo")]
@ -20,9 +22,15 @@ pub struct Input {
pub cmd: CargoCmd, pub cmd: CargoCmd,
/// Print the exact commands `cargo-3ds` is running. Note that this does not /// Print the exact commands `cargo-3ds` is running. Note that this does not
/// set the verbose flag for cargo itself. /// set the verbose flag for cargo itself. To set cargo's verbosity flag, add
#[arg(long, short = 'v')] /// `-- -v` to the end of the command line.
#[arg(long, short = 'v', global = true)]
pub verbose: bool, 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<String>,
} }
/// Run a cargo command. COMMAND will be forwarded to the real /// 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. /// used to disambiguate cargo arguments from executable arguments.
/// For example, `cargo 3ds run -- -- xyz` runs an executable with the argument /// For example, `cargo 3ds run -- -- xyz` runs an executable with the argument
/// `xyz`. /// `xyz`.
#[arg(trailing_var_arg = true)] #[arg(
#[arg(allow_hyphen_values = true)] trailing_var_arg = true,
#[arg(global = true)] allow_hyphen_values = true,
#[arg(name = "CARGO_ARGS")] value_name = "CARGO_ARGS"
)]
args: Vec<String>, args: Vec<String>,
} }
#[derive(Parser, Debug)] #[derive(Args, Debug)]
pub struct Build { pub struct Build {
#[arg(from_global)]
pub verbose: bool,
// Passthrough cargo options. // Passthrough cargo options.
#[command(flatten)] #[command(flatten)]
pub cargo_args: RemainingArgs, pub passthrough: RemainingArgs,
} }
#[derive(Parser, Debug)] #[derive(Args, Debug)]
pub struct Run { pub struct Run {
/// Specify the IP address of the device to send the executable to. /// Specify the IP address of the device to send the executable to.
/// ///
@ -109,16 +121,20 @@ pub struct Run {
// Passthrough `cargo build` options. // Passthrough `cargo build` options.
#[command(flatten)] #[command(flatten)]
pub build_args: Build, pub build_args: Build,
#[arg(from_global)]
config: Vec<String>,
} }
#[derive(Parser, Debug)] #[derive(Args, Debug)]
pub struct Test { pub struct Test {
/// If set, the built executable will not be sent to the device to run it. /// If set, the built executable will not be sent to the device to run it.
#[arg(long)] #[arg(long)]
pub no_run: bool, pub no_run: bool,
/// If set, documentation tests will be built instead of unit tests. /// 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)] #[arg(long)]
pub doc: bool, pub doc: bool,
@ -127,7 +143,7 @@ pub struct Test {
pub run_args: Run, pub run_args: Run,
} }
#[derive(Parser, Debug)] #[derive(Args, Debug)]
pub struct New { pub struct New {
/// Path of the new project. /// Path of the new project.
#[arg(required = true)] #[arg(required = true)]
@ -142,29 +158,28 @@ impl CargoCmd {
/// Returns the additional arguments run by the "official" cargo subcommand. /// Returns the additional arguments run by the "official" cargo subcommand.
pub fn cargo_args(&self) -> Vec<String> { pub fn cargo_args(&self) -> Vec<String> {
match self { match self {
CargoCmd::Build(build) => build.cargo_args.cargo_args(), CargoCmd::Build(build) => build.passthrough.cargo_args(),
CargoCmd::Run(run) => run.build_args.cargo_args.cargo_args(), CargoCmd::Run(run) => run.build_args.passthrough.cargo_args(),
CargoCmd::Test(test) => { 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, but we want to respect
// the user's "runner" configuration if set.
//
// 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());
}
// 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 { if test.doc {
eprintln!("Documentation tests requested, no 3dsx will be built or run"); cargo_args.extend([
"--doc".into(),
// https://github.com/rust-lang/cargo/issues/7040 // https://github.com/rust-lang/cargo/issues/7040
cargo_args.append(&mut vec![ "-Z".into(),
"--doc".to_string(), "doctest-xcompile".into(),
"-Z".to_string(),
"doctest-xcompile".to_string(),
// doctests don't automatically build the `test` crate,
// so we manually specify it on the command line
"-Z".to_string(),
"build-std=std,test".to_string(),
]); ]);
} else {
cargo_args.push("--no-run".to_string());
} }
cargo_args cargo_args
@ -185,10 +200,18 @@ impl CargoCmd {
/// # Notes /// # Notes
/// ///
/// This is not equivalent to the lowercase name of the [`CargoCmd`] variant. /// 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 { pub fn subcommand_name(&self) -> &str {
match self { match self {
CargoCmd::Build(_) | CargoCmd::Run(_) => "build", CargoCmd::Build(_) => "build",
CargoCmd::Run(run) => {
if run.use_custom_runner() {
"run"
} else {
"build"
}
}
CargoCmd::Test(_) => "test", CargoCmd::Test(_) => "test",
CargoCmd::New(_) => "new", CargoCmd::New(_) => "new",
CargoCmd::Passthrough(cmd) => &cmd[0], CargoCmd::Passthrough(cmd) => &cmd[0],
@ -205,30 +228,38 @@ impl CargoCmd {
/// Whether or not this command should build a 3DSX executable file. /// Whether or not this command should build a 3DSX executable file.
pub fn should_build_3dsx(&self) -> bool { pub fn should_build_3dsx(&self) -> bool {
matches!( match self {
self, Self::Build(_) | CargoCmd::Run(_) => true,
Self::Build(_) | Self::Run(_) | Self::Test(Test { doc: false, .. }) &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 /// Whether or not the resulting executable should be sent to the 3DS with
/// `3dslink`. /// `3dslink`.
pub fn should_link_to_device(&self) -> bool { pub fn should_link_to_device(&self) -> bool {
match self { match self {
Self::Test(test) => !test.no_run, Self::Test(Test { no_run: true, .. }) => false,
Self::Run(_) => true, Self::Run(run) | Self::Test(Test { run_args: run, .. }) => !run.use_custom_runner(),
_ => false, _ => 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<Option<String>, String> { pub fn extract_message_format(&mut self) -> Result<Option<String>, String> {
let cargo_args = match self { let cargo_args = match self {
Self::Build(build) => &mut build.cargo_args.args, Self::Build(build) => &mut build.passthrough.args,
Self::Run(run) => &mut run.build_args.cargo_args.args, Self::Run(run) => &mut run.build_args.passthrough.args,
Self::New(new) => &mut new.cargo_args.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, Self::Passthrough(args) => args,
}; };
@ -287,7 +318,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 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. /// - `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 // Process the metadata only for commands that have it/use it
let config = if self.should_build_3dsx() { let config = if self.should_build_3dsx() {
eprintln!("Getting metadata"); eprintln!("Getting metadata");
@ -299,9 +330,9 @@ impl CargoCmd {
// Run callback only for commands that use it // Run callback only for commands that use it
match self { match self {
Self::Build(cmd) => cmd.callback(&config, verbose), Self::Build(cmd) => cmd.callback(&config),
Self::Run(cmd) => cmd.callback(&config, verbose), Self::Run(cmd) => cmd.callback(&config),
Self::Test(cmd) => cmd.callback(&config, verbose), Self::Test(cmd) => cmd.callback(&config),
Self::New(cmd) => cmd.callback(), Self::New(cmd) => cmd.callback(),
_ => (), _ => (),
} }
@ -309,7 +340,7 @@ impl CargoCmd {
} }
impl RemainingArgs { 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<String> { pub fn cargo_args(&self) -> Vec<String> {
self.split_args().0 self.split_args().0
} }
@ -324,6 +355,8 @@ impl RemainingArgs {
if let Some(split) = args.iter().position(|s| s == "--") { if let Some(split) = args.iter().position(|s| s == "--") {
let second_half = args.split_off(split + 1); let second_half = args.split_off(split + 1);
// take off the "--" arg we found, we'll add one later if needed
args.pop();
(args, second_half) (args, second_half)
} else { } else {
@ -336,13 +369,13 @@ impl Build {
/// Callback for `cargo 3ds build`. /// Callback for `cargo 3ds build`.
/// ///
/// This callback handles building the application as a `.3dsx` file. /// This callback handles building the application as a `.3dsx` file.
fn callback(&self, config: &Option<CTRConfig>, verbose: bool) { fn callback(&self, config: &Option<CTRConfig>) {
if let Some(config) = config { if let Some(config) = config {
eprintln!("Building smdh: {}", config.path_smdh().display()); eprintln!("Building smdh: {}", config.path_smdh().display());
build_smdh(config, verbose); build_smdh(config, self.verbose);
eprintln!("Building 3dsx: {}", config.path_3dsx().display()); eprintln!("Building 3dsx: {}", config.path_3dsx().display());
build_3dsx(config, verbose); build_3dsx(config, self.verbose);
} }
} }
} }
@ -368,7 +401,7 @@ impl Run {
args.push("--server".to_string()); 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() { if !exe_args.is_empty() {
// For some reason 3dslink seems to want 2 instances of `--`, one // For some reason 3dslink seems to want 2 instances of `--`, one
// in front of all of the args like this... // in front of all of the args like this...
@ -392,14 +425,58 @@ impl Run {
/// Callback for `cargo 3ds run`. /// Callback for `cargo 3ds run`.
/// ///
/// This callback handles launching the application via `3dslink`. /// This callback handles launching the application via `3dslink`.
fn callback(&self, config: &Option<CTRConfig>, verbose: bool) { fn callback(&self, config: &Option<CTRConfig>) {
// Run the normal "build" callback // Run the normal "build" callback
self.build_args.callback(config, verbose); self.build_args.callback(config);
if !self.use_custom_runner() {
if let Some(cfg) = config {
eprintln!("Running 3dslink");
link(cfg, self, self.build_args.verbose);
}
}
}
if let Some(cfg) = config { /// Returns whether the cargo environment has `target.armv6k-nintendo-3ds.runner`
eprintln!("Running 3dslink"); /// configured. This will only be checked once during the lifetime of the program,
link(cfg, self, verbose); /// 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 use_custom_runner(&self) -> bool {
static HAS_RUNNER: OnceLock<bool> = OnceLock::new();
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 custom_runner_configured { "" } else { "not " }
);
} }
custom_runner_configured
} }
} }
@ -407,24 +484,24 @@ impl Test {
/// Callback for `cargo 3ds test`. /// Callback for `cargo 3ds test`.
/// ///
/// This callback handles launching the application via `3dslink`. /// This callback handles launching the application via `3dslink`.
fn callback(&self, config: &Option<CTRConfig>, verbose: bool) { fn callback(&self, config: &Option<CTRConfig>) {
if self.no_run { if self.no_run {
// If the tests don't have to run, use the "build" callback // 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 { } else {
// If the tests have to run, use the "run" callback // If the tests have to run, use the "run" callback
self.run_args.callback(config, verbose); self.run_args.callback(config);
} }
} }
} }
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] [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() { fn main() {
ctru::use_panic_handler(); ctru::use_panic_handler();
@ -434,8 +511,8 @@ fn main() {
let gfx = Gfx::new().unwrap(); let gfx = Gfx::new().unwrap();
let _console = Console::new(gfx.top_screen.borrow_mut()); let _console = Console::new(gfx.top_screen.borrow_mut());
println!(\"Hello, World!\"); println!("Hello, World!");
println!(\"\\x1b[29;16HPress Start to exit\"); println!("\x1b[29;16HPress Start to exit");
while apt.main_loop() { while apt.main_loop() {
gfx.wait_for_vblank(); gfx.wait_for_vblank();
@ -446,7 +523,7 @@ fn main() {
} }
} }
} }
"; "#;
impl New { impl New {
/// Callback for `cargo 3ds new`. /// Callback for `cargo 3ds new`.
@ -485,10 +562,10 @@ impl New {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use clap::CommandFactory; use clap::CommandFactory;
use super::*;
#[test] #[test]
fn verify_app() { fn verify_app() {
Cargo::command().debug_assert(); Cargo::command().debug_assert();
@ -517,9 +594,10 @@ mod tests {
for (args, expected) in CASES { for (args, expected) in CASES {
let mut cmd = CargoCmd::Build(Build { let mut cmd = CargoCmd::Build(Build {
cargo_args: RemainingArgs { passthrough: RemainingArgs {
args: args.iter().map(ToString::to_string).collect(), args: args.iter().map(ToString::to_string).collect(),
}, },
verbose: false,
}); });
assert_eq!( assert_eq!(
@ -528,7 +606,7 @@ mod tests {
); );
if let CargoCmd::Build(build) = cmd { if let CargoCmd::Build(build) = cmd {
assert_eq!(build.cargo_args.args, vec!["--foo", "bar"]); assert_eq!(build.passthrough.args, vec!["--foo", "bar"]);
} else { } else {
unreachable!(); unreachable!();
} }
@ -539,9 +617,10 @@ mod tests {
fn extract_format_err() { fn extract_format_err() {
for args in [&["--message-format=foo"][..], &["--message-format", "foo"]] { for args in [&["--message-format=foo"][..], &["--message-format", "foo"]] {
let mut cmd = CargoCmd::Build(Build { let mut cmd = CargoCmd::Build(Build {
cargo_args: RemainingArgs { passthrough: RemainingArgs {
args: args.iter().map(ToString::to_string).collect(), args: args.iter().map(ToString::to_string).collect(),
}, },
verbose: false,
}); });
assert!(cmd.extract_message_format().is_err()); assert!(cmd.extract_message_format().is_err());
@ -564,25 +643,37 @@ mod tests {
}, },
TestParam { TestParam {
input: &["--example", "hello-world", "--", "--do-stuff", "foo"], input: &["--example", "hello-world", "--", "--do-stuff", "foo"],
expected_cargo: &["--example", "hello-world", "--"], expected_cargo: &["--example", "hello-world"],
expected_exe: &["--do-stuff", "foo"], expected_exe: &["--do-stuff", "foo"],
}, },
TestParam { TestParam {
input: &["--lib", "--", "foo"], input: &["--lib", "--", "foo"],
expected_cargo: &["--lib", "--"], expected_cargo: &["--lib"],
expected_exe: &["foo"], expected_exe: &["foo"],
}, },
TestParam { TestParam {
input: &["foo", "--", "bar"], input: &["foo", "--", "bar"],
expected_cargo: &["foo", "--"], expected_cargo: &["foo"],
expected_exe: &["bar"], expected_exe: &["bar"],
}, },
] { ] {
let Run { build_args, .. } = let input: Vec<&str> = ["cargo", "3ds", "run"]
Run::parse_from(std::iter::once(&"run").chain(param.input)); .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.passthrough.cargo_args(), param.expected_cargo);
assert_eq!(build_args.cargo_args.exe_args(), param.expected_exe); assert_eq!(build_args.passthrough.exe_args(), param.expected_exe);
} }
} }
} }

69
src/lib.rs

@ -1,6 +1,10 @@
pub mod command; 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 cargo_metadata::{Message, MetadataCommand};
use command::{Input, Test}; use command::{Input, Test};
@ -9,11 +13,7 @@ use semver::Version;
use serde::Deserialize; use serde::Deserialize;
use tee::TeeReader; use tee::TeeReader;
use core::fmt; use crate::command::{CargoCmd, Run};
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio};
use std::{env, io, process};
/// Build 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. /// 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 /// For commands that produce an executable output, this function will build the
/// `.elf` binary that can be used to create other 3ds files. /// `.elf` binary that can be used to create other 3ds files.
pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus, Vec<Message>) { pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus, Vec<Message>) {
let mut command = make_cargo_command(&input.cmd, &message_format); let mut command = make_cargo_command(input, &message_format);
if input.verbose { if input.verbose {
print_command(&command); print_command(&command);
@ -62,15 +62,15 @@ pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus,
/// ///
/// For "build" commands (which compile code, such as `cargo 3ds build` or `cargo 3ds clippy`), /// 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. /// 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<String>) -> Command { pub fn make_cargo_command(input: &Input, message_format: &Option<String>) -> Command {
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let cargo_cmd = &input.cmd;
let mut command = Command::new(cargo); let mut command = cargo(&input.config);
command.arg(cmd.subcommand_name()); command.arg(cargo_cmd.subcommand_name());
// Any command that needs to compile code will run under this environment. // 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. // 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() let rust_flags = env::var("RUSTFLAGS").unwrap_or_default()
+ &format!( + &format!(
" -L{}/libctru/lib -lctru", " -L{}/libctru/lib -lctru",
@ -91,24 +91,37 @@ pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option<String>) -> Co
let sysroot = find_sysroot(); let sysroot = find_sysroot();
if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() {
eprintln!("No pre-build std found, using build-std"); eprintln!("No pre-build std found, using build-std");
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");
} }
} }
if matches!(cmd, CargoCmd::Test(_)) { if let CargoCmd::Test(test) = cargo_cmd {
// Cargo doesn't like --no-run for doctests: let no_run_flag = if test.run_args.use_custom_runner() {
// https://github.com/rust-lang/rust/issues/87022 ""
let rustdoc_flags = std::env::var("RUSTDOCFLAGS").unwrap_or_default() } else {
// TODO: should we make this output directory depend on profile etc? // We don't support running doctests by default, but cargo doesn't like
+ " --no-run --persist-doctests target/doctests"; // --no-run for doctests, so we have to plumb it in via RUSTDOCFLAGS
" --no-run"
};
// 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); command.env("RUSTDOCFLAGS", rustdoc_flags);
} }
let cargo_args = cmd.cargo_args(); 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());
}
}
command command
.args(cargo_args)
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stderr(Stdio::inherit()); .stderr(Stdio::inherit());
@ -116,6 +129,14 @@ pub fn make_cargo_command(cmd: &CargoCmd, message_format: &Option<String>) -> Co
command 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());
let mut cmd = Command::new(cargo);
cmd.args(config.iter().map(|cfg| format!("--config={cfg}")));
cmd
}
fn print_command(command: &Command) { fn print_command(command: &Command) {
let mut cmd_str = vec![command.get_program().to_string_lossy().to_string()]; 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())); cmd_str.extend(command.get_args().map(|s| s.to_string_lossy().to_string()));
@ -129,8 +150,7 @@ fn print_command(command: &Command) {
v.map_or_else(String::new, |s| shlex::quote(&s).to_string()) v.map_or_else(String::new, |s| shlex::quote(&s).to_string())
); );
} }
eprintln!(" {}", shlex::join(cmd_str.iter().map(String::as_str))); eprintln!(" {}\n", shlex::join(cmd_str.iter().map(String::as_str)));
eprintln!();
} }
/// Finds the sysroot path of the current toolchain /// Finds the sysroot path of the current toolchain
@ -158,7 +178,8 @@ pub fn check_rust_version() {
eprintln!("cargo-3ds requires a nightly rustc version."); eprintln!("cargo-3ds requires a nightly rustc version.");
eprintln!( eprintln!(
"Please run `rustup override set nightly` to use nightly in the \ "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); process::exit(1);
} }

7
src/main.rs

@ -1,10 +1,9 @@
use std::process;
use cargo_3ds::command::Cargo; use cargo_3ds::command::Cargo;
use cargo_3ds::{check_rust_version, run_cargo}; use cargo_3ds::{check_rust_version, run_cargo};
use clap::Parser; use clap::Parser;
use std::process;
fn main() { fn main() {
check_rust_version(); check_rust_version();
@ -24,5 +23,5 @@ fn main() {
process::exit(status.code().unwrap_or(1)); process::exit(status.code().unwrap_or(1));
} }
input.cmd.run_callback(&messages, input.verbose); input.cmd.run_callback(&messages);
} }

Loading…
Cancel
Save