| use crate::command_prelude::*; |
| |
| use anyhow::anyhow; |
| use cargo::core::{GitReference, SourceId, Workspace}; |
| use cargo::ops; |
| use cargo::util::IntoUrl; |
| |
| use cargo_util::paths; |
| |
| pub fn cli() -> Command { |
| subcommand("install") |
| .about("Install a Rust binary. Default location is $HOME/.cargo/bin") |
| .arg( |
| Arg::new("crate") |
| .value_parser(clap::builder::NonEmptyStringValueParser::new()) |
| .num_args(0..), |
| ) |
| .arg( |
| opt("version", "Specify a version to install") |
| .alias("vers") |
| .value_name("VERSION") |
| .requires("crate"), |
| ) |
| .arg( |
| opt("index", "Registry index to install from") |
| .value_name("INDEX") |
| .requires("crate") |
| .conflicts_with_all(&["git", "path", "registry"]), |
| ) |
| .arg( |
| opt("registry", "Registry to use") |
| .value_name("REGISTRY") |
| .requires("crate") |
| .conflicts_with_all(&["git", "path", "index"]), |
| ) |
| .arg( |
| opt("git", "Git URL to install the specified crate from") |
| .value_name("URL") |
| .conflicts_with_all(&["path", "index", "registry"]), |
| ) |
| .arg( |
| opt("branch", "Branch to use when installing from git") |
| .value_name("BRANCH") |
| .requires("git"), |
| ) |
| .arg( |
| opt("tag", "Tag to use when installing from git") |
| .value_name("TAG") |
| .requires("git"), |
| ) |
| .arg( |
| opt("rev", "Specific commit to use when installing from git") |
| .value_name("SHA") |
| .requires("git"), |
| ) |
| .arg( |
| opt("path", "Filesystem path to local crate to install") |
| .value_name("PATH") |
| .conflicts_with_all(&["git", "index", "registry"]), |
| ) |
| .arg(opt("root", "Directory to install packages into").value_name("DIR")) |
| .arg(flag("force", "Force overwriting existing crates or binaries").short('f')) |
| .arg(flag("no-track", "Do not save tracking information")) |
| .arg(flag( |
| "list", |
| "list all installed packages and their versions", |
| )) |
| .arg_ignore_rust_version() |
| .arg_message_format() |
| .arg_quiet() |
| .arg_targets_bins_examples( |
| "Install only the specified binary", |
| "Install all binaries", |
| "Install only the specified example", |
| "Install all examples", |
| ) |
| .arg_features() |
| .arg_jobs() |
| .arg(flag( |
| "debug", |
| "Build in debug mode (with the 'dev' profile) instead of release mode", |
| )) |
| .arg_profile("Install artifacts with the specified profile") |
| .arg_target_triple("Build for the target triple") |
| .arg_target_dir() |
| .arg_timings() |
| .after_help("Run `cargo help install` for more detailed information.\n") |
| } |
| |
| pub fn exec(config: &mut Config, args: &ArgMatches) -> CliResult { |
| let path = args.value_of_path("path", config); |
| if let Some(path) = &path { |
| config.reload_rooted_at(path)?; |
| } else { |
| // TODO: Consider calling set_search_stop_path(home). |
| config.reload_rooted_at(config.home().clone().into_path_unlocked())?; |
| } |
| |
| // In general, we try to avoid normalizing paths in Cargo, |
| // but in these particular cases we need it to fix rust-lang/cargo#10283. |
| // (Handle `SourceId::for_path` and `Workspace::new`, |
| // but not `Config::reload_rooted_at` which is always cwd) |
| let path = path.map(|p| paths::normalize_path(&p)); |
| |
| let version = args.get_one::<String>("version").map(String::as_str); |
| let krates = args |
| .get_many::<String>("crate") |
| .unwrap_or_default() |
| .map(|k| resolve_crate(k, version)) |
| .collect::<crate::CargoResult<Vec<_>>>()?; |
| |
| for (crate_name, _) in krates.iter() { |
| if let Some(toolchain) = crate_name.strip_prefix("+") { |
| return Err(anyhow!( |
| "invalid character `+` in package name: `+{toolchain}` |
| Use `cargo +{toolchain} install` if you meant to use the `{toolchain}` toolchain." |
| ) |
| .into()); |
| } |
| } |
| |
| let mut from_cwd = false; |
| |
| let source = if let Some(url) = args.get_one::<String>("git") { |
| let url = url.into_url()?; |
| let gitref = if let Some(branch) = args.get_one::<String>("branch") { |
| GitReference::Branch(branch.clone()) |
| } else if let Some(tag) = args.get_one::<String>("tag") { |
| GitReference::Tag(tag.clone()) |
| } else if let Some(rev) = args.get_one::<String>("rev") { |
| GitReference::Rev(rev.clone()) |
| } else { |
| GitReference::DefaultBranch |
| }; |
| SourceId::for_git(&url, gitref)? |
| } else if let Some(path) = &path { |
| SourceId::for_path(path)? |
| } else if krates.is_empty() { |
| from_cwd = true; |
| SourceId::for_path(config.cwd())? |
| } else if let Some(index) = args.get_one::<String>("index") { |
| SourceId::for_registry(&index.into_url()?)? |
| } else if let Some(registry) = args.registry(config)? { |
| SourceId::alt_registry(config, ®istry)? |
| } else { |
| SourceId::crates_io(config)? |
| }; |
| |
| let root = args.get_one::<String>("root").map(String::as_str); |
| |
| // We only provide workspace information for local crate installation from |
| // one of the following sources: |
| // - From current working directory (only work for edition 2015). |
| // - From a specific local file path (from `--path` arg). |
| // |
| // This workspace information is for emitting helpful messages from |
| // `ArgMatchesExt::compile_options` and won't affect the actual compilation. |
| let workspace = if from_cwd { |
| args.workspace(config).ok() |
| } else if let Some(path) = &path { |
| Workspace::new(&path.join("Cargo.toml"), config).ok() |
| } else { |
| None |
| }; |
| |
| let mut compile_opts = args.compile_options( |
| config, |
| CompileMode::Build, |
| workspace.as_ref(), |
| ProfileChecking::Custom, |
| )?; |
| |
| compile_opts.build_config.requested_profile = |
| args.get_profile_name(config, "release", ProfileChecking::Custom)?; |
| |
| if args.flag("list") { |
| ops::install_list(root, config)?; |
| } else { |
| ops::install( |
| config, |
| root, |
| krates, |
| source, |
| from_cwd, |
| &compile_opts, |
| args.flag("force"), |
| args.flag("no-track"), |
| )?; |
| } |
| Ok(()) |
| } |
| |
| fn resolve_crate<'k>( |
| mut krate: &'k str, |
| mut version: Option<&'k str>, |
| ) -> crate::CargoResult<(&'k str, Option<&'k str>)> { |
| if let Some((k, v)) = krate.split_once('@') { |
| if version.is_some() { |
| anyhow::bail!("cannot specify both `@{v}` and `--version`"); |
| } |
| if k.is_empty() { |
| // by convention, arguments starting with `@` are response files |
| anyhow::bail!("missing crate name for `@{v}`"); |
| } |
| krate = k; |
| version = Some(v); |
| } |
| Ok((krate, version)) |
| } |