diff --git a/src/command.rs b/src/command.rs index 3bd79fa..00972d3 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,10 +1,7 @@ -// TODO: docstrings for everything!!! - use clap::{Args, Parser, Subcommand}; #[derive(Parser, Debug)] -#[command(name = "cargo")] -#[command(bin_name = "cargo")] +#[command(name = "cargo", bin_name = "cargo")] pub enum Cargo { #[command(name = "3ds")] Input(Input), @@ -13,20 +10,44 @@ pub enum Cargo { #[derive(Args, Debug)] #[command(version, about)] pub struct Input { - /// The cargo command to run. This command will be forwarded to the real - /// `cargo` with the appropriate arguments for a 3DS executable. #[command(subcommand)] - pub cmd: CargoCommand, + pub cmd: CargoCmd, +} + +/// The cargo command to run. This command will be forwarded to the real +/// `cargo` with the appropriate arguments for a 3DS executable. +/// +/// If another command is passed which is not recognized, it will be passed +/// through unmodified to `cargo` with RUSTFLAGS set for the 3DS. +#[derive(Subcommand, Debug)] +#[command(allow_external_subcommands = true)] +pub enum CargoCmd { + /// Builds an executable suitable to run on a 3DS (3dsx). + Build(CargoArgs), + + /// Builds an executable and sends it to a device with `3dslink`. + Run(Run), + + /// Builds a test executable and sends it to a device with `3dslink`. + /// + /// This can be used with `--test` for integration tests, or `--lib` for + /// unit tests (which require a custom test runner). + Test(Test), - /// Don't actually run any commands, just echo them to the console. - /// This is mostly intended for testing. - #[arg(long, hide = true)] - pub dry_run: bool, + // NOTE: it seems docstring + name for external subcommands are not rendered + // in help, but we might as well set them here in case a future version of clap + // does include them in help text. + /// Run any other `cargo` command with RUSTFLAGS set for the 3DS. + #[command(external_subcommand, name = "COMMAND")] + Passthrough(Vec), +} +#[derive(Args, Debug)] +pub struct CargoArgs { /// Pass additional options through to the `cargo` command. /// /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` - /// options from cargo options. All args after `--` will be passed through + /// options from cargo options. Any argument after `--` will be passed through /// to cargo unmodified. /// /// If one of the arguments is itself `--`, the args following that will be @@ -36,50 +57,31 @@ pub struct Input { /// /// > cargo 3ds run -- -- arg1 arg2 #[arg(trailing_var_arg = true)] + #[arg(allow_hyphen_values = true)] #[arg(global = true)] - cargo_options: Vec, + #[arg(name = "CARGO_ARGS")] + args: Vec, } -#[derive(Subcommand, Debug)] -pub enum CargoCommand { - /// Builds an executable suitable to run on a 3DS (3dsx). - Build, - /// Equivalent to `cargo check`. - Check, - /// Equivalent to `cargo clippy`. - Clippy, - /// Equivalent to `cargo doc`. - Doc, - /// Builds an executable and sends it to a device with `3dslink`. - Run(Run), - /// Builds a test executable and sends it to a device with `3dslink`. - /// - /// This can be used with `--test` for integration tests, or `--lib` for - /// unit tests (which require a custom test runner). - Test(Test), - // - // TODO: this doesn't seem to work for some reason... - // #[command(external_subcommand)] - // Other(Vec), -} - -#[derive(Args, Debug)] +#[derive(Parser, Debug)] pub struct Test { /// If set, the built executable will not be sent to the device to run it. #[arg(long)] pub no_run: bool, + + // The test command uses a superset of the same arguments as Run. #[command(flatten)] pub run_args: Run, } -#[derive(Args, Debug)] +#[derive(Parser, Debug)] pub struct Run { /// Specify the IP address of the device to send the executable to. /// /// Corresponds to 3dslink's `--address` arg, which defaults to automatically /// finding the device. #[arg(long, short = 'a')] - pub address: Option, + pub address: Option, /// Set the 0th argument of the executable when running it. Corresponds to /// 3dslink's `--argv0` argument. @@ -88,7 +90,7 @@ pub struct Run { /// Start the 3dslink server after sending the executable. Corresponds to /// 3dslink's `--server` argument. - #[arg(long, short = 's')] + #[arg(long, short = 's', default_value_t = false)] pub server: bool, /// Set the number of tries when connecting to the device to send the executable. @@ -96,9 +98,13 @@ pub struct Run { // Can't use `short = 'r'` because that would conflict with cargo's `--release/-r` #[arg(long)] pub retries: Option, + + // Passthrough cargo options. + #[command(flatten)] + pub cargo_args: CargoArgs, } -impl Input { +impl CargoArgs { /// Get the args to be passed to the executable itself (not `cargo`). pub fn cargo_opts(&self) -> &[String] { self.split_args().0 @@ -110,15 +116,31 @@ impl Input { } fn split_args(&self) -> (&[String], &[String]) { - if let Some(split) = self.cargo_options.iter().position(|arg| arg == "--") { - let split = if &self.cargo_options[split] == "--" { + if let Some(split) = self + .args + .iter() + .position(|s| s == "--" || !s.starts_with('-')) + { + let split = if self.args[split] == "--" { split + 1 } else { split }; - self.cargo_options.split_at(split) + self.args.split_at(split) } else { - (&self.cargo_options[..], &[]) + (&self.args[..], &[]) } } } + +#[cfg(test)] +mod tests { + use super::*; + + use clap::CommandFactory; + + #[test] + fn verify_app() { + Cargo::command().debug_assert(); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1fcc0ac..0aa7ddf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,6 @@ -extern crate core; - pub mod command; -use crate::command::{CargoCommand, Input}; +use crate::command::{CargoCmd, Input}; use cargo_metadata::{Message, MetadataCommand}; use core::fmt; use rustc_version::Channel; @@ -22,11 +20,11 @@ pub fn get_should_link(input: &mut Input) -> bool { // When running compile only commands, don't link the executable to the 3ds. // Otherwise, link and run on the 3ds but do not run locally. match input.cmd { - CargoCommand::Run(_) => true, - CargoCommand::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { - // input.cargo_opts().push("--no-run".to_string()); - true - } + CargoCmd::Run(_) => true, + // CargoCmd::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { + // // input.cargo_opts().push("--no-run".to_string()); + // true + // } _ => false, } } @@ -35,41 +33,42 @@ pub fn get_should_link(input: &mut Input) -> bool { /// default to `json-render-diagnostics`. pub fn get_message_format(input: &mut Input) -> String { // Checks for a position within the args where '--message-format' is located - if let Some(pos) = input - .cargo_opts() - .iter() - .position(|s| s.starts_with("--message-format")) - { - // Remove the arg from list - let arg = input.cargo_opts()[pos].clone(); // TODO - - // Allows for usage of '--message-format=' and also using space separation. - // Check for a '=' delimiter and use the second half of the split as the format, - // otherwise remove next arg which is now at the same position as the original flag. - let format = if let Some((_, format)) = arg.split_once('=') { - format.to_string() - } else { - input.cargo_opts()[pos].clone() // TODO - }; - - // Non-json formats are not supported so the executable exits. - if format.starts_with("json") { - format - } else { - eprintln!("error: non-JSON `message-format` is not supported"); - process::exit(1); - } - } else { - // Default to 'json-render-diagnostics' - DEFAULT_MESSAGE_FORMAT.to_string() - } + todo!(); + // if let Some(pos) = input + // .cargo_opts() + // .iter() + // .position(|s| s.starts_with("--message-format")) + // { + // // Remove the arg from list + // let arg = input.cargo_opts()[pos].clone(); // TODO + + // // Allows for usage of '--message-format=' and also using space separation. + // // Check for a '=' delimiter and use the second half of the split as the format, + // // otherwise remove next arg which is now at the same position as the original flag. + // let format = if let Some((_, format)) = arg.split_once('=') { + // format.to_string() + // } else { + // input.cargo_opts()[pos].clone() // TODO + // }; + + // // Non-json formats are not supported so the executable exits. + // if format.starts_with("json") { + // format + // } else { + // eprintln!("error: non-JSON `message-format` is not supported"); + // process::exit(1); + // } + // } else { + // // Default to 'json-render-diagnostics' + // DEFAULT_MESSAGE_FORMAT.to_string() + // } } /// Build the elf that will be used to create other 3ds files. /// The command built from [`make_cargo_build_command`] is executed /// and the messages from the spawned process are parsed and returned. pub fn build_elf( - cmd: CargoCommand, + cmd: CargoCmd, message_format: &str, args: &Vec, ) -> (ExitStatus, Vec) { @@ -100,7 +99,7 @@ pub fn build_elf( /// Create the cargo build command, but don't execute it. /// If there is no pre-built std detected in the sysroot, `build-std` is used. pub fn make_cargo_build_command( - cmd: CargoCommand, + cmd: CargoCmd, message_format: &str, args: &Vec, ) -> Command { @@ -114,11 +113,9 @@ pub fn make_cargo_build_command( let mut command = Command::new(cargo); let cmd = match cmd { - CargoCommand::Build | CargoCommand::Run(_) => "build", - CargoCommand::Test(_) => "test", - CargoCommand::Check => "check", - CargoCommand::Clippy => "clippy", - CargoCommand::Doc => "doc", + CargoCmd::Build(_) | CargoCmd::Run(_) => "build", + CargoCmd::Test(_) => "test", + CargoCmd::Passthrough(_) => todo!(), }; command diff --git a/src/main.rs b/src/main.rs index dd5501b..a457646 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use cargo_3ds::command::Cargo; +use cargo_3ds::command::{Cargo, CargoCmd, Input, Run, Test}; use cargo_3ds::{ build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata, get_should_link, link, }; -use clap::Parser; +use clap::{CommandFactory, FromArgMatches, Parser}; use std::process; fn main() { @@ -12,10 +12,21 @@ fn main() { let Cargo::Input(mut input) = Cargo::parse(); dbg!(&input); - dbg!(input.cargo_opts()); - dbg!(input.exe_args()); - // let should_link = get_should_link(&mut input); + let cargo_args = match &input.cmd { + CargoCmd::Build(cargo_args) + | CargoCmd::Run(Run { cargo_args, .. }) + | CargoCmd::Test(Test { + run_args: Run { cargo_args, .. }, + .. + }) => cargo_args, + CargoCmd::Passthrough(other) => todo!(), + }; + + dbg!(cargo_args.cargo_opts()); + dbg!(cargo_args.exe_args()); + + // let // let message_format = get_message_format(&mut input); // let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts);