diff --git a/src/bin/cargo/cli.rs b/src/bin/cargo/cli.rs index a32e01626ed..f43bd2a948a 100644 --- a/src/bin/cargo/cli.rs +++ b/src/bin/cargo/cli.rs @@ -9,7 +9,19 @@ use super::commands; use command_prelude::*; pub fn main(config: &mut Config) -> CliResult { - let args = cli().get_matches_safe()?; + let args = match cli().get_matches_safe() { + Ok(args) => args, + Err(e) => { + if e.kind == clap::ErrorKind::UnrecognizedSubcommand { + // An unrecognized subcommand might be an external subcommand. + let cmd = &e.info.as_ref().unwrap()[0].to_owned(); + return super::execute_external_subcommand(config, cmd, &[cmd, "--help"]) + .map_err(|_| e.into()); + } else { + return Err(e)?; + } + } + }; if args.value_of("unstable-features") == Some("help") { println!( diff --git a/src/doc/src/reference/external-tools.md b/src/doc/src/reference/external-tools.md index 0ba2c518684..d996858b254 100644 --- a/src/doc/src/reference/external-tools.md +++ b/src/doc/src/reference/external-tools.md @@ -91,13 +91,24 @@ the `.d` files alongside the artifacts. Cargo is designed to be extensible with new subcommands without having to modify Cargo itself. This is achieved by translating a cargo invocation of the form cargo `(?[^ ]+)` into an invocation of an external tool -`cargo-${command}` that then needs to be present in one of the user's `$PATH` -directories. +`cargo-${command}`. The external tool must be present in one of the user's +`$PATH` directories. -Custom subcommand may use `CARGO` environment variable to call back to +When Cargo invokes a custom subcommand, the first argument to the subcommand +will be the filename of the custom subcommand, as usual. The second argument +will be the subcommand name itself. For example, the second argument would be +`${command}` when invoking `cargo-${command}`. Any additional arguments on the +command line will be forwarded unchanged. + +Cargo can also display the help output of a custom subcommand with `cargo help +${command}`. Cargo assumes that the subcommand will print a help message if its +third argument is `--help`. So, `cargo help ${command}` would invoke +`cargo-${command} ${command} --help`. + +Custom subcommands may use the `CARGO` environment variable to call back to Cargo. Alternatively, it can link to `cargo` crate as a library, but this approach has drawbacks: -* Cargo as a library is unstable, API changes without deprecation, +* Cargo as a library is unstable: the API may change without deprecation -* versions of Cargo library and Cargo binary may be different. +* versions of the linked Cargo library may be different from the Cargo binary diff --git a/tests/testsuite/cargo_command.rs b/tests/testsuite/cargo_command.rs index d5ea78fa923..819dca3da40 100644 --- a/tests/testsuite/cargo_command.rs +++ b/tests/testsuite/cargo_command.rs @@ -322,6 +322,29 @@ fn cargo_help() { ); } +#[test] +fn cargo_help_external_subcommand() { + Package::new("cargo-fake-help", "1.0.0") + .file( + "src/main.rs", + r#" + fn main() { + if ::std::env::args().nth(2) == Some(String::from("--help")) { + println!("fancy help output"); + } + }"#, + ) + .publish(); + assert_that( + cargo_process().args(&["install", "cargo-fake-help"]), + execs().with_status(0), + ); + assert_that( + cargo_process().args(&["help", "fake-help"]), + execs().with_status(0).with_stdout("fancy help output\n") + ); +} + #[test] fn explain() { assert_that(