diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0163641 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @rust3ds/active diff --git a/README.md b/README.md index 05ac64d..92dc166 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,18 @@ executable arguments, *another* `--` can be used. For example: This works without two `--` instances because `--verbose` begins the set of `cargo` arguments and ends the set of 3DS-specific arguments. +### Caveats + +Due to the fact that only one executable at a time can be sent with `3dslink`, +by default only the "last" executable built will be used. If a `test` or `run` +command builds more than one binary, you may need to filter it in order to run +the executable you want. + +Doc tests sort of work, but `cargo-3ds` uses a number of unstable cargo and +rustdoc features to make them work, so the output won't be as pretty and will +require some manual workarounds to actually run the tests and see output from them. +For now, `cargo 3ds test --doc` will not build a 3dsx file or use `3dslink` at all. + ## License This project is distributed under the MIT license or the Apache-2.0 license. diff --git a/src/command.rs b/src/command.rs index a014b31..7877819 100644 --- a/src/command.rs +++ b/src/command.rs @@ -66,6 +66,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, @@ -104,15 +109,18 @@ pub struct Run { 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(_)) + matches!( + self, + Self::Build(_) | Self::Run(_) | Self::Test(Test { doc: 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 { - CargoCmd::Test(test) => !test.no_run, - CargoCmd::Run(_) => true, + Self::Test(test) => !test.no_run, + Self::Run(_) => true, _ => false, } } @@ -120,12 +128,26 @@ impl CargoCmd { pub const DEFAULT_MESSAGE_FORMAT: &str = "json-render-diagnostics"; pub fn extract_message_format(&mut self) -> Result, String> { - Self::extract_message_format_from_args(match self { - CargoCmd::Build(args) => &mut args.args, - CargoCmd::Run(run) => &mut run.cargo_args.args, - CargoCmd::Test(test) => &mut test.run_args.cargo_args.args, - CargoCmd::Passthrough(args) => args, - }) + let cargo_args = match self { + Self::Build(args) => &mut args.args, + Self::Run(run) => &mut run.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)?; + if format.is_some() { + return Ok(format); + } + + if let Self::Test(Test { doc: true, .. }) = self { + // We don't care about JSON output for doctests since we're not + // building any 3dsx etc. Just use the default output as it's more + // readable compared to DEFAULT_MESSAGE_FORMAT + Ok(Some(String::from("human"))) + } else { + Ok(None) + } } fn extract_message_format_from_args( diff --git a/src/lib.rs b/src/lib.rs index aeb1bfd..8fde305 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod command; use crate::command::CargoCmd; use cargo_metadata::{Message, MetadataCommand}; +use command::Test; use rustc_version::Channel; use semver::Version; use serde::Deserialize; @@ -26,15 +27,23 @@ pub fn run_cargo(cmd: &CargoCmd, message_format: Option) -> (ExitStatus, let mut tee_reader; let mut stdout_reader; - let buf_reader: &mut dyn BufRead = if message_format.is_none() { - stdout_reader = BufReader::new(command_stdout); - &mut stdout_reader - } else { - // The user presumably cares about the message format, so we should + + let buf_reader: &mut dyn BufRead = match (message_format, cmd) { + // The user presumably cares about the message format if set, so we should // copy stuff to stdout like they expect. We can still extract the executable // information out of it that we need for 3dsxtool etc. - tee_reader = BufReader::new(TeeReader::new(command_stdout, io::stdout())); - &mut tee_reader + (Some(_), _) | + // Rustdoc unfortunately prints to stdout for compile errors, so + // we also use a tee when building doc tests too. + // Possibly related: https://github.com/rust-lang/rust/issues/75135 + (None, CargoCmd::Test(Test { doc: true, .. })) => { + tee_reader = BufReader::new(TeeReader::new(command_stdout, io::stdout())); + &mut tee_reader + } + _ => { + stdout_reader = BufReader::new(command_stdout); + &mut stdout_reader + } }; let messages = Message::parse_stream(buf_reader) @@ -75,7 +84,7 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) ); if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { - eprintln!("No pre-build std found, using build-std"); + eprintln!("No pre-built std found, using build-std"); command.arg("-Z").arg("build-std"); } @@ -83,9 +92,27 @@ pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option) CargoCmd::Build(cargo_args) => cargo_args.cargo_args(), CargoCmd::Run(run) => run.cargo_args.cargo_args(), CargoCmd::Test(test) => { - // We can't run 3DS executables on the host, so pass --no-run here and - // send the executable with 3dslink later, if the user wants - command.arg("--no-run"); + // 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() } CargoCmd::Passthrough(other) => &other[1..], @@ -146,10 +173,7 @@ pub fn check_rust_version() { }; if old_version || old_commit { - eprintln!( - "cargo-3ds requires rustc nightly version >= {}", - MINIMUM_COMMIT_DATE, - ); + eprintln!("cargo-3ds requires rustc nightly version >= {MINIMUM_COMMIT_DATE}"); eprintln!("Please run `rustup update nightly` to upgrade your nightly version"); process::exit(1);