mirror of
https://github.com/rust-lang/cargo
synced 2024-08-25 02:25:46 +00:00
Add install-upgrade.
This commit is contained in:
parent
6bdb9d3b11
commit
e023a6672b
|
@ -35,6 +35,7 @@ Available unstable (nightly-only) flags:
|
|||
-Z offline -- Offline mode that does not perform network requests
|
||||
-Z unstable-options -- Allow the usage of unstable options such as --registry
|
||||
-Z config-profile -- Read profiles from .cargo/config files
|
||||
-Z install-upgrade -- `cargo install` will upgrade instead of failing
|
||||
|
||||
Run with 'cargo -Z [FLAG] [SUBCOMMAND]'"
|
||||
);
|
||||
|
|
|
@ -46,6 +46,10 @@ pub fn cli() -> App {
|
|||
))
|
||||
.arg_jobs()
|
||||
.arg(opt("force", "Force overwriting existing crates or binaries").short("f"))
|
||||
.arg(opt(
|
||||
"no-track",
|
||||
"Do not save tracking information (unstable)",
|
||||
))
|
||||
.arg_features()
|
||||
.arg(opt("debug", "Build in debug mode instead of release mode"))
|
||||
.arg_targets_bins_examples(
|
||||
|
@ -148,6 +152,12 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
|
|||
let version = args.value_of("version");
|
||||
let root = args.value_of("root");
|
||||
|
||||
if args.is_present("no-track") && !config.cli_unstable().install_upgrade {
|
||||
Err(failure::format_err!(
|
||||
"`--no-track` flag is unstable, pass `-Z install-upgrade` to enable it"
|
||||
))?;
|
||||
};
|
||||
|
||||
if args.is_present("list") {
|
||||
ops::install_list(root, config)?;
|
||||
} else {
|
||||
|
@ -159,6 +169,7 @@ pub fn exec(config: &mut Config, args: &ArgMatches<'_>) -> CliResult {
|
|||
version,
|
||||
&compile_opts,
|
||||
args.is_present("force"),
|
||||
args.is_present("no-track"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -328,6 +328,7 @@ pub struct CliUnstable {
|
|||
pub config_profile: bool,
|
||||
pub dual_proc_macros: bool,
|
||||
pub mtime_on_use: bool,
|
||||
pub install_upgrade: bool,
|
||||
}
|
||||
|
||||
impl CliUnstable {
|
||||
|
@ -372,6 +373,7 @@ impl CliUnstable {
|
|||
"config-profile" => self.config_profile = true,
|
||||
"dual-proc-macros" => self.dual_proc_macros = true,
|
||||
"mtime-on-use" => self.mtime_on_use = true,
|
||||
"install-upgrade" => self.install_upgrade = true,
|
||||
_ => failure::bail!("unknown `-Z` flag specified: {}", k),
|
||||
}
|
||||
|
||||
|
|
|
@ -3,18 +3,16 @@ use std::path::{Path, PathBuf};
|
|||
use std::sync::Arc;
|
||||
use std::{env, fs};
|
||||
|
||||
use failure::{bail, format_err};
|
||||
use tempfile::Builder as TempFileBuilder;
|
||||
|
||||
use crate::core::compiler::{DefaultExecutor, Executor};
|
||||
use crate::core::{Edition, Package, Source, SourceId};
|
||||
use crate::core::{PackageId, Workspace};
|
||||
use crate::core::{Edition, PackageId, Source, SourceId, Workspace};
|
||||
use crate::ops;
|
||||
use crate::ops::common_for_install_and_uninstall::*;
|
||||
use crate::ops::{self, CompileFilter};
|
||||
use crate::sources::{GitSource, SourceConfigMap};
|
||||
use crate::util::errors::{CargoResult, CargoResultExt};
|
||||
use crate::util::paths;
|
||||
use crate::util::Config;
|
||||
use crate::util::Filesystem;
|
||||
use crate::util::{paths, Config, Filesystem, Freshness};
|
||||
|
||||
struct Transaction {
|
||||
bins: Vec<PathBuf>,
|
||||
|
@ -42,6 +40,7 @@ pub fn install(
|
|||
vers: Option<&str>,
|
||||
opts: &ops::CompileOptions<'_>,
|
||||
force: bool,
|
||||
no_track: bool,
|
||||
) -> CargoResult<()> {
|
||||
let root = resolve_root(root, opts.config)?;
|
||||
let map = SourceConfigMap::new(opts.config)?;
|
||||
|
@ -56,6 +55,7 @@ pub fn install(
|
|||
vers,
|
||||
opts,
|
||||
force,
|
||||
no_track,
|
||||
true,
|
||||
)?;
|
||||
(true, false)
|
||||
|
@ -75,6 +75,7 @@ pub fn install(
|
|||
vers,
|
||||
opts,
|
||||
force,
|
||||
no_track,
|
||||
first,
|
||||
) {
|
||||
Ok(()) => succeeded.push(krate),
|
||||
|
@ -106,7 +107,7 @@ pub fn install(
|
|||
if installed_anything {
|
||||
// Print a warning that if this directory isn't in PATH that they won't be
|
||||
// able to run these commands.
|
||||
let dst = metadata(opts.config, &root)?.parent().join("bin");
|
||||
let dst = root.join("bin").into_path_unlocked();
|
||||
let path = env::var_os("PATH").unwrap_or_default();
|
||||
for path in env::split_paths(&path) {
|
||||
if path == dst {
|
||||
|
@ -122,7 +123,7 @@ pub fn install(
|
|||
}
|
||||
|
||||
if scheduled_error {
|
||||
failure::bail!("some crates failed to install");
|
||||
bail!("some crates failed to install");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -137,6 +138,7 @@ fn install_one(
|
|||
vers: Option<&str>,
|
||||
opts: &ops::CompileOptions<'_>,
|
||||
force: bool,
|
||||
no_track: bool,
|
||||
is_first_install: bool,
|
||||
) -> CargoResult<()> {
|
||||
let config = opts.config;
|
||||
|
@ -153,7 +155,7 @@ fn install_one(
|
|||
} else if source_id.is_path() {
|
||||
let mut src = path_source(source_id, config)?;
|
||||
if !src.path().is_dir() {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"`{}` is not a directory. \
|
||||
--path must point to a directory containing a Cargo.toml file.",
|
||||
src.path().display()
|
||||
|
@ -161,14 +163,14 @@ fn install_one(
|
|||
}
|
||||
if !src.path().join("Cargo.toml").exists() {
|
||||
if from_cwd {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"`{}` is not a crate root; specify a crate to \
|
||||
install from crates.io, or use --path or --git to \
|
||||
specify an alternate source",
|
||||
src.path().display()
|
||||
);
|
||||
} else {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"`{}` does not contain a Cargo.toml file. \
|
||||
--path must point to a directory containing a Cargo.toml file.",
|
||||
src.path().display()
|
||||
|
@ -187,7 +189,7 @@ fn install_one(
|
|||
config,
|
||||
is_first_install,
|
||||
&mut |_| {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"must specify a crate to install from \
|
||||
crates.io, or use --path or --git to \
|
||||
specify alternate source"
|
||||
|
@ -230,7 +232,7 @@ fn install_one(
|
|||
Use `cargo build` if you want to simply build the package.",
|
||||
)?
|
||||
} else {
|
||||
failure::bail!(
|
||||
bail!(
|
||||
"Using `cargo install` to install the binaries for the \
|
||||
package in current working directory is no longer supported, \
|
||||
use `cargo install --path .` instead. \
|
||||
|
@ -239,18 +241,61 @@ fn install_one(
|
|||
}
|
||||
};
|
||||
|
||||
config.shell().status("Installing", pkg)?;
|
||||
if !opts.filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
|
||||
bail!("specified package `{}` has no binaries", pkg);
|
||||
}
|
||||
|
||||
// Preflight checks to check up front whether we'll overwrite something.
|
||||
// We have to check this again afterwards, but may as well avoid building
|
||||
// anything if we're gonna throw it away anyway.
|
||||
{
|
||||
let metadata = metadata(config, root)?;
|
||||
let list = read_crate_list(&metadata)?;
|
||||
let dst = metadata.parent().join("bin");
|
||||
check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
|
||||
let dst = root.join("bin").into_path_unlocked();
|
||||
let rustc = config.load_global_rustc(Some(&ws))?;
|
||||
let target = opts
|
||||
.build_config
|
||||
.requested_target
|
||||
.as_ref()
|
||||
.unwrap_or(&rustc.host)
|
||||
.clone();
|
||||
|
||||
// Helper for --no-track flag to make sure it doesn't overwrite anything.
|
||||
let no_track_duplicates = || -> CargoResult<BTreeMap<String, Option<PackageId>>> {
|
||||
let duplicates: BTreeMap<String, Option<PackageId>> = exe_names(pkg, &opts.filter)
|
||||
.into_iter()
|
||||
.filter(|name| dst.join(name).exists())
|
||||
.map(|name| (name, None))
|
||||
.collect();
|
||||
if !force && !duplicates.is_empty() {
|
||||
let mut msg: Vec<String> = duplicates
|
||||
.iter()
|
||||
.map(|(name, _)| format!("binary `{}` already exists in destination", name))
|
||||
.collect();
|
||||
msg.push("Add --force to overwrite".to_string());
|
||||
bail!("{}", msg.join("\n"));
|
||||
}
|
||||
Ok(duplicates)
|
||||
};
|
||||
|
||||
if no_track {
|
||||
// Check for conflicts.
|
||||
no_track_duplicates()?;
|
||||
} else {
|
||||
let tracker = InstallTracker::load(config, root)?;
|
||||
let (freshness, _duplicates) =
|
||||
tracker.check_upgrade(&dst, pkg, force, opts, &target, &rustc.verbose_version)?;
|
||||
if freshness == Freshness::Fresh {
|
||||
let msg = format!(
|
||||
"package `{}` is already installed, use --force to override",
|
||||
pkg
|
||||
);
|
||||
config.shell().status("Ignored", &msg)?;
|
||||
return Ok(());
|
||||
}
|
||||
// Unlock while building.
|
||||
drop(tracker);
|
||||
}
|
||||
|
||||
config.shell().status("Installing", pkg)?;
|
||||
|
||||
let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
|
||||
let compile = ops::compile_ws(&ws, Some(source), opts, &exec).chain_err(|| {
|
||||
if let Some(td) = td_opt.take() {
|
||||
|
@ -258,14 +303,14 @@ fn install_one(
|
|||
td.into_path();
|
||||
}
|
||||
|
||||
failure::format_err!(
|
||||
format_err!(
|
||||
"failed to compile `{}`, intermediate artifacts can be \
|
||||
found at `{}`",
|
||||
pkg,
|
||||
ws.target_dir().display()
|
||||
)
|
||||
})?;
|
||||
let binaries: Vec<(&str, &Path)> = compile
|
||||
let mut binaries: Vec<(&str, &Path)> = compile
|
||||
.binaries
|
||||
.iter()
|
||||
.map(|bin| {
|
||||
|
@ -273,21 +318,24 @@ fn install_one(
|
|||
if let Some(s) = name.to_str() {
|
||||
Ok((s, bin.as_ref()))
|
||||
} else {
|
||||
failure::bail!("Binary `{:?}` name can't be serialized into string", name)
|
||||
bail!("Binary `{:?}` name can't be serialized into string", name)
|
||||
}
|
||||
})
|
||||
.collect::<CargoResult<_>>()?;
|
||||
if binaries.is_empty() {
|
||||
failure::bail!(
|
||||
"no binaries are available for install using the selected \
|
||||
features"
|
||||
);
|
||||
bail!("no binaries are available for install using the selected features");
|
||||
}
|
||||
// This is primarily to make testing easier.
|
||||
binaries.sort_unstable();
|
||||
|
||||
let metadata = metadata(config, root)?;
|
||||
let mut list = read_crate_list(&metadata)?;
|
||||
let dst = metadata.parent().join("bin");
|
||||
let duplicates = check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
|
||||
let (tracker, duplicates) = if no_track {
|
||||
(None, no_track_duplicates()?)
|
||||
} else {
|
||||
let tracker = InstallTracker::load(config, root)?;
|
||||
let (_freshness, duplicates) =
|
||||
tracker.check_upgrade(&dst, pkg, force, opts, &target, &rustc.verbose_version)?;
|
||||
(Some(tracker), duplicates)
|
||||
};
|
||||
|
||||
fs::create_dir_all(&dst)?;
|
||||
|
||||
|
@ -304,7 +352,7 @@ fn install_one(
|
|||
continue;
|
||||
}
|
||||
fs::copy(src, &dst).chain_err(|| {
|
||||
failure::format_err!("failed to copy `{}` to `{}`", src.display(), dst.display())
|
||||
format_err!("failed to copy `{}` to `{}`", src.display(), dst.display())
|
||||
})?;
|
||||
}
|
||||
|
||||
|
@ -314,6 +362,7 @@ fn install_one(
|
|||
.partition(|&bin| duplicates.contains_key(bin));
|
||||
|
||||
let mut installed = Transaction { bins: Vec::new() };
|
||||
let mut successful_bins = BTreeSet::new();
|
||||
|
||||
// Move the temporary copies into `dst` starting with new binaries.
|
||||
for bin in to_install.iter() {
|
||||
|
@ -321,76 +370,45 @@ fn install_one(
|
|||
let dst = dst.join(bin);
|
||||
config.shell().status("Installing", dst.display())?;
|
||||
fs::rename(&src, &dst).chain_err(|| {
|
||||
failure::format_err!("failed to move `{}` to `{}`", src.display(), dst.display())
|
||||
format_err!("failed to move `{}` to `{}`", src.display(), dst.display())
|
||||
})?;
|
||||
installed.bins.push(dst);
|
||||
successful_bins.insert(bin.to_string());
|
||||
}
|
||||
|
||||
// Repeat for binaries which replace existing ones but don't pop the error
|
||||
// up until after updating metadata.
|
||||
let mut replaced_names = Vec::new();
|
||||
let result = {
|
||||
let replace_result = {
|
||||
let mut try_install = || -> CargoResult<()> {
|
||||
for &bin in to_replace.iter() {
|
||||
let src = staging_dir.path().join(bin);
|
||||
let dst = dst.join(bin);
|
||||
config.shell().status("Replacing", dst.display())?;
|
||||
fs::rename(&src, &dst).chain_err(|| {
|
||||
failure::format_err!(
|
||||
"failed to move `{}` to `{}`",
|
||||
src.display(),
|
||||
dst.display()
|
||||
)
|
||||
format_err!("failed to move `{}` to `{}`", src.display(), dst.display())
|
||||
})?;
|
||||
replaced_names.push(bin);
|
||||
successful_bins.insert(bin.to_string());
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
try_install()
|
||||
};
|
||||
|
||||
// Update records of replaced binaries.
|
||||
for &bin in replaced_names.iter() {
|
||||
if let Some(&Some(ref p)) = duplicates.get(bin) {
|
||||
if let Some(set) = list.v1_mut().get_mut(p) {
|
||||
set.remove(bin);
|
||||
}
|
||||
if !no_track {
|
||||
let mut tracker = tracker.unwrap();
|
||||
tracker.mark_installed(
|
||||
pkg,
|
||||
&successful_bins,
|
||||
vers.map(|s| s.to_string()),
|
||||
opts,
|
||||
target,
|
||||
rustc.verbose_version,
|
||||
);
|
||||
|
||||
match tracker.save() {
|
||||
Err(err) => replace_result.chain_err(|| err)?,
|
||||
Ok(_) => replace_result?,
|
||||
}
|
||||
// Failsafe to force replacing metadata for git packages
|
||||
// https://github.com/rust-lang/cargo/issues/4582
|
||||
if let Some(set) = list.v1_mut().remove(&pkg.package_id()) {
|
||||
list.v1_mut().insert(pkg.package_id(), set);
|
||||
}
|
||||
list.v1_mut()
|
||||
.entry(pkg.package_id())
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.insert(bin.to_string());
|
||||
}
|
||||
|
||||
// Remove empty metadata lines.
|
||||
let pkgs = list
|
||||
.v1()
|
||||
.iter()
|
||||
.filter_map(|(&p, set)| if set.is_empty() { Some(p) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
for p in pkgs.iter() {
|
||||
list.v1_mut().remove(p);
|
||||
}
|
||||
|
||||
// If installation was successful record newly installed binaries.
|
||||
if result.is_ok() {
|
||||
list.v1_mut()
|
||||
.entry(pkg.package_id())
|
||||
.or_insert_with(BTreeSet::new)
|
||||
.extend(to_install.iter().map(|s| s.to_string()));
|
||||
}
|
||||
|
||||
let write_result = write_crate_list(&metadata, list);
|
||||
match write_result {
|
||||
// Replacement error (if any) isn't actually caused by write error
|
||||
// but this seems to be the only way to show both.
|
||||
Err(err) => result.chain_err(|| err)?,
|
||||
Ok(_) => result?,
|
||||
}
|
||||
|
||||
// Reaching here means all actions have succeeded. Clean up.
|
||||
|
@ -402,98 +420,63 @@ fn install_one(
|
|||
paths::remove_dir_all(&target_dir)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_overwrites(
|
||||
dst: &Path,
|
||||
pkg: &Package,
|
||||
filter: &ops::CompileFilter,
|
||||
prev: &CrateListingV1,
|
||||
force: bool,
|
||||
) -> CargoResult<BTreeMap<String, Option<PackageId>>> {
|
||||
// If explicit --bin or --example flags were passed then those'll
|
||||
// get checked during cargo_compile, we only care about the "build
|
||||
// everything" case here
|
||||
if !filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
|
||||
failure::bail!("specified package has no binaries")
|
||||
}
|
||||
let duplicates = find_duplicates(dst, pkg, filter, prev);
|
||||
if force || duplicates.is_empty() {
|
||||
return Ok(duplicates);
|
||||
}
|
||||
// Format the error message.
|
||||
let mut msg = String::new();
|
||||
for (bin, p) in duplicates.iter() {
|
||||
msg.push_str(&format!("binary `{}` already exists in destination", bin));
|
||||
if let Some(p) = p.as_ref() {
|
||||
msg.push_str(&format!(" as part of `{}`\n", p));
|
||||
// Helper for creating status messages.
|
||||
fn executables<T: AsRef<str>>(mut names: impl Iterator<Item = T> + Clone) -> String {
|
||||
if names.clone().count() == 1 {
|
||||
format!("(executable `{}`)", names.next().unwrap().as_ref())
|
||||
} else {
|
||||
msg.push_str("\n");
|
||||
format!(
|
||||
"(executables {})",
|
||||
names
|
||||
.map(|b| format!("`{}`", b.as_ref()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
msg.push_str("Add --force to overwrite");
|
||||
Err(failure::format_err!("{}", msg))
|
||||
}
|
||||
|
||||
fn find_duplicates(
|
||||
dst: &Path,
|
||||
pkg: &Package,
|
||||
filter: &ops::CompileFilter,
|
||||
prev: &CrateListingV1,
|
||||
) -> BTreeMap<String, Option<PackageId>> {
|
||||
let check = |name: String| {
|
||||
// Need to provide type, works around Rust Issue #93349
|
||||
let name = format!("{}{}", name, env::consts::EXE_SUFFIX);
|
||||
if fs::metadata(dst.join(&name)).is_err() {
|
||||
None
|
||||
} else if let Some((&p, _)) = prev.v1().iter().find(|&(_, v)| v.contains(&name)) {
|
||||
Some((name, Some(p)))
|
||||
} else {
|
||||
Some((name, None))
|
||||
if duplicates.is_empty() {
|
||||
config.shell().status(
|
||||
"Installed",
|
||||
format!("package `{}` {}", pkg, executables(successful_bins.iter())),
|
||||
)?;
|
||||
Ok(())
|
||||
} else {
|
||||
if !to_install.is_empty() {
|
||||
config.shell().status(
|
||||
"Installed",
|
||||
format!("package `{}` {}", pkg, executables(to_install.iter())),
|
||||
)?;
|
||||
}
|
||||
};
|
||||
match *filter {
|
||||
CompileFilter::Default { .. } => pkg
|
||||
.targets()
|
||||
.iter()
|
||||
.filter(|t| t.is_bin())
|
||||
.filter_map(|t| check(t.name().to_string()))
|
||||
.collect(),
|
||||
CompileFilter::Only {
|
||||
ref bins,
|
||||
ref examples,
|
||||
..
|
||||
} => {
|
||||
let all_bins: Vec<String> = bins.try_collect().unwrap_or_else(|| {
|
||||
pkg.targets()
|
||||
.iter()
|
||||
.filter(|t| t.is_bin())
|
||||
.map(|t| t.name().to_string())
|
||||
.collect()
|
||||
});
|
||||
let all_examples: Vec<String> = examples.try_collect().unwrap_or_else(|| {
|
||||
pkg.targets()
|
||||
.iter()
|
||||
.filter(|t| t.is_exe_example())
|
||||
.map(|t| t.name().to_string())
|
||||
.collect()
|
||||
});
|
||||
|
||||
all_bins
|
||||
.iter()
|
||||
.chain(all_examples.iter())
|
||||
.filter_map(|t| check(t.clone()))
|
||||
.collect::<BTreeMap<String, Option<PackageId>>>()
|
||||
// Invert the duplicate map.
|
||||
let mut pkg_map = BTreeMap::new();
|
||||
for (bin_name, opt_pkg_id) in &duplicates {
|
||||
let key = opt_pkg_id.map_or_else(|| "unknown".to_string(), |pkg_id| pkg_id.to_string());
|
||||
pkg_map
|
||||
.entry(key)
|
||||
.or_insert_with(|| Vec::new())
|
||||
.push(bin_name);
|
||||
}
|
||||
for (pkg_descr, bin_names) in &pkg_map {
|
||||
config.shell().status(
|
||||
"Replaced",
|
||||
format!(
|
||||
"package `{}` with `{}` {}",
|
||||
pkg_descr,
|
||||
pkg,
|
||||
executables(bin_names.iter())
|
||||
),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Display a list of installed binaries.
|
||||
pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
|
||||
let dst = resolve_root(dst, config)?;
|
||||
let dst = metadata(config, &dst)?;
|
||||
let list = read_crate_list(&dst)?;
|
||||
for (k, v) in list.v1().iter() {
|
||||
let root = resolve_root(dst, config)?;
|
||||
let tracker = InstallTracker::load(config, &root)?;
|
||||
for (k, v) in tracker.all_installed_bins() {
|
||||
println!("{}:", k);
|
||||
for bin in v {
|
||||
println!(" {}", bin);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::collections::btree_map::Entry;
|
||||
use std::{env, fs};
|
||||
use failure::bail;
|
||||
use std::collections::BTreeSet;
|
||||
use std::env;
|
||||
|
||||
use crate::core::PackageId;
|
||||
use crate::core::{PackageIdSpec, SourceId};
|
||||
|
@ -7,7 +8,7 @@ use crate::ops::common_for_install_and_uninstall::*;
|
|||
use crate::util::errors::CargoResult;
|
||||
use crate::util::paths;
|
||||
use crate::util::Config;
|
||||
use crate::util::{FileLock, Filesystem};
|
||||
use crate::util::Filesystem;
|
||||
|
||||
pub fn uninstall(
|
||||
root: Option<&str>,
|
||||
|
@ -16,7 +17,7 @@ pub fn uninstall(
|
|||
config: &Config,
|
||||
) -> CargoResult<()> {
|
||||
if specs.len() > 1 && !bins.is_empty() {
|
||||
failure::bail!("A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.");
|
||||
bail!("A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.");
|
||||
}
|
||||
|
||||
let root = resolve_root(root, config)?;
|
||||
|
@ -62,7 +63,7 @@ pub fn uninstall(
|
|||
};
|
||||
|
||||
if scheduled_error {
|
||||
failure::bail!("some packages failed to uninstall");
|
||||
bail!("some packages failed to uninstall");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -74,80 +75,74 @@ pub fn uninstall_one(
|
|||
bins: &[String],
|
||||
config: &Config,
|
||||
) -> CargoResult<()> {
|
||||
let crate_metadata = metadata(config, root)?;
|
||||
let metadata = read_crate_list(&crate_metadata)?;
|
||||
let pkgid = PackageIdSpec::query_str(spec, metadata.v1().keys().cloned())?;
|
||||
uninstall_pkgid(&crate_metadata, metadata, pkgid, bins, config)
|
||||
let tracker = InstallTracker::load(config, root)?;
|
||||
let all_pkgs = tracker.all_installed_bins().map(|(pkg_id, _set)| *pkg_id);
|
||||
let pkgid = PackageIdSpec::query_str(spec, all_pkgs)?;
|
||||
uninstall_pkgid(root, tracker, pkgid, bins, config)
|
||||
}
|
||||
|
||||
fn uninstall_cwd(root: &Filesystem, bins: &[String], config: &Config) -> CargoResult<()> {
|
||||
let crate_metadata = metadata(config, root)?;
|
||||
let metadata = read_crate_list(&crate_metadata)?;
|
||||
let tracker = InstallTracker::load(config, root)?;
|
||||
let source_id = SourceId::for_path(config.cwd())?;
|
||||
let src = path_source(source_id, config)?;
|
||||
let (pkg, _source) = select_pkg(src, None, None, config, true, &mut |path| {
|
||||
path.read_packages()
|
||||
})?;
|
||||
let pkgid = pkg.package_id();
|
||||
uninstall_pkgid(&crate_metadata, metadata, pkgid, bins, config)
|
||||
uninstall_pkgid(root, tracker, pkgid, bins, config)
|
||||
}
|
||||
|
||||
fn uninstall_pkgid(
|
||||
crate_metadata: &FileLock,
|
||||
mut metadata: CrateListingV1,
|
||||
root: &Filesystem,
|
||||
mut tracker: InstallTracker,
|
||||
pkgid: PackageId,
|
||||
bins: &[String],
|
||||
config: &Config,
|
||||
) -> CargoResult<()> {
|
||||
let mut to_remove = Vec::new();
|
||||
{
|
||||
let mut installed = match metadata.v1_mut().entry(pkgid) {
|
||||
Entry::Occupied(e) => e,
|
||||
Entry::Vacant(..) => failure::bail!("package `{}` is not installed", pkgid),
|
||||
};
|
||||
let installed = match tracker.installed_bins(pkgid) {
|
||||
Some(bins) => bins.clone(),
|
||||
None => bail!("package `{}` is not installed", pkgid),
|
||||
};
|
||||
|
||||
let dst = crate_metadata.parent().join("bin");
|
||||
for bin in installed.get() {
|
||||
let bin = dst.join(bin);
|
||||
if fs::metadata(&bin).is_err() {
|
||||
failure::bail!(
|
||||
"corrupt metadata, `{}` does not exist when it should",
|
||||
bin.display()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let bins = bins
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.ends_with(env::consts::EXE_SUFFIX) {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{}{}", s, env::consts::EXE_SUFFIX)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for bin in bins.iter() {
|
||||
if !installed.get().contains(bin) {
|
||||
failure::bail!("binary `{}` not installed as part of `{}`", bin, pkgid)
|
||||
}
|
||||
}
|
||||
|
||||
if bins.is_empty() {
|
||||
to_remove.extend(installed.get().iter().map(|b| dst.join(b)));
|
||||
installed.get_mut().clear();
|
||||
} else {
|
||||
for bin in bins.iter() {
|
||||
to_remove.push(dst.join(bin));
|
||||
installed.get_mut().remove(bin);
|
||||
}
|
||||
}
|
||||
if installed.get().is_empty() {
|
||||
installed.remove();
|
||||
let dst = root.join("bin").into_path_unlocked();
|
||||
for bin in &installed {
|
||||
let bin = dst.join(bin);
|
||||
if !bin.exists() {
|
||||
bail!(
|
||||
"corrupt metadata, `{}` does not exist when it should",
|
||||
bin.display()
|
||||
)
|
||||
}
|
||||
}
|
||||
write_crate_list(crate_metadata, metadata)?;
|
||||
|
||||
let bins = bins
|
||||
.iter()
|
||||
.map(|s| {
|
||||
if s.ends_with(env::consts::EXE_SUFFIX) {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{}{}", s, env::consts::EXE_SUFFIX)
|
||||
}
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
for bin in bins.iter() {
|
||||
if !installed.contains(bin) {
|
||||
bail!("binary `{}` not installed as part of `{}`", bin, pkgid)
|
||||
}
|
||||
}
|
||||
|
||||
if bins.is_empty() {
|
||||
to_remove.extend(installed.iter().map(|b| dst.join(b)));
|
||||
tracker.remove(pkgid, &installed);
|
||||
} else {
|
||||
for bin in bins.iter() {
|
||||
to_remove.push(dst.join(bin));
|
||||
}
|
||||
tracker.remove(pkgid, &bins);
|
||||
}
|
||||
tracker.save()?;
|
||||
for bin in to_remove {
|
||||
config.shell().status("Removing", bin.display())?;
|
||||
paths::remove_file(bin)?;
|
||||
|
|
|
@ -1,45 +1,481 @@
|
|||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::{btree_map, BTreeMap, BTreeSet};
|
||||
use std::env;
|
||||
use std::io::prelude::*;
|
||||
use std::io::SeekFrom;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use failure::{bail, format_err};
|
||||
use semver::VersionReq;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::core::PackageId;
|
||||
use crate::core::{Dependency, Package, Source, SourceId};
|
||||
use crate::core::{Dependency, Package, PackageId, Source, SourceId};
|
||||
use crate::ops::{self, CompileFilter, CompileOptions};
|
||||
use crate::sources::PathSource;
|
||||
use crate::util::errors::{CargoResult, CargoResultExt};
|
||||
use crate::util::{internal, Config, ToSemver};
|
||||
use crate::util::{FileLock, Filesystem};
|
||||
use crate::util::{Config, ToSemver};
|
||||
use crate::util::{FileLock, Filesystem, Freshness};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum CrateListing {
|
||||
V1(CrateListingV1),
|
||||
Empty(Empty),
|
||||
/// On-disk tracking for which package installed which binary.
|
||||
///
|
||||
/// v1 is an older style, v2 is a new (experimental) style that tracks more
|
||||
/// information. The new style is only enabled with the `-Z install-upgrade`
|
||||
/// flag (which sets the `unstable_upgrade` flag). v1 is still considered the
|
||||
/// source of truth. When v2 is used, it will sync with any changes with v1,
|
||||
/// and will continue to update v1.
|
||||
///
|
||||
/// This maintains a filesystem lock, preventing other instances of Cargo from
|
||||
/// modifying at the same time. Drop the value to unlock.
|
||||
pub struct InstallTracker {
|
||||
v1: CrateListingV1,
|
||||
v2: CrateListingV2,
|
||||
v1_lock: FileLock,
|
||||
v2_lock: Option<FileLock>,
|
||||
unstable_upgrade: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Empty {}
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
struct CrateListingV2 {
|
||||
installs: BTreeMap<PackageId, InstallInfo>,
|
||||
/// Forwards compatibility.
|
||||
#[serde(flatten)]
|
||||
other: BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
struct InstallInfo {
|
||||
/// Version requested via `--version`.
|
||||
/// None if `--version` not specified. Currently not used, possibly may be
|
||||
/// used in the future.
|
||||
version_req: Option<String>,
|
||||
/// Set of binary names installed.
|
||||
bins: BTreeSet<String>,
|
||||
/// Set of features explicitly enabled.
|
||||
features: BTreeSet<String>,
|
||||
all_features: bool,
|
||||
no_default_features: bool,
|
||||
/// Either "debug" or "release".
|
||||
profile: String,
|
||||
/// The installation target.
|
||||
/// Either the host or the value specified in `--target`.
|
||||
/// None if unknown (when loading from v1).
|
||||
target: Option<String>,
|
||||
/// Output of `rustc -V`.
|
||||
/// None if unknown (when loading from v1).
|
||||
/// Currently not used, possibly may be used in the future.
|
||||
rustc: Option<String>,
|
||||
/// Forwards compatibility.
|
||||
#[serde(flatten)]
|
||||
other: BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct CrateListingV1 {
|
||||
v1: BTreeMap<PackageId, BTreeSet<String>>,
|
||||
}
|
||||
|
||||
impl CrateListingV1 {
|
||||
pub fn v1(&self) -> &BTreeMap<PackageId, BTreeSet<String>> {
|
||||
&self.v1
|
||||
impl InstallTracker {
|
||||
/// Create an InstallTracker from information on disk.
|
||||
pub fn load(config: &Config, root: &Filesystem) -> CargoResult<InstallTracker> {
|
||||
let unstable_upgrade = config.cli_unstable().install_upgrade;
|
||||
let v1_lock = root.open_rw(Path::new(".crates.toml"), config, "crate metadata")?;
|
||||
let v2_lock = if unstable_upgrade {
|
||||
Some(root.open_rw(Path::new(".crates2.json"), config, "crate metadata")?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let v1 = (|| -> CargoResult<_> {
|
||||
let mut contents = String::new();
|
||||
v1_lock.file().read_to_string(&mut contents)?;
|
||||
if contents.is_empty() {
|
||||
Ok(CrateListingV1::default())
|
||||
} else {
|
||||
Ok(toml::from_str(&contents)
|
||||
.chain_err(|| format_err!("invalid TOML found for metadata"))?)
|
||||
}
|
||||
})()
|
||||
.chain_err(|| {
|
||||
format_err!(
|
||||
"failed to parse crate metadata at `{}`",
|
||||
v1_lock.path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
|
||||
let v2 = (|| -> CargoResult<_> {
|
||||
if unstable_upgrade {
|
||||
let mut contents = String::new();
|
||||
v2_lock
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.file()
|
||||
.read_to_string(&mut contents)?;
|
||||
let mut v2 = if contents.is_empty() {
|
||||
CrateListingV2::default()
|
||||
} else {
|
||||
serde_json::from_str(&contents)
|
||||
.chain_err(|| format_err!("invalid JSON found for metadata"))?
|
||||
};
|
||||
v2.sync_v1(&v1)?;
|
||||
Ok(v2)
|
||||
} else {
|
||||
Ok(CrateListingV2::default())
|
||||
}
|
||||
})()
|
||||
.chain_err(|| {
|
||||
format_err!(
|
||||
"failed to parse crate metadata at `{}`",
|
||||
v2_lock.as_ref().unwrap().path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(InstallTracker {
|
||||
v1,
|
||||
v2,
|
||||
v1_lock,
|
||||
v2_lock,
|
||||
unstable_upgrade,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn v1_mut(&mut self) -> &mut BTreeMap<PackageId, BTreeSet<String>> {
|
||||
&mut self.v1
|
||||
/// Checks if the given package should be built, and checks if executables
|
||||
/// already exist in the destination directory.
|
||||
///
|
||||
/// Returns a tuple `(freshness, map)`. `freshness` indicates if the
|
||||
/// package should be built (`Dirty`) or if it is already up-to-date
|
||||
/// (`Fresh`). The map maps binary names to the PackageId that installed
|
||||
/// it (which is None if not known).
|
||||
///
|
||||
/// Returns an error if there is a duplicate and `--force` is not used.
|
||||
pub fn check_upgrade(
|
||||
&self,
|
||||
dst: &Path,
|
||||
pkg: &Package,
|
||||
force: bool,
|
||||
opts: &CompileOptions<'_>,
|
||||
target: &str,
|
||||
_rustc: &str,
|
||||
) -> CargoResult<(Freshness, BTreeMap<String, Option<PackageId>>)> {
|
||||
let exes = exe_names(pkg, &opts.filter);
|
||||
let duplicates = self.find_duplicates(dst, &exes);
|
||||
if force || duplicates.is_empty() {
|
||||
return Ok((Freshness::Dirty, duplicates));
|
||||
}
|
||||
// If any duplicates are not tracked, then --force is required.
|
||||
// If any duplicates are from a package with a different name, --force is required.
|
||||
let matching_duplicates: Vec<PackageId> = duplicates
|
||||
.values()
|
||||
.filter_map(|v| match v {
|
||||
Some(dupe_pkg_id) if dupe_pkg_id.name() == pkg.name() => Some(*dupe_pkg_id),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if self.unstable_upgrade && matching_duplicates.len() == duplicates.len() {
|
||||
let source_id = pkg.package_id().source_id();
|
||||
if source_id.is_path() {
|
||||
return Ok((Freshness::Dirty, duplicates));
|
||||
}
|
||||
if matching_duplicates.iter().all(|dupe_pkg_id| {
|
||||
let info = self
|
||||
.v2
|
||||
.installs
|
||||
.get(dupe_pkg_id)
|
||||
.expect("dupes must be in sync");
|
||||
let precise_equal = if source_id.is_git() {
|
||||
dupe_pkg_id.source_id().precise() == source_id.precise()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
dupe_pkg_id.version() == pkg.version()
|
||||
&& dupe_pkg_id.source_id() == source_id
|
||||
&& precise_equal
|
||||
&& info.features == feature_set(&opts.features)
|
||||
&& info.all_features == opts.all_features
|
||||
&& info.no_default_features == opts.no_default_features
|
||||
&& info.profile == profile_name(opts.build_config.release)
|
||||
&& (info.target.is_none()
|
||||
|| info.target.as_ref().map(|t| t.as_ref()) == Some(target))
|
||||
&& info.bins == exes
|
||||
}) {
|
||||
Ok((Freshness::Fresh, duplicates))
|
||||
} else {
|
||||
Ok((Freshness::Dirty, duplicates))
|
||||
}
|
||||
} else {
|
||||
// Format the error message.
|
||||
let mut msg = String::new();
|
||||
for (bin, p) in duplicates.iter() {
|
||||
msg.push_str(&format!("binary `{}` already exists in destination", bin));
|
||||
if let Some(p) = p.as_ref() {
|
||||
msg.push_str(&format!(" as part of `{}`\n", p));
|
||||
} else {
|
||||
msg.push_str("\n");
|
||||
}
|
||||
}
|
||||
msg.push_str("Add --force to overwrite");
|
||||
bail!("{}", msg);
|
||||
}
|
||||
}
|
||||
|
||||
fn find_duplicates(
|
||||
&self,
|
||||
dst: &Path,
|
||||
exes: &BTreeSet<String>,
|
||||
) -> BTreeMap<String, Option<PackageId>> {
|
||||
exes.iter()
|
||||
.filter_map(|name| {
|
||||
if !dst.join(&name).exists() {
|
||||
None
|
||||
} else if self.unstable_upgrade {
|
||||
let p = self.v2.package_for_bin(name);
|
||||
Some((name.clone(), p))
|
||||
} else {
|
||||
let p = self.v1.package_for_bin(name);
|
||||
Some((name.clone(), p))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Mark that a package was installed.
|
||||
pub fn mark_installed(
|
||||
&mut self,
|
||||
package: &Package,
|
||||
bins: &BTreeSet<String>,
|
||||
version_req: Option<String>,
|
||||
opts: &CompileOptions<'_>,
|
||||
target: String,
|
||||
rustc: String,
|
||||
) {
|
||||
if self.unstable_upgrade {
|
||||
self.v2
|
||||
.mark_installed(package, bins, version_req, opts, target, rustc)
|
||||
}
|
||||
self.v1.mark_installed(package, bins);
|
||||
}
|
||||
|
||||
/// Save tracking information to disk.
|
||||
pub fn save(&self) -> CargoResult<()> {
|
||||
self.v1.save(&self.v1_lock).chain_err(|| {
|
||||
format_err!(
|
||||
"failed to write crate metadata at `{}`",
|
||||
self.v1_lock.path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
|
||||
if self.unstable_upgrade {
|
||||
self.v2.save(self.v2_lock.as_ref().unwrap()).chain_err(|| {
|
||||
format_err!(
|
||||
"failed to write crate metadata at `{}`",
|
||||
self.v2_lock.as_ref().unwrap().path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Iterator of all installed binaries.
|
||||
/// Items are `(pkg_id, bins)` where `bins` is the set of binaries that
|
||||
/// package installed.
|
||||
pub fn all_installed_bins(&self) -> impl Iterator<Item = (&PackageId, &BTreeSet<String>)> {
|
||||
self.v1.v1.iter()
|
||||
}
|
||||
|
||||
/// Set of binaries installed by a particular package.
|
||||
/// Returns None if the package is not installed.
|
||||
pub fn installed_bins(&self, pkg_id: PackageId) -> Option<&BTreeSet<String>> {
|
||||
self.v1.v1.get(&pkg_id)
|
||||
}
|
||||
|
||||
/// Remove a package from the tracker.
|
||||
pub fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
|
||||
self.v1.remove(pkg_id, bins);
|
||||
if self.unstable_upgrade {
|
||||
self.v2.remove(pkg_id, bins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateListingV1 {
|
||||
fn package_for_bin(&self, bin_name: &str) -> Option<PackageId> {
|
||||
self.v1
|
||||
.iter()
|
||||
.find(|(_, bins)| bins.contains(bin_name))
|
||||
.map(|(pkg_id, _)| *pkg_id)
|
||||
}
|
||||
|
||||
fn mark_installed(&mut self, pkg: &Package, bins: &BTreeSet<String>) {
|
||||
// Remove bins from any other packages.
|
||||
for other_bins in self.v1.values_mut() {
|
||||
for bin in bins {
|
||||
other_bins.remove(bin);
|
||||
}
|
||||
}
|
||||
// Remove empty metadata lines. If only BTreeMap had `retain`.
|
||||
let to_remove = self
|
||||
.v1
|
||||
.iter()
|
||||
.filter_map(|(&p, set)| if set.is_empty() { Some(p) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
for p in to_remove.iter() {
|
||||
self.v1.remove(p);
|
||||
}
|
||||
// Add these bins.
|
||||
let mut bins = bins.clone();
|
||||
self.v1
|
||||
.entry(pkg.package_id())
|
||||
.and_modify(|set| set.append(&mut bins))
|
||||
.or_insert(bins);
|
||||
}
|
||||
|
||||
fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
|
||||
let mut installed = match self.v1.entry(pkg_id) {
|
||||
btree_map::Entry::Occupied(e) => e,
|
||||
btree_map::Entry::Vacant(..) => panic!("v1 unexpected missing `{}`", pkg_id),
|
||||
};
|
||||
|
||||
for bin in bins {
|
||||
installed.get_mut().remove(bin);
|
||||
}
|
||||
if installed.get().is_empty() {
|
||||
installed.remove();
|
||||
}
|
||||
}
|
||||
|
||||
fn save(&self, lock: &FileLock) -> CargoResult<()> {
|
||||
let mut file = lock.file();
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
file.set_len(0)?;
|
||||
let data = toml::to_string(self)?;
|
||||
file.write_all(data.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CrateListingV2 {
|
||||
fn sync_v1(&mut self, v1: &CrateListingV1) -> CargoResult<()> {
|
||||
// Make the `bins` entries the same.
|
||||
for (pkg_id, bins) in &v1.v1 {
|
||||
self.installs
|
||||
.entry(*pkg_id)
|
||||
.and_modify(|info| info.bins = bins.clone())
|
||||
.or_insert_with(|| InstallInfo::from_v1(bins));
|
||||
}
|
||||
// Remove any packages that aren't present in v1.
|
||||
let to_remove: Vec<_> = self
|
||||
.installs
|
||||
.keys()
|
||||
.filter(|pkg_id| !v1.v1.contains_key(pkg_id))
|
||||
.cloned()
|
||||
.collect();
|
||||
for pkg_id in to_remove {
|
||||
self.installs.remove(&pkg_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn package_for_bin(&self, bin_name: &str) -> Option<PackageId> {
|
||||
self.installs
|
||||
.iter()
|
||||
.find(|(_, info)| info.bins.contains(bin_name))
|
||||
.map(|(pkg_id, _)| *pkg_id)
|
||||
}
|
||||
|
||||
fn mark_installed(
|
||||
&mut self,
|
||||
pkg: &Package,
|
||||
bins: &BTreeSet<String>,
|
||||
version_req: Option<String>,
|
||||
opts: &CompileOptions<'_>,
|
||||
target: String,
|
||||
rustc: String,
|
||||
) {
|
||||
// Remove bins from any other packages.
|
||||
for info in &mut self.installs.values_mut() {
|
||||
for bin in bins {
|
||||
info.bins.remove(bin);
|
||||
}
|
||||
}
|
||||
// Remove empty metadata lines. If only BTreeMap had `retain`.
|
||||
let to_remove = self
|
||||
.installs
|
||||
.iter()
|
||||
.filter_map(|(&p, info)| if info.bins.is_empty() { Some(p) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
for p in to_remove.iter() {
|
||||
self.installs.remove(p);
|
||||
}
|
||||
// Add these bins.
|
||||
if let Some(info) = self.installs.get_mut(&pkg.package_id()) {
|
||||
for bin in bins {
|
||||
info.bins.remove(bin);
|
||||
}
|
||||
info.version_req = version_req;
|
||||
info.features = feature_set(&opts.features);
|
||||
info.all_features = opts.all_features;
|
||||
info.no_default_features = opts.no_default_features;
|
||||
info.profile = profile_name(opts.build_config.release).to_string();
|
||||
info.target = Some(target);
|
||||
info.rustc = Some(rustc);
|
||||
} else {
|
||||
self.installs.insert(
|
||||
pkg.package_id(),
|
||||
InstallInfo {
|
||||
version_req,
|
||||
bins: bins.clone(),
|
||||
features: feature_set(&opts.features),
|
||||
all_features: opts.all_features,
|
||||
no_default_features: opts.no_default_features,
|
||||
profile: profile_name(opts.build_config.release).to_string(),
|
||||
target: Some(target),
|
||||
rustc: Some(rustc),
|
||||
other: BTreeMap::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
|
||||
let mut info_entry = match self.installs.entry(pkg_id) {
|
||||
btree_map::Entry::Occupied(e) => e,
|
||||
btree_map::Entry::Vacant(..) => panic!("v2 unexpected missing `{}`", pkg_id),
|
||||
};
|
||||
|
||||
for bin in bins {
|
||||
info_entry.get_mut().bins.remove(bin);
|
||||
}
|
||||
if info_entry.get().bins.is_empty() {
|
||||
info_entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
fn save(&self, lock: &FileLock) -> CargoResult<()> {
|
||||
let mut file = lock.file();
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
file.set_len(0)?;
|
||||
serde_json::to_writer(file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InstallInfo {
|
||||
fn from_v1(set: &BTreeSet<String>) -> InstallInfo {
|
||||
InstallInfo {
|
||||
version_req: None,
|
||||
bins: set.clone(),
|
||||
features: BTreeSet::new(),
|
||||
all_features: false,
|
||||
no_default_features: false,
|
||||
profile: "release".to_string(),
|
||||
target: None,
|
||||
rustc: None,
|
||||
other: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the root directory where installation is done.
|
||||
pub fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<Filesystem> {
|
||||
let config_root = config.get_path("install.root")?;
|
||||
Ok(flag
|
||||
|
@ -50,14 +486,16 @@ pub fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<Filesyst
|
|||
.unwrap_or_else(|| config.home().clone()))
|
||||
}
|
||||
|
||||
/// Determines the `PathSource` from a `SourceId`.
|
||||
pub fn path_source<'a>(source_id: SourceId, config: &'a Config) -> CargoResult<PathSource<'a>> {
|
||||
let path = source_id
|
||||
.url()
|
||||
.to_file_path()
|
||||
.map_err(|()| failure::format_err!("path sources must have a valid path"))?;
|
||||
.map_err(|()| format_err!("path sources must have a valid path"))?;
|
||||
Ok(PathSource::new(&path, source_id, config))
|
||||
}
|
||||
|
||||
/// Gets a Package based on command-line requirements.
|
||||
pub fn select_pkg<'a, T>(
|
||||
mut source: T,
|
||||
name: Option<&str>,
|
||||
|
@ -73,124 +511,137 @@ where
|
|||
source.update()?;
|
||||
}
|
||||
|
||||
match name {
|
||||
Some(name) => {
|
||||
let vers = match vers {
|
||||
Some(v) => {
|
||||
// If the version begins with character <, >, =, ^, ~ parse it as a
|
||||
// version range, otherwise parse it as a specific version
|
||||
let first = v.chars().nth(0).ok_or_else(|| {
|
||||
failure::format_err!("no version provided for the `--vers` flag")
|
||||
})?;
|
||||
if let Some(name) = name {
|
||||
let vers = if let Some(v) = vers {
|
||||
// If the version begins with character <, >, =, ^, ~ parse it as a
|
||||
// version range, otherwise parse it as a specific version
|
||||
let first = v
|
||||
.chars()
|
||||
.nth(0)
|
||||
.ok_or_else(|| format_err!("no version provided for the `--vers` flag"))?;
|
||||
|
||||
match first {
|
||||
'<' | '>' | '=' | '^' | '~' => match v.parse::<VersionReq>() {
|
||||
Ok(v) => Some(v.to_string()),
|
||||
Err(_) => failure::bail!(
|
||||
let is_req = "<>=^~".contains(first) || v.contains('*');
|
||||
if is_req {
|
||||
match v.parse::<VersionReq>() {
|
||||
Ok(v) => Some(v.to_string()),
|
||||
Err(_) => bail!(
|
||||
"the `--vers` provided, `{}`, is \
|
||||
not a valid semver version requirement\n\n\
|
||||
Please have a look at \
|
||||
https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \
|
||||
for the correct format",
|
||||
v
|
||||
),
|
||||
}
|
||||
} else {
|
||||
match v.to_semver() {
|
||||
Ok(v) => Some(format!("={}", v)),
|
||||
Err(e) => {
|
||||
let mut msg = if config.cli_unstable().install_upgrade {
|
||||
format!(
|
||||
"the `--vers` provided, `{}`, is \
|
||||
not a valid semver version requirement\n\n
|
||||
Please have a look at \
|
||||
https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \
|
||||
for the correct format",
|
||||
not a valid semver version: {}\n",
|
||||
v, e
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"the `--vers` provided, `{}`, is \
|
||||
not a valid semver version\n\n\
|
||||
historically Cargo treated this \
|
||||
as a semver version requirement \
|
||||
accidentally\nand will continue \
|
||||
to do so, but this behavior \
|
||||
will be removed eventually",
|
||||
v
|
||||
),
|
||||
},
|
||||
_ => match v.to_semver() {
|
||||
Ok(v) => Some(format!("={}", v)),
|
||||
Err(_) => {
|
||||
let mut msg = format!(
|
||||
"\
|
||||
the `--vers` provided, `{}`, is \
|
||||
not a valid semver version\n\n\
|
||||
historically Cargo treated this \
|
||||
as a semver version requirement \
|
||||
accidentally\nand will continue \
|
||||
to do so, but this behavior \
|
||||
will be removed eventually",
|
||||
v
|
||||
);
|
||||
)
|
||||
};
|
||||
|
||||
// If it is not a valid version but it is a valid version
|
||||
// requirement, add a note to the warning
|
||||
if v.parse::<VersionReq>().is_ok() {
|
||||
msg.push_str(&format!(
|
||||
"\nif you want to specify semver range, \
|
||||
add an explicit qualifier, like ^{}",
|
||||
v
|
||||
));
|
||||
}
|
||||
config.shell().warn(&msg)?;
|
||||
Some(v.to_string())
|
||||
}
|
||||
},
|
||||
// If it is not a valid version but it is a valid version
|
||||
// requirement, add a note to the warning
|
||||
if v.parse::<VersionReq>().is_ok() {
|
||||
msg.push_str(&format!(
|
||||
"\nif you want to specify semver range, \
|
||||
add an explicit qualifier, like ^{}",
|
||||
v
|
||||
));
|
||||
}
|
||||
if config.cli_unstable().install_upgrade {
|
||||
bail!(msg);
|
||||
} else {
|
||||
config.shell().warn(&msg)?;
|
||||
}
|
||||
Some(v.to_string())
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let vers = vers.as_ref().map(|s| &**s);
|
||||
let vers_spec = if vers.is_none() && source.source_id().is_registry() {
|
||||
// Avoid pre-release versions from crate.io
|
||||
// unless explicitly asked for
|
||||
Some("*")
|
||||
} else {
|
||||
vers
|
||||
};
|
||||
let dep = Dependency::parse_no_deprecated(name, vers_spec, source.source_id())?;
|
||||
let deps = source.query_vec(&dep)?;
|
||||
match deps.iter().map(|p| p.package_id()).max() {
|
||||
Some(pkgid) => {
|
||||
let pkg = Box::new(&mut source).download_now(pkgid, config)?;
|
||||
Ok((pkg, Box::new(source)))
|
||||
},
|
||||
None => {
|
||||
let vers_info = vers
|
||||
.map(|v| format!(" with version `{}`", v))
|
||||
.unwrap_or_default();
|
||||
failure::bail!(
|
||||
"could not find `{}` in {}{}",
|
||||
name,
|
||||
source.source_id(),
|
||||
vers_info
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let candidates = list_all(&mut source)?;
|
||||
let binaries = candidates
|
||||
.iter()
|
||||
.filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
|
||||
let examples = candidates
|
||||
.iter()
|
||||
.filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
|
||||
let pkg = match one(binaries, |v| multi_err("binaries", v))? {
|
||||
Some(p) => p,
|
||||
None => match one(examples, |v| multi_err("examples", v))? {
|
||||
Some(p) => p,
|
||||
None => failure::bail!(
|
||||
"no packages found with binaries or \
|
||||
examples"
|
||||
),
|
||||
},
|
||||
};
|
||||
return Ok((pkg.clone(), Box::new(source)));
|
||||
|
||||
fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
|
||||
pkgs.sort_unstable_by_key(|a| a.name());
|
||||
format!(
|
||||
"multiple packages with {} found: {}",
|
||||
kind,
|
||||
pkgs.iter()
|
||||
.map(|p| p.name().as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let vers = vers.as_ref().map(|s| &**s);
|
||||
let vers_spec = if vers.is_none() && source.source_id().is_registry() {
|
||||
// Avoid pre-release versions from crate.io
|
||||
// unless explicitly asked for
|
||||
Some("*")
|
||||
} else {
|
||||
vers
|
||||
};
|
||||
let dep = Dependency::parse_no_deprecated(name, vers_spec, source.source_id())?;
|
||||
let deps = source.query_vec(&dep)?;
|
||||
match deps.iter().map(|p| p.package_id()).max() {
|
||||
Some(pkgid) => {
|
||||
let pkg = Box::new(&mut source).download_now(pkgid, config)?;
|
||||
Ok((pkg, Box::new(source)))
|
||||
}
|
||||
None => {
|
||||
let vers_info = vers
|
||||
.map(|v| format!(" with version `{}`", v))
|
||||
.unwrap_or_default();
|
||||
bail!(
|
||||
"could not find `{}` in {}{}",
|
||||
name,
|
||||
source.source_id(),
|
||||
vers_info
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let candidates = list_all(&mut source)?;
|
||||
let binaries = candidates
|
||||
.iter()
|
||||
.filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
|
||||
let examples = candidates
|
||||
.iter()
|
||||
.filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
|
||||
let pkg = match one(binaries, |v| multi_err("binaries", v))? {
|
||||
Some(p) => p,
|
||||
None => match one(examples, |v| multi_err("examples", v))? {
|
||||
Some(p) => p,
|
||||
None => bail!(
|
||||
"no packages found with binaries or \
|
||||
examples"
|
||||
),
|
||||
},
|
||||
};
|
||||
return Ok((pkg.clone(), Box::new(source)));
|
||||
|
||||
fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
|
||||
pkgs.sort_unstable_by_key(|a| a.name());
|
||||
format!(
|
||||
"multiple packages with {} found: {}",
|
||||
kind,
|
||||
pkgs.iter()
|
||||
.map(|p| p.name().as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
|
||||
/// Get one element from the iterator.
|
||||
/// Returns None if none left.
|
||||
/// Returns error if there is more than one item in the iterator.
|
||||
fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
|
||||
where
|
||||
I: Iterator,
|
||||
F: FnOnce(Vec<I::Item>) -> String,
|
||||
|
@ -199,53 +650,61 @@ where
|
|||
(Some(i1), Some(i2)) => {
|
||||
let mut v = vec![i1, i2];
|
||||
v.extend(i);
|
||||
Err(failure::format_err!("{}", f(v)))
|
||||
Err(format_err!("{}", f(v)))
|
||||
}
|
||||
(Some(i), None) => Ok(Some(i)),
|
||||
(None, _) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_crate_list(file: &FileLock) -> CargoResult<CrateListingV1> {
|
||||
let listing = (|| -> CargoResult<_> {
|
||||
let mut contents = String::new();
|
||||
file.file().read_to_string(&mut contents)?;
|
||||
let listing =
|
||||
toml::from_str(&contents).chain_err(|| internal("invalid TOML found for metadata"))?;
|
||||
match listing {
|
||||
CrateListing::V1(v1) => Ok(v1),
|
||||
CrateListing::Empty(_) => Ok(CrateListingV1 {
|
||||
v1: BTreeMap::new(),
|
||||
}),
|
||||
fn profile_name(release: bool) -> &'static str {
|
||||
if release {
|
||||
"release"
|
||||
} else {
|
||||
"dev"
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to convert features Vec to a BTreeSet.
|
||||
fn feature_set(features: &[String]) -> BTreeSet<String> {
|
||||
features.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// Helper to get the executable names from a filter.
|
||||
pub fn exe_names(pkg: &Package, filter: &ops::CompileFilter) -> BTreeSet<String> {
|
||||
let to_exe = |name| format!("{}{}", name, env::consts::EXE_SUFFIX);
|
||||
match filter {
|
||||
CompileFilter::Default { .. } => pkg
|
||||
.targets()
|
||||
.iter()
|
||||
.filter(|t| t.is_bin())
|
||||
.map(|t| to_exe(t.name()))
|
||||
.collect(),
|
||||
CompileFilter::Only {
|
||||
ref bins,
|
||||
ref examples,
|
||||
..
|
||||
} => {
|
||||
let all_bins: Vec<String> = bins.try_collect().unwrap_or_else(|| {
|
||||
pkg.targets()
|
||||
.iter()
|
||||
.filter(|t| t.is_bin())
|
||||
.map(|t| t.name().to_string())
|
||||
.collect()
|
||||
});
|
||||
let all_examples: Vec<String> = examples.try_collect().unwrap_or_else(|| {
|
||||
pkg.targets()
|
||||
.iter()
|
||||
.filter(|t| t.is_exe_example())
|
||||
.map(|t| t.name().to_string())
|
||||
.collect()
|
||||
});
|
||||
|
||||
all_bins
|
||||
.iter()
|
||||
.chain(all_examples.iter())
|
||||
.map(|name| to_exe(name))
|
||||
.collect()
|
||||
}
|
||||
})()
|
||||
.chain_err(|| {
|
||||
failure::format_err!(
|
||||
"failed to parse crate metadata at `{}`",
|
||||
file.path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
Ok(listing)
|
||||
}
|
||||
|
||||
pub fn write_crate_list(file: &FileLock, listing: CrateListingV1) -> CargoResult<()> {
|
||||
(|| -> CargoResult<_> {
|
||||
let mut file = file.file();
|
||||
file.seek(SeekFrom::Start(0))?;
|
||||
file.set_len(0)?;
|
||||
let data = toml::to_string(&CrateListing::V1(listing))?;
|
||||
file.write_all(data.as_bytes())?;
|
||||
Ok(())
|
||||
})()
|
||||
.chain_err(|| {
|
||||
failure::format_err!(
|
||||
"failed to write crate metadata at `{}`",
|
||||
file.path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn metadata(config: &Config, root: &Filesystem) -> CargoResult<FileLock> {
|
||||
root.open_rw(Path::new(".crates.toml"), config, "crate metadata")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -232,3 +232,32 @@ extra-info = "qwerty"
|
|||
|
||||
Metabuild packages should have a public function called `metabuild` that
|
||||
performs the same actions as a regular `build.rs` script would perform.
|
||||
|
||||
### install-upgrade
|
||||
* Tracking Issue: [#6797](https://github.com/rust-lang/cargo/issues/6797)
|
||||
|
||||
The `install-upgrade` feature changes the behavior of `cargo install` so that
|
||||
it will reinstall a package if it is not "up-to-date". If it is "up-to-date",
|
||||
it will do nothing and exit with success instead of failing. Example:
|
||||
|
||||
```
|
||||
cargo +nightly install foo -Z install-upgrade
|
||||
```
|
||||
|
||||
Cargo tracks some information to determine if a package is "up-to-date",
|
||||
including:
|
||||
|
||||
- The package version and source.
|
||||
- The set of binary names installed.
|
||||
- The chosen features.
|
||||
- The release mode (`--debug`).
|
||||
- The target (`--target`).
|
||||
|
||||
If any of these values change, then Cargo will reinstall the package.
|
||||
|
||||
Installation will still fail if a different package installs a binary of the
|
||||
same name. `--force` may be used to unconditionally reinstall the package.
|
||||
|
||||
Additionally, a new flag `--no-track` is available to prevent `cargo install`
|
||||
from writing tracking information in `$CARGO_HOME` about which packages are
|
||||
installed.
|
||||
|
|
|
@ -141,12 +141,14 @@ fn simple_install() {
|
|||
|
||||
cargo_process("install bar")
|
||||
.with_stderr(
|
||||
" Installing bar v0.1.0
|
||||
Compiling foo v0.0.1
|
||||
Compiling bar v0.1.0
|
||||
Finished release [optimized] target(s) in [..]s
|
||||
Installing [..]bar[..]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
"\
|
||||
[INSTALLING] bar v0.1.0
|
||||
[COMPILING] foo v0.0.1
|
||||
[COMPILING] bar v0.1.0
|
||||
[FINISHED] release [optimized] target(s) in [..]s
|
||||
[INSTALLING] [..]bar[..]
|
||||
[INSTALLED] package `bar v0.1.0` (executable `bar[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -229,12 +231,14 @@ fn install_without_feature_dep() {
|
|||
|
||||
cargo_process("install bar")
|
||||
.with_stderr(
|
||||
" Installing bar v0.1.0
|
||||
Compiling foo v0.0.1
|
||||
Compiling bar v0.1.0
|
||||
Finished release [optimized] target(s) in [..]s
|
||||
Installing [..]bar[..]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
"\
|
||||
[INSTALLING] bar v0.1.0
|
||||
[COMPILING] foo v0.0.1
|
||||
[COMPILING] bar v0.1.0
|
||||
[FINISHED] release [optimized] target(s) in [..]s
|
||||
[INSTALLING] [..]bar[..]
|
||||
[INSTALLED] package `bar v0.1.0` (executable `bar[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
|
|
@ -35,7 +35,8 @@ fn simple() {
|
|||
[COMPILING] foo v0.0.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -63,16 +64,18 @@ fn multiple_pkgs() {
|
|||
[COMPILING] foo v0.0.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] bar v0.0.2 (registry `[CWD]/registry`)
|
||||
[INSTALLING] bar v0.0.2
|
||||
[COMPILING] bar v0.0.2
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
|
||||
error: could not find `baz` in registry `[..]`
|
||||
[INSTALLED] package `bar v0.0.2` (executable `bar[EXE]`)
|
||||
[ERROR] could not find `baz` in registry `[..]`
|
||||
[SUMMARY] Successfully installed foo, bar! Failed to install baz (see error(s) above).
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
error: some crates failed to install
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[ERROR] some crates failed to install
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -111,7 +114,8 @@ fn pick_max_version() {
|
|||
[COMPILING] foo v0.2.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[INSTALLED] package `foo v0.2.1` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -251,7 +255,6 @@ fn install_path() {
|
|||
.with_status(101)
|
||||
.with_stderr(
|
||||
"\
|
||||
[INSTALLING] foo v0.0.1 [..]
|
||||
[ERROR] binary `foo[..]` already exists in destination as part of `foo v0.0.1 [..]`
|
||||
Add --force to overwrite
|
||||
",
|
||||
|
@ -427,8 +430,7 @@ fn no_binaries() {
|
|||
.with_status(101)
|
||||
.with_stderr(
|
||||
"\
|
||||
[INSTALLING] foo [..]
|
||||
[ERROR] specified package has no binaries
|
||||
[ERROR] specified package `foo v0.0.1 ([..])` has no binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -461,7 +463,6 @@ fn install_twice() {
|
|||
.with_status(101)
|
||||
.with_stderr(
|
||||
"\
|
||||
[INSTALLING] foo v0.0.1 [..]
|
||||
[ERROR] binary `foo-bin1[..]` already exists in destination as part of `foo v0.0.1 ([..])`
|
||||
binary `foo-bin2[..]` already exists in destination as part of `foo v0.0.1 ([..])`
|
||||
Add --force to overwrite
|
||||
|
@ -490,7 +491,8 @@ fn install_force() {
|
|||
[COMPILING] foo v0.2.0 ([..])
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[REPLACED] package `foo v0.0.1 ([..]/foo)` with `foo v0.2.0 ([..]/foo2)` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -530,7 +532,9 @@ fn install_force_partial_overlap() {
|
|||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/foo-bin3[EXE]
|
||||
[REPLACING] [CWD]/home/.cargo/bin/foo-bin2[EXE]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[INSTALLED] package `foo v0.2.0 ([..]/foo2)` (executable `foo-bin3[EXE]`)
|
||||
[REPLACED] package `foo v0.0.1 ([..]/foo)` with `foo v0.2.0 ([..]/foo2)` (executable `foo-bin2[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -572,7 +576,8 @@ fn install_force_bin() {
|
|||
[COMPILING] foo v0.2.0 ([..])
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[REPLACING] [CWD]/home/.cargo/bin/foo-bin2[EXE]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[REPLACED] package `foo v0.0.1 ([..]/foo)` with `foo v0.2.0 ([..]/foo2)` (executable `foo-bin2[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -627,7 +632,8 @@ fn git_repo() {
|
|||
[COMPILING] foo v0.1.0 ([..])
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[INSTALLED] package `foo v0.1.0 ([..]/foo#[..])` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -809,7 +815,8 @@ fn uninstall_cwd() {
|
|||
[COMPILING] foo v0.0.1 ([CWD])
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] {home}/bin/foo[EXE]
|
||||
warning: be sure to add `{home}/bin` to your PATH to be able to run the installed binaries",
|
||||
[INSTALLED] package `foo v0.0.1 ([..]/foo)` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `{home}/bin` to your PATH to be able to run the installed binaries",
|
||||
home = cargo_home().display(),
|
||||
))
|
||||
.run();
|
||||
|
@ -868,10 +875,12 @@ fn do_not_rebuilds_on_local_install() {
|
|||
cargo_process("install --path")
|
||||
.arg(p.root())
|
||||
.with_stderr(
|
||||
"[INSTALLING] [..]
|
||||
"\
|
||||
[INSTALLING] [..]
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[INSTALLED] package `foo v0.0.1 ([..]/foo)` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -1313,7 +1322,8 @@ fn workspace_uses_workspace_target_dir() {
|
|||
"[INSTALLING] [..]
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]
|
||||
warning: be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
[INSTALLED] package `bar v0.1.0 ([..]/bar)` (executable `bar[EXE]`)
|
||||
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
@ -1376,3 +1386,25 @@ fn install_path_config() {
|
|||
.with_stderr_contains("[..]--target nonexistent[..]")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_version_req() {
|
||||
// Try using a few versionreq styles.
|
||||
pkg("foo", "0.0.3");
|
||||
pkg("foo", "1.0.4");
|
||||
pkg("foo", "1.0.5");
|
||||
cargo_process("install foo --version=*")
|
||||
.with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
|
||||
.with_stderr_contains("[INSTALLING] foo v1.0.5")
|
||||
.run();
|
||||
cargo_process("uninstall foo").run();
|
||||
cargo_process("install foo --version=^1.0")
|
||||
.with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
|
||||
.with_stderr_contains("[INSTALLING] foo v1.0.5")
|
||||
.run();
|
||||
cargo_process("uninstall foo").run();
|
||||
cargo_process("install foo --version=0.0.*")
|
||||
.with_stderr_does_not_contain("[WARNING][..]is not a valid semver[..]")
|
||||
.with_stderr_contains("[INSTALLING] foo v0.0.3")
|
||||
.run();
|
||||
}
|
||||
|
|
718
tests/testsuite/install_upgrade.rs
Normal file
718
tests/testsuite/install_upgrade.rs
Normal file
|
@ -0,0 +1,718 @@
|
|||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::support::install::{cargo_home, exe};
|
||||
use crate::support::paths::CargoPathExt;
|
||||
use crate::support::registry::Package;
|
||||
use crate::support::{
|
||||
basic_manifest, cargo_process, cross_compile, execs, git, process, project, Execs,
|
||||
};
|
||||
|
||||
// Helper for publishing a package.
|
||||
fn pkg(name: &str, vers: &str) {
|
||||
Package::new(name, vers)
|
||||
.file(
|
||||
"src/main.rs",
|
||||
r#"fn main() { println!("{}", env!("CARGO_PKG_VERSION")) }"#,
|
||||
)
|
||||
.publish();
|
||||
}
|
||||
|
||||
fn v1_path() -> PathBuf {
|
||||
cargo_home().join(".crates.toml")
|
||||
}
|
||||
|
||||
fn v2_path() -> PathBuf {
|
||||
cargo_home().join(".crates2.json")
|
||||
}
|
||||
|
||||
fn load_crates2() -> serde_json::Value {
|
||||
serde_json::from_str(&fs::read_to_string(v2_path()).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
fn installed_exe(name: &str) -> PathBuf {
|
||||
cargo_home().join("bin").join(exe(name))
|
||||
}
|
||||
|
||||
/// Helper for executing binaries installed by cargo.
|
||||
fn installed_process(name: &str) -> Execs {
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
thread_local!(static UNIQUE_ID: usize = NEXT_ID.fetch_add(1, Ordering::SeqCst));
|
||||
|
||||
// This copies the executable to a unique name so that it may be safely
|
||||
// replaced on Windows. See Project::rename_run for details.
|
||||
let src = installed_exe(name);
|
||||
let dst = installed_exe(&UNIQUE_ID.with(|my_id| format!("{}-{}", name, my_id)));
|
||||
// Note: Cannot use copy. On Linux, file descriptors may be left open to
|
||||
// the executable as other tests in other threads are constantly spawning
|
||||
// new processes (see https://github.com/rust-lang/cargo/pull/5557 for
|
||||
// more).
|
||||
fs::rename(&src, &dst)
|
||||
.unwrap_or_else(|e| panic!("Failed to rename `{:?}` to `{:?}`: {}", src, dst, e));
|
||||
// Leave behind a fake file so that reinstall duplicate check works.
|
||||
fs::write(src, "").unwrap();
|
||||
let p = process(dst);
|
||||
execs().with_process_builder(p)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_upgrade() {
|
||||
// Installing and upgrading from a registry.
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] foo v1.0.0 (registry [..])
|
||||
[INSTALLING] foo v1.0.0
|
||||
[COMPILING] foo v1.0.0
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
[INSTALLED] package `foo v1.0.0` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
installed_process("foo").with_stdout("1.0.0").run();
|
||||
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[IGNORED] package `foo v1.0.0` is already installed[..]
|
||||
[WARNING] be sure to add [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
||||
pkg("foo", "1.0.1");
|
||||
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] foo v1.0.1 (registry [..])
|
||||
[INSTALLING] foo v1.0.1
|
||||
[COMPILING] foo v1.0.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
|
||||
[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
|
||||
installed_process("foo").with_stdout("1.0.1").run();
|
||||
|
||||
cargo_process("install foo --version=1.0.0 -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[COMPILING] foo v1.0.0")
|
||||
.run();
|
||||
installed_process("foo").with_stdout("1.0.0").run();
|
||||
|
||||
cargo_process("install foo --version=^1.0 -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[COMPILING] foo v1.0.1")
|
||||
.run();
|
||||
installed_process("foo").with_stdout("1.0.1").run();
|
||||
cargo_process("install foo --version=^1.0 -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[IGNORED] package `foo v1.0.1` is already installed[..]")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uninstall() {
|
||||
// Basic uninstall test.
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
cargo_process("uninstall foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
let data = load_crates2();
|
||||
assert_eq!(data["installs"].as_object().unwrap().len(), 0);
|
||||
let v1 = cargo_home().join(".crates.toml");
|
||||
let data: toml::Value = toml::from_str(&fs::read_to_string(&v1).unwrap()).unwrap();
|
||||
assert_eq!(data.get("v1").unwrap().as_table().unwrap().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_force() {
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
cargo_process("install foo -Z install-upgrade --force")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[INSTALLING] foo v1.0.0
|
||||
[COMPILING] foo v1.0.0
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[REPLACING] [..]/.cargo/bin/foo[EXE]
|
||||
[REPLACED] package `foo v1.0.0` with `foo v1.0.0` (executable `foo[EXE]`)
|
||||
[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ambiguous_version_no_longer_allowed() {
|
||||
// Non-semver-requirement is not allowed for `--version`.
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo --version=1.0 -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[ERROR] the `--vers` provided, `1.0`, is not a valid semver version: cannot parse '1.0' as a semver
|
||||
|
||||
if you want to specify semver range, add an explicit qualifier, like ^1.0
|
||||
",
|
||||
)
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_is_always_dirty() {
|
||||
// --path should always reinstall.
|
||||
let p = project().file("src/main.rs", "fn main() {}").build();
|
||||
p.cargo("install --path . -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
p.cargo("install --path . -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[REPLACING] [..]/foo[EXE]")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_conflicts_unknown() {
|
||||
// If an untracked file is in the way, it should fail.
|
||||
pkg("foo", "1.0.0");
|
||||
let exe = installed_exe("foo");
|
||||
exe.parent().unwrap().mkdir_p();
|
||||
fs::write(exe, "").unwrap();
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[ERROR] binary `foo[EXE]` already exists in destination")
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_for_conflicts_known() {
|
||||
// If the same binary exists in another package, it should fail.
|
||||
pkg("foo", "1.0.0");
|
||||
Package::new("bar", "1.0.0")
|
||||
.file("src/bin/foo.rs", "fn main() {}")
|
||||
.publish();
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
cargo_process("install bar -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains(
|
||||
"[ERROR] binary `foo[EXE]` already exists in destination as part of `foo v1.0.0`",
|
||||
)
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_multiple_binary_names() {
|
||||
// Can individually install with --bin or --example
|
||||
Package::new("foo", "1.0.0")
|
||||
.file("src/main.rs", r#"fn main() { println!("foo"); }"#)
|
||||
.file("src/bin/a.rs", r#"fn main() { println!("a"); }"#)
|
||||
.file("examples/ex1.rs", r#"fn main() { println!("ex1"); }"#)
|
||||
.publish();
|
||||
cargo_process("install foo -Z install-upgrade --bin foo")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("foo").run();
|
||||
assert!(!installed_exe("a").exists());
|
||||
assert!(!installed_exe("ex1").exists());
|
||||
cargo_process("install foo -Z install-upgrade --bin a")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("a").with_stdout("a").run();
|
||||
assert!(!installed_exe("ex1").exists());
|
||||
cargo_process("install foo -Z install-upgrade --example ex1")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("ex1").with_stdout("ex1").run();
|
||||
cargo_process("uninstall foo -Z install-upgrade --bin foo")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
assert!(!installed_exe("foo").exists());
|
||||
assert!(installed_exe("ex1").exists());
|
||||
cargo_process("uninstall foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
assert!(!installed_exe("ex1").exists());
|
||||
assert!(!installed_exe("a").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_already_installed_fresh() {
|
||||
// Install with v1, then try to install again with v2.
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo").run();
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.with_stderr_contains("[IGNORED] package `foo v1.0.0` is already installed[..]")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v1_already_installed_dirty() {
|
||||
// Install with v1, then install a new version with v2.
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo").run();
|
||||
pkg("foo", "1.0.1");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.with_stderr_contains("[COMPILING] foo v1.0.1")
|
||||
.with_stderr_contains("[REPLACING] [..]/foo[EXE]")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_features_rebuilds() {
|
||||
Package::new("foo", "1.0.0")
|
||||
.file(
|
||||
"src/main.rs",
|
||||
r#"fn main() {
|
||||
if cfg!(feature = "f1") {
|
||||
println!("f1");
|
||||
}
|
||||
if cfg!(feature = "f2") {
|
||||
println!("f2");
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
.file(
|
||||
"Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "foo"
|
||||
version = "1.0.0"
|
||||
|
||||
[features]
|
||||
f1 = []
|
||||
f2 = []
|
||||
default = ["f1"]
|
||||
"#,
|
||||
)
|
||||
.publish();
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("f1").run();
|
||||
cargo_process("install foo -Z install-upgrade --no-default-features")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("").run();
|
||||
cargo_process("install foo -Z install-upgrade --all-features")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("f1\nf2").run();
|
||||
cargo_process("install foo -Z install-upgrade --no-default-features --features=f1")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("f1").run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_profile_rebuilds() {
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
cargo_process("install foo -Z install-upgrade --debug")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[COMPILING] foo v1.0.0")
|
||||
.with_stderr_contains("[REPLACING] [..]foo[EXE]")
|
||||
.run();
|
||||
cargo_process("install foo -Z install-upgrade --debug")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[IGNORED] package `foo v1.0.0` is already installed[..]")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_target_rebuilds() {
|
||||
if cross_compile::disabled() {
|
||||
return;
|
||||
}
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
let target = cross_compile::alternate();
|
||||
cargo_process("install foo -v -Z install-upgrade --target")
|
||||
.arg(&target)
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[COMPILING] foo v1.0.0")
|
||||
.with_stderr_contains("[REPLACING] [..]foo[EXE]")
|
||||
.with_stderr_contains(&format!("[..]--target {}[..]", target))
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_bin_sets_rebuilds() {
|
||||
// Changing which bins in a multi-bin project should reinstall.
|
||||
Package::new("foo", "1.0.0")
|
||||
.file("src/main.rs", "fn main() { }")
|
||||
.file("src/bin/x.rs", "fn main() { }")
|
||||
.file("src/bin/y.rs", "fn main() { }")
|
||||
.publish();
|
||||
cargo_process("install foo -Z install-upgrade --bin x")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
assert!(installed_exe("x").exists());
|
||||
assert!(!installed_exe("y").exists());
|
||||
assert!(!installed_exe("foo").exists());
|
||||
cargo_process("install foo -Z install-upgrade --bin y")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[INSTALLED] package `foo v1.0.0` (executable `y[EXE]`)")
|
||||
.run();
|
||||
assert!(installed_exe("x").exists());
|
||||
assert!(installed_exe("y").exists());
|
||||
assert!(!installed_exe("foo").exists());
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[INSTALLED] package `foo v1.0.0` (executable `foo[EXE]`)")
|
||||
.with_stderr_contains(
|
||||
"[REPLACED] package `foo v1.0.0` with `foo v1.0.0` (executables `x[EXE]`, `y[EXE]`)",
|
||||
)
|
||||
.run();
|
||||
assert!(installed_exe("x").exists());
|
||||
assert!(installed_exe("y").exists());
|
||||
assert!(installed_exe("foo").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forwards_compatible() {
|
||||
// Unknown fields should be preserved.
|
||||
pkg("foo", "1.0.0");
|
||||
pkg("bar", "1.0.0");
|
||||
cargo_process("install foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
let key = "foo 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)";
|
||||
let v2 = cargo_home().join(".crates2.json");
|
||||
let mut data = load_crates2();
|
||||
data["newfield"] = serde_json::Value::Bool(true);
|
||||
data["installs"][key]["moreinfo"] = serde_json::Value::String("shazam".to_string());
|
||||
fs::write(&v2, serde_json::to_string(&data).unwrap()).unwrap();
|
||||
cargo_process("install bar -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
let data: serde_json::Value = serde_json::from_str(&fs::read_to_string(&v2).unwrap()).unwrap();
|
||||
assert_eq!(data["newfield"].as_bool().unwrap(), true);
|
||||
assert_eq!(
|
||||
data["installs"][key]["moreinfo"].as_str().unwrap(),
|
||||
"shazam"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v2_syncs() {
|
||||
// V2 inherits the installs from V1.
|
||||
pkg("one", "1.0.0");
|
||||
pkg("two", "1.0.0");
|
||||
pkg("three", "1.0.0");
|
||||
let p = project()
|
||||
.file("src/bin/x.rs", "fn main() {}")
|
||||
.file("src/bin/y.rs", "fn main() {}")
|
||||
.build();
|
||||
cargo_process("install one -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
p.cargo("install -Z install-upgrade --path .")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
// v1 add/remove
|
||||
cargo_process("install two").run();
|
||||
cargo_process("uninstall one").run();
|
||||
// This should pick up that `two` was added, `one` was removed.
|
||||
cargo_process("install three -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
cargo_process("install --list")
|
||||
.with_stdout(
|
||||
"\
|
||||
foo v0.0.1 ([..]/foo):
|
||||
x[EXE]
|
||||
y[EXE]
|
||||
three v1.0.0:
|
||||
three[EXE]
|
||||
two v1.0.0:
|
||||
two[EXE]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
cargo_process("install one -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("one").with_stdout("1.0.0").run();
|
||||
cargo_process("install two -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[IGNORED] package `two v1.0.0` is already installed[..]")
|
||||
.run();
|
||||
// v1 remove
|
||||
p.cargo("uninstall --bin x").run();
|
||||
pkg("x", "1.0.0");
|
||||
pkg("y", "1.0.0");
|
||||
// This should succeed because `x` was removed in V1.
|
||||
cargo_process("install x -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
// This should fail because `y` still exists in a different package.
|
||||
cargo_process("install y -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains(
|
||||
"[ERROR] binary `y[EXE]` already exists in destination \
|
||||
as part of `foo v0.0.1 ([..])`",
|
||||
)
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upgrade_git() {
|
||||
let git_project =
|
||||
git::new("foo", |project| project.file("src/main.rs", "fn main() {}")).unwrap();
|
||||
// install
|
||||
cargo_process("install -Z install-upgrade --git")
|
||||
.arg(git_project.url().to_string())
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
// Check install stays fresh.
|
||||
cargo_process("install -Z install-upgrade --git")
|
||||
.arg(git_project.url().to_string())
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains(
|
||||
"[IGNORED] package `foo v0.0.1 (file://[..]/foo#[..])` is \
|
||||
already installed,[..]",
|
||||
)
|
||||
.run();
|
||||
// Modify a file.
|
||||
let repo = git2::Repository::open(git_project.root()).unwrap();
|
||||
git_project.change_file("src/main.rs", r#"fn main() {println!("onomatopoeia");}"#);
|
||||
git::add(&repo);
|
||||
git::commit(&repo);
|
||||
// Install should reinstall.
|
||||
cargo_process("install -Z install-upgrade --git")
|
||||
.arg(git_project.url().to_string())
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
|
||||
.with_stderr_contains("[REPLACING] [..]/foo[EXE]")
|
||||
.run();
|
||||
installed_process("foo").with_stdout("onomatopoeia").run();
|
||||
// Check install stays fresh.
|
||||
cargo_process("install -Z install-upgrade --git")
|
||||
.arg(git_project.url().to_string())
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr_contains(
|
||||
"[IGNORED] package `foo v0.0.1 (file://[..]/foo#[..])` is \
|
||||
already installed,[..]",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn switch_sources() {
|
||||
// Installing what appears to be the same thing, but from different
|
||||
// sources should reinstall.
|
||||
pkg("foo", "1.0.0");
|
||||
Package::new("foo", "1.0.0")
|
||||
.file("src/main.rs", r#"fn main() { println!("alt"); }"#)
|
||||
.alternative(true)
|
||||
.publish();
|
||||
let p = project()
|
||||
.at("foo-local") // so it doesn't use the same directory as the git project
|
||||
.file("Cargo.toml", &basic_manifest("foo", "1.0.0"))
|
||||
.file("src/main.rs", r#"fn main() { println!("local"); }"#)
|
||||
.build();
|
||||
let git_project = git::new("foo", |project| {
|
||||
project.file("src/main.rs", r#"fn main() { println!("git"); }"#)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cargo_process("install -Z install-upgrade foo")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("1.0.0").run();
|
||||
cargo_process("install -Z install-upgrade foo --registry alternative")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("alt").run();
|
||||
p.cargo("install -Z install-upgrade --path .")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("local").run();
|
||||
cargo_process("install -Z install-upgrade --git")
|
||||
.arg(git_project.url().to_string())
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
installed_process("foo").with_stdout("git").run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_report() {
|
||||
// Testing the full output that indicates installed/ignored/replaced/summary.
|
||||
pkg("one", "1.0.0");
|
||||
pkg("two", "1.0.0");
|
||||
fn three(vers: &str) {
|
||||
Package::new("three", vers)
|
||||
.file("src/main.rs", "fn main() { }")
|
||||
.file("src/bin/x.rs", "fn main() { }")
|
||||
.file("src/bin/y.rs", "fn main() { }")
|
||||
.publish();
|
||||
}
|
||||
three("1.0.0");
|
||||
cargo_process("install -Z install-upgrade one two three")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] one v1.0.0 (registry `[..]`)
|
||||
[INSTALLING] one v1.0.0
|
||||
[COMPILING] one v1.0.0
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]/.cargo/bin/one[EXE]
|
||||
[INSTALLED] package `one v1.0.0` (executable `one[EXE]`)
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] two v1.0.0 (registry `[..]`)
|
||||
[INSTALLING] two v1.0.0
|
||||
[COMPILING] two v1.0.0
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]/.cargo/bin/two[EXE]
|
||||
[INSTALLED] package `two v1.0.0` (executable `two[EXE]`)
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] three v1.0.0 (registry `[..]`)
|
||||
[INSTALLING] three v1.0.0
|
||||
[COMPILING] three v1.0.0
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]/.cargo/bin/three[EXE]
|
||||
[INSTALLING] [..]/.cargo/bin/x[EXE]
|
||||
[INSTALLING] [..]/.cargo/bin/y[EXE]
|
||||
[INSTALLED] package `three v1.0.0` (executables `three[EXE]`, `x[EXE]`, `y[EXE]`)
|
||||
[SUMMARY] Successfully installed one, two, three!
|
||||
[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
pkg("foo", "1.0.1");
|
||||
pkg("bar", "1.0.1");
|
||||
three("1.0.1");
|
||||
cargo_process("install -Z install-upgrade one two three")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[IGNORED] package `one v1.0.0` is already installed, use --force to override
|
||||
[IGNORED] package `two v1.0.0` is already installed, use --force to override
|
||||
[DOWNLOADING] crates ...
|
||||
[DOWNLOADED] three v1.0.1 (registry `[..]`)
|
||||
[INSTALLING] three v1.0.1
|
||||
[COMPILING] three v1.0.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[REPLACING] [..]/.cargo/bin/three[EXE]
|
||||
[REPLACING] [..]/.cargo/bin/x[EXE]
|
||||
[REPLACING] [..]/.cargo/bin/y[EXE]
|
||||
[REPLACED] package `three v1.0.0` with `three v1.0.1` (executables `three[EXE]`, `x[EXE]`, `y[EXE]`)
|
||||
[SUMMARY] Successfully installed one, two, three!
|
||||
[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
cargo_process("uninstall -Z install-upgrade three")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[REMOVING] [..]/.cargo/bin/three[EXE]
|
||||
[REMOVING] [..]/.cargo/bin/x[EXE]
|
||||
[REMOVING] [..]/.cargo/bin/y[EXE]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
cargo_process("install -Z install-upgrade three --bin x")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[INSTALLING] three v1.0.1
|
||||
[COMPILING] three v1.0.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]/.cargo/bin/x[EXE]
|
||||
[INSTALLED] package `three v1.0.1` (executable `x[EXE]`)
|
||||
[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
cargo_process("install -Z install-upgrade three")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[INSTALLING] three v1.0.1
|
||||
[COMPILING] three v1.0.1
|
||||
[FINISHED] release [optimized] target(s) in [..]
|
||||
[INSTALLING] [..]/.cargo/bin/three[EXE]
|
||||
[INSTALLING] [..]/.cargo/bin/y[EXE]
|
||||
[REPLACING] [..]/.cargo/bin/x[EXE]
|
||||
[INSTALLED] package `three v1.0.1` (executables `three[EXE]`, `y[EXE]`)
|
||||
[REPLACED] package `three v1.0.1` with `three v1.0.1` (executable `x[EXE]`)
|
||||
[WARNING] be sure to add `[..]/.cargo/bin` to your PATH [..]
|
||||
",
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_track_gated() {
|
||||
cargo_process("install --no-track foo")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"[ERROR] `--no-track` flag is unstable, pass `-Z install-upgrade` to enable it",
|
||||
)
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_track() {
|
||||
pkg("foo", "1.0.0");
|
||||
cargo_process("install --no-track foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.run();
|
||||
assert!(!v1_path().exists());
|
||||
assert!(!v2_path().exists());
|
||||
cargo_process("install --no-track foo -Z install-upgrade")
|
||||
.masquerade_as_nightly_cargo()
|
||||
.with_stderr(
|
||||
"\
|
||||
[UPDATING] `[..]` index
|
||||
[ERROR] binary `foo[EXE]` already exists in destination
|
||||
Add --force to overwrite
|
||||
",
|
||||
)
|
||||
.with_status(101)
|
||||
.run();
|
||||
}
|
|
@ -44,6 +44,7 @@ mod generate_lockfile;
|
|||
mod git;
|
||||
mod init;
|
||||
mod install;
|
||||
mod install_upgrade;
|
||||
mod jobserver;
|
||||
mod list_targets;
|
||||
mod local_registry;
|
||||
|
|
|
@ -1616,6 +1616,9 @@ fn substitute_macros(input: &str) -> String {
|
|||
("[SUMMARY]", " Summary"),
|
||||
("[FIXING]", " Fixing"),
|
||||
("[EXE]", env::consts::EXE_SUFFIX),
|
||||
("[IGNORED]", " Ignored"),
|
||||
("[INSTALLED]", " Installed"),
|
||||
("[REPLACED]", " Replaced"),
|
||||
];
|
||||
let mut result = input.to_owned();
|
||||
for &(pat, subst) in ¯os {
|
||||
|
|
Loading…
Reference in a new issue