use std::path::PathBuf;
use anyhow::{bail, Result};
use cargo_component::{
commands::{AddCommand, BindingsCommand, NewCommand, PublishCommand, UpdateCommand},
config::{CargoArguments, Config},
load_component_metadata, load_metadata, run_cargo_command,
};
use cargo_component_core::{
command::{CACHE_DIR_ENV_VAR, CONFIG_FILE_ENV_VAR},
terminal::{Color, Terminal, Verbosity},
};
use clap::{CommandFactory, Parser};
fn version() -> &'static str {
option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION"))
}
const BUILTIN_COMMANDS: &[&str] = &[
"add",
"bindings",
"component", "help",
"init",
"new",
"publish",
"remove",
"rm",
"update",
"vendor",
"yank",
];
const UNSUPPORTED_COMMANDS: &[&str] = &[
"install",
"login",
"logout",
"owner",
"package",
"search",
"uninstall",
];
const AFTER_HELP: &str = "Unrecognized subcommands will be passed to cargo verbatim after\n\
relevant component bindings are updated.\n\
\n\
See `cargo help` for more information on available cargo commands.";
#[derive(Parser)]
#[clap(
bin_name = "cargo",
version,
propagate_version = true,
arg_required_else_help = true,
after_help = AFTER_HELP
)]
#[command(version = version())]
enum CargoComponent {
#[clap(subcommand, hide = true, after_help = AFTER_HELP)]
Component(Command), #[clap(flatten)]
Command(Command),
}
#[derive(Parser)]
enum Command {
Add(AddCommand),
Bindings(BindingsCommand),
New(NewCommand),
Update(UpdateCommand),
Publish(PublishCommand),
}
fn detect_subcommand() -> Option<String> {
let mut iter = std::env::args().skip(1).peekable();
if let Some(arg) = iter.peek() {
if arg == "component" {
iter.next().unwrap();
}
}
for arg in iter {
if arg == "--" {
break;
}
if !arg.starts_with('-') {
return Some(arg);
}
}
None
}
#[tokio::main]
async fn main() -> Result<()> {
pretty_env_logger::init_custom_env("CARGO_COMPONENT_LOG");
let subcommand = detect_subcommand();
match subcommand.as_deref() {
Some(cmd) if BUILTIN_COMMANDS.contains(&cmd) => {
if let Err(e) = match CargoComponent::parse() {
CargoComponent::Component(cmd) | CargoComponent::Command(cmd) => match cmd {
Command::Add(cmd) => cmd.exec().await,
Command::Bindings(cmd) => cmd.exec().await,
Command::New(cmd) => cmd.exec().await,
Command::Update(cmd) => cmd.exec().await,
Command::Publish(cmd) => cmd.exec().await,
},
} {
let terminal = Terminal::new(Verbosity::Normal, Color::Auto);
terminal.error(format!("{e:?}"))?;
std::process::exit(1);
}
}
Some(cmd) if UNSUPPORTED_COMMANDS.contains(&cmd) => {
let terminal = Terminal::new(Verbosity::Normal, Color::Auto);
terminal.error(format!(
"command `{cmd}` is not supported by `cargo component`\n\n\
use `cargo {cmd}` instead"
))?;
std::process::exit(1);
}
None => {
CargoComponent::parse();
CargoComponent::command().print_long_help()?;
}
_ => {
let cargo_args = CargoArguments::parse()?;
let cache_dir = std::env::var(CACHE_DIR_ENV_VAR).map(PathBuf::from).ok();
let config_file = std::env::var(CONFIG_FILE_ENV_VAR).map(PathBuf::from).ok();
let config = Config::new(
Terminal::new(
if cargo_args.quiet {
Verbosity::Quiet
} else {
match cargo_args.verbose {
0 => Verbosity::Normal,
_ => Verbosity::Verbose,
}
},
cargo_args.color.unwrap_or_default(),
),
config_file,
)
.await?;
let metadata = load_metadata(cargo_args.manifest_path.as_deref())?;
let packages = load_component_metadata(
&metadata,
cargo_args.packages.iter(),
cargo_args.workspace,
)?;
if packages.is_empty() {
bail!(
"manifest `{path}` contains no package or the workspace has no members",
path = metadata.workspace_root.join("Cargo.toml")
);
}
let spawn_args: Vec<_> = std::env::args().skip(1).collect();
let client = config.client(cache_dir, cargo_args.offline).await?;
if let Err(e) = run_cargo_command(
client,
&config,
&metadata,
&packages,
subcommand.as_deref(),
&cargo_args,
&spawn_args,
)
.await
{
config.terminal().error(format!("{e:?}"))?;
std::process::exit(1);
}
}
}
Ok(())
}