Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9b94513

Browse files
committedMay 20, 2020
Auto merge of #8022 - illicitonion:trywithout, r=ehuss
Try installing exact versions before updating When an exact version is being installed, if we already have that version from the index, we don't need to update the index before installing it. Don't do this if it's not an exact version, because the update may find us a newer version. This is particularly useful for scripts which unconditionally run `cargo install some-crate --version=1.2.3`. Before install-update, I wrote a crate to do this (https://crates.io/crates/cargo-ensure-installed) which I'm trying to replace with just `cargo install`, but the extra latency of updating the index for a no-op is noticeable.
2 parents cf00ee1 + b719272 commit 9b94513

File tree

5 files changed

+420
-195
lines changed

5 files changed

+420
-195
lines changed
 

‎src/cargo/ops/cargo_install.rs‎

Lines changed: 241 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ use std::sync::Arc;
44
use std::{env, fs};
55

66
use anyhow::{bail, format_err};
7+
use semver::VersionReq;
78
use tempfile::Builder as TempFileBuilder;
89

910
use crate::core::compiler::Freshness;
1011
use crate::core::compiler::{CompileKind, DefaultExecutor, Executor};
11-
use crate::core::{Edition, Package, PackageId, Source, SourceId, Workspace};
12+
use crate::core::{Dependency, Edition, Package, PackageId, Source, SourceId, Workspace};
1213
use crate::ops::common_for_install_and_uninstall::*;
13-
use crate::sources::{GitSource, SourceConfigMap};
14+
use crate::sources::{GitSource, PathSource, SourceConfigMap};
1415
use crate::util::errors::{CargoResult, CargoResultExt};
15-
use crate::util::{paths, Config, Filesystem};
16+
use crate::util::{paths, Config, Filesystem, Rustc, ToSemver};
1617
use crate::{drop_println, ops};
1718

1819
struct Transaction {
@@ -65,7 +66,9 @@ pub fn install(
6566
} else {
6667
let mut succeeded = vec![];
6768
let mut failed = vec![];
68-
let mut first = true;
69+
// "Tracks whether or not the source (such as a registry or git repo) has been updated.
70+
// This is used to avoid updating it multiple times when installing multiple crates.
71+
let mut did_update = false;
6972
for krate in krates {
7073
let root = root.clone();
7174
let map = map.clone();
@@ -80,15 +83,19 @@ pub fn install(
8083
opts,
8184
force,
8285
no_track,
83-
first,
86+
!did_update,
8487
) {
85-
Ok(()) => succeeded.push(krate),
88+
Ok(still_needs_update) => {
89+
succeeded.push(krate);
90+
did_update |= !still_needs_update;
91+
}
8692
Err(e) => {
8793
crate::display_error(&e, &mut config.shell());
88-
failed.push(krate)
94+
failed.push(krate);
95+
// We assume an update was performed if we got an error.
96+
did_update = true;
8997
}
9098
}
91-
first = false;
9299
}
93100

94101
let mut summary = vec![];
@@ -133,6 +140,11 @@ pub fn install(
133140
Ok(())
134141
}
135142

143+
// Returns whether a subsequent call should attempt to update again.
144+
// The `needs_update_if_source_is_index` parameter indicates whether or not the source index should
145+
// be updated. This is used ensure it is only updated once when installing multiple crates.
146+
// The return value here is used so that the caller knows what to pass to the
147+
// `needs_update_if_source_is_index` parameter when `install_one` is called again.
136148
fn install_one(
137149
config: &Config,
138150
root: &Filesystem,
@@ -144,8 +156,8 @@ fn install_one(
144156
opts: &ops::CompileOptions,
145157
force: bool,
146158
no_track: bool,
147-
is_first_install: bool,
148-
) -> CargoResult<()> {
159+
needs_update_if_source_is_index: bool,
160+
) -> CargoResult<bool> {
149161
if let Some(name) = krate {
150162
if name == "." {
151163
bail!(
@@ -155,72 +167,110 @@ fn install_one(
155167
)
156168
}
157169
}
158-
let pkg = if source_id.is_git() {
159-
select_pkg(
160-
GitSource::new(source_id, config)?,
161-
krate,
162-
vers,
163-
config,
164-
true,
165-
&mut |git| git.read_packages(),
166-
)?
167-
} else if source_id.is_path() {
168-
let mut src = path_source(source_id, config)?;
169-
if !src.path().is_dir() {
170-
bail!(
171-
"`{}` is not a directory. \
172-
--path must point to a directory containing a Cargo.toml file.",
173-
src.path().display()
174-
)
175-
}
176-
if !src.path().join("Cargo.toml").exists() {
177-
if from_cwd {
178-
bail!(
179-
"`{}` is not a crate root; specify a crate to \
180-
install from crates.io, or use --path or --git to \
181-
specify an alternate source",
182-
src.path().display()
183-
);
170+
171+
let dst = root.join("bin").into_path_unlocked();
172+
173+
let pkg = {
174+
let dep = {
175+
if let Some(krate) = krate {
176+
let vers = if let Some(vers_flag) = vers {
177+
Some(parse_semver_flag(vers_flag)?.to_string())
178+
} else {
179+
if source_id.is_registry() {
180+
// Avoid pre-release versions from crate.io
181+
// unless explicitly asked for
182+
Some(String::from("*"))
183+
} else {
184+
None
185+
}
186+
};
187+
Some(Dependency::parse_no_deprecated(
188+
krate,
189+
vers.as_deref(),
190+
source_id,
191+
)?)
184192
} else {
193+
None
194+
}
195+
};
196+
197+
if source_id.is_git() {
198+
let mut source = GitSource::new(source_id, config)?;
199+
select_pkg(
200+
&mut source,
201+
dep,
202+
|git: &mut GitSource<'_>| git.read_packages(),
203+
config,
204+
)?
205+
} else if source_id.is_path() {
206+
let mut src = path_source(source_id, config)?;
207+
if !src.path().is_dir() {
185208
bail!(
186-
"`{}` does not contain a Cargo.toml file. \
187-
--path must point to a directory containing a Cargo.toml file.",
209+
"`{}` is not a directory. \
210+
--path must point to a directory containing a Cargo.toml file.",
188211
src.path().display()
189212
)
190213
}
191-
}
192-
src.update()?;
193-
select_pkg(src, krate, vers, config, false, &mut |path| {
194-
path.read_packages()
195-
})?
196-
} else {
197-
select_pkg(
198-
map.load(source_id, &HashSet::new())?,
199-
krate,
200-
vers,
201-
config,
202-
is_first_install,
203-
&mut |_| {
214+
if !src.path().join("Cargo.toml").exists() {
215+
if from_cwd {
216+
bail!(
217+
"`{}` is not a crate root; specify a crate to \
218+
install from crates.io, or use --path or --git to \
219+
specify an alternate source",
220+
src.path().display()
221+
);
222+
} else {
223+
bail!(
224+
"`{}` does not contain a Cargo.toml file. \
225+
--path must point to a directory containing a Cargo.toml file.",
226+
src.path().display()
227+
)
228+
}
229+
}
230+
select_pkg(
231+
&mut src,
232+
dep,
233+
|path: &mut PathSource<'_>| path.read_packages(),
234+
config,
235+
)?
236+
} else {
237+
if let Some(dep) = dep {
238+
let mut source = map.load(source_id, &HashSet::new())?;
239+
if let Ok(Some(pkg)) = installed_exact_package(
240+
dep.clone(),
241+
&mut source,
242+
config,
243+
opts,
244+
root,
245+
&dst,
246+
force,
247+
) {
248+
let msg = format!(
249+
"package `{}` is already installed, use --force to override",
250+
pkg
251+
);
252+
config.shell().status("Ignored", &msg)?;
253+
return Ok(true);
254+
}
255+
select_dep_pkg(&mut source, dep, config, needs_update_if_source_is_index)?
256+
} else {
204257
bail!(
205258
"must specify a crate to install from \
206259
crates.io, or use --path or --git to \
207260
specify alternate source"
208261
)
209-
},
210-
)?
262+
}
263+
}
211264
};
212265

213-
let (mut ws, git_package) = if source_id.is_git() {
266+
let (mut ws, rustc, target) = make_ws_rustc_target(config, opts, &source_id, pkg.clone())?;
267+
let pkg = if source_id.is_git() {
214268
// Don't use ws.current() in order to keep the package source as a git source so that
215269
// install tracking uses the correct source.
216-
(Workspace::new(pkg.manifest_path(), config)?, Some(&pkg))
217-
} else if source_id.is_path() {
218-
(Workspace::new(pkg.manifest_path(), config)?, None)
270+
pkg
219271
} else {
220-
(Workspace::ephemeral(pkg, config, None, false)?, None)
272+
ws.current()?.clone()
221273
};
222-
ws.set_ignore_lock(config.lock_update_allowed());
223-
ws.set_require_optional_deps(false);
224274

225275
let mut td_opt = None;
226276
let mut needs_cleanup = false;
@@ -238,8 +288,6 @@ fn install_one(
238288
ws.set_target_dir(target_dir);
239289
}
240290

241-
let pkg = git_package.map_or_else(|| ws.current(), |pkg| Ok(pkg))?;
242-
243291
if from_cwd {
244292
if pkg.manifest().edition() == Edition::Edition2015 {
245293
config.shell().warn(
@@ -265,20 +313,9 @@ fn install_one(
265313
bail!("specified package `{}` has no binaries", pkg);
266314
}
267315

268-
// Preflight checks to check up front whether we'll overwrite something.
269-
// We have to check this again afterwards, but may as well avoid building
270-
// anything if we're gonna throw it away anyway.
271-
let dst = root.join("bin").into_path_unlocked();
272-
let rustc = config.load_global_rustc(Some(&ws))?;
273-
let requested_kind = opts.build_config.single_requested_kind()?;
274-
let target = match &requested_kind {
275-
CompileKind::Host => rustc.host.as_str(),
276-
CompileKind::Target(target) => target.short_name(),
277-
};
278-
279316
// Helper for --no-track flag to make sure it doesn't overwrite anything.
280317
let no_track_duplicates = || -> CargoResult<BTreeMap<String, Option<PackageId>>> {
281-
let duplicates: BTreeMap<String, Option<PackageId>> = exe_names(pkg, &opts.filter)
318+
let duplicates: BTreeMap<String, Option<PackageId>> = exe_names(&pkg, &opts.filter)
282319
.into_iter()
283320
.filter(|name| dst.join(name).exists())
284321
.map(|name| (name, None))
@@ -300,22 +337,17 @@ fn install_one(
300337
// Check for conflicts.
301338
no_track_duplicates()?;
302339
} else {
303-
let tracker = InstallTracker::load(config, root)?;
304-
let (freshness, _duplicates) =
305-
tracker.check_upgrade(&dst, pkg, force, opts, target, &rustc.verbose_version)?;
306-
if freshness == Freshness::Fresh {
340+
if is_installed(&pkg, config, opts, &rustc, &target, root, &dst, force)? {
307341
let msg = format!(
308342
"package `{}` is already installed, use --force to override",
309343
pkg
310344
);
311345
config.shell().status("Ignored", &msg)?;
312-
return Ok(());
346+
return Ok(false);
313347
}
314-
// Unlock while building.
315-
drop(tracker);
316348
}
317349

318-
config.shell().status("Installing", pkg)?;
350+
config.shell().status("Installing", &pkg)?;
319351

320352
check_yanked_install(&ws)?;
321353

@@ -356,7 +388,7 @@ fn install_one(
356388
} else {
357389
let tracker = InstallTracker::load(config, root)?;
358390
let (_freshness, duplicates) =
359-
tracker.check_upgrade(&dst, pkg, force, opts, target, &rustc.verbose_version)?;
391+
tracker.check_upgrade(&dst, &pkg, force, opts, &target, &rustc.verbose_version)?;
360392
(Some(tracker), duplicates)
361393
};
362394

@@ -417,15 +449,15 @@ fn install_one(
417449

418450
if let Some(mut tracker) = tracker {
419451
tracker.mark_installed(
420-
pkg,
452+
&pkg,
421453
&successful_bins,
422454
vers.map(|s| s.to_string()),
423455
opts,
424-
target,
456+
&target,
425457
&rustc.verbose_version,
426458
);
427459

428-
if let Err(e) = remove_orphaned_bins(&ws, &mut tracker, &duplicates, pkg, &dst) {
460+
if let Err(e) = remove_orphaned_bins(&ws, &mut tracker, &duplicates, &pkg, &dst) {
429461
// Don't hard error on remove.
430462
config
431463
.shell()
@@ -467,7 +499,7 @@ fn install_one(
467499
"Installed",
468500
format!("package `{}` {}", pkg, executables(successful_bins.iter())),
469501
)?;
470-
Ok(())
502+
Ok(false)
471503
} else {
472504
if !to_install.is_empty() {
473505
config.shell().status(
@@ -492,7 +524,128 @@ fn install_one(
492524
),
493525
)?;
494526
}
495-
Ok(())
527+
Ok(false)
528+
}
529+
}
530+
531+
fn is_installed(
532+
pkg: &Package,
533+
config: &Config,
534+
opts: &ops::CompileOptions,
535+
rustc: &Rustc,
536+
target: &str,
537+
root: &Filesystem,
538+
dst: &Path,
539+
force: bool,
540+
) -> CargoResult<bool> {
541+
let tracker = InstallTracker::load(config, root)?;
542+
let (freshness, _duplicates) =
543+
tracker.check_upgrade(dst, pkg, force, opts, target, &rustc.verbose_version)?;
544+
Ok(freshness == Freshness::Fresh)
545+
}
546+
547+
/// Checks if vers can only be satisfied by exactly one version of a package in a registry, and it's
548+
/// already installed. If this is the case, we can skip interacting with a registry to check if
549+
/// newer versions may be installable, as no newer version can exist.
550+
fn installed_exact_package<T>(
551+
dep: Dependency,
552+
source: &mut T,
553+
config: &Config,
554+
opts: &ops::CompileOptions,
555+
root: &Filesystem,
556+
dst: &Path,
557+
force: bool,
558+
) -> CargoResult<Option<Package>>
559+
where
560+
T: Source,
561+
{
562+
if !dep.is_locked() {
563+
// If the version isn't exact, we may need to update the registry and look for a newer
564+
// version - we can't know if the package is installed without doing so.
565+
return Ok(None);
566+
}
567+
// Try getting the package from the registry without updating it, to avoid a potentially
568+
// expensive network call in the case that the package is already installed.
569+
// If this fails, the caller will possibly do an index update and try again, this is just a
570+
// best-effort check to see if we can avoid hitting the network.
571+
if let Ok(pkg) = select_dep_pkg(source, dep, config, false) {
572+
let (_ws, rustc, target) =
573+
make_ws_rustc_target(&config, opts, &source.source_id(), pkg.clone())?;
574+
if let Ok(true) = is_installed(&pkg, config, opts, &rustc, &target, root, &dst, force) {
575+
return Ok(Some(pkg));
576+
}
577+
}
578+
Ok(None)
579+
}
580+
581+
fn make_ws_rustc_target<'cfg>(
582+
config: &'cfg Config,
583+
opts: &ops::CompileOptions,
584+
source_id: &SourceId,
585+
pkg: Package,
586+
) -> CargoResult<(Workspace<'cfg>, Rustc, String)> {
587+
let mut ws = if source_id.is_git() || source_id.is_path() {
588+
Workspace::new(pkg.manifest_path(), config)?
589+
} else {
590+
Workspace::ephemeral(pkg, config, None, false)?
591+
};
592+
ws.set_ignore_lock(config.lock_update_allowed());
593+
ws.set_require_optional_deps(false);
594+
595+
let rustc = config.load_global_rustc(Some(&ws))?;
596+
let target = match &opts.build_config.single_requested_kind()? {
597+
CompileKind::Host => rustc.host.as_str().to_owned(),
598+
CompileKind::Target(target) => target.short_name().to_owned(),
599+
};
600+
601+
Ok((ws, rustc, target))
602+
}
603+
604+
/// Parses x.y.z as if it were =x.y.z, and gives CLI-specific error messages in the case of invalid
605+
/// values.
606+
fn parse_semver_flag(v: &str) -> CargoResult<VersionReq> {
607+
// If the version begins with character <, >, =, ^, ~ parse it as a
608+
// version range, otherwise parse it as a specific version
609+
let first = v
610+
.chars()
611+
.next()
612+
.ok_or_else(|| format_err!("no version provided for the `--vers` flag"))?;
613+
614+
let is_req = "<>=^~".contains(first) || v.contains('*');
615+
if is_req {
616+
match v.parse::<VersionReq>() {
617+
Ok(v) => Ok(v),
618+
Err(_) => bail!(
619+
"the `--vers` provided, `{}`, is \
620+
not a valid semver version requirement\n\n\
621+
Please have a look at \
622+
https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \
623+
for the correct format",
624+
v
625+
),
626+
}
627+
} else {
628+
match v.to_semver() {
629+
Ok(v) => Ok(VersionReq::exact(&v)),
630+
Err(e) => {
631+
let mut msg = format!(
632+
"the `--vers` provided, `{}`, is \
633+
not a valid semver version: {}\n",
634+
v, e
635+
);
636+
637+
// If it is not a valid version but it is a valid version
638+
// requirement, add a note to the warning
639+
if v.parse::<VersionReq>().is_ok() {
640+
msg.push_str(&format!(
641+
"\nif you want to specify semver range, \
642+
add an explicit qualifier, like ^{}",
643+
v
644+
));
645+
}
646+
bail!(msg);
647+
}
648+
}
496649
}
497650
}
498651

‎src/cargo/ops/cargo_uninstall.rs‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::env;
55
use crate::core::PackageId;
66
use crate::core::{PackageIdSpec, SourceId};
77
use crate::ops::common_for_install_and_uninstall::*;
8+
use crate::sources::PathSource;
89
use crate::util::errors::CargoResult;
910
use crate::util::paths;
1011
use crate::util::Config;
@@ -84,10 +85,13 @@ pub fn uninstall_one(
8485
fn uninstall_cwd(root: &Filesystem, bins: &[String], config: &Config) -> CargoResult<()> {
8586
let tracker = InstallTracker::load(config, root)?;
8687
let source_id = SourceId::for_path(config.cwd())?;
87-
let src = path_source(source_id, config)?;
88-
let pkg = select_pkg(src, None, None, config, true, &mut |path| {
89-
path.read_packages()
90-
})?;
88+
let mut src = path_source(source_id, config)?;
89+
let pkg = select_pkg(
90+
&mut src,
91+
None,
92+
|path: &mut PathSource<'_>| path.read_packages(),
93+
config,
94+
)?;
9195
let pkgid = pkg.package_id();
9296
uninstall_pkgid(root, tracker, pkgid, bins, config)
9397
}

‎src/cargo/ops/common_for_install_and_uninstall.rs‎

Lines changed: 53 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ use std::io::SeekFrom;
55
use std::path::{Path, PathBuf};
66

77
use anyhow::{bail, format_err};
8-
use semver::VersionReq;
98
use serde::{Deserialize, Serialize};
109

1110
use crate::core::compiler::Freshness;
1211
use crate::core::{Dependency, Package, PackageId, Source, SourceId};
1312
use crate::ops::{self, CompileFilter, CompileOptions};
1413
use crate::sources::PathSource;
1514
use crate::util::errors::{CargoResult, CargoResultExt};
16-
use crate::util::{Config, ToSemver};
15+
use crate::util::Config;
1716
use crate::util::{FileLock, Filesystem};
1817

1918
/// On-disk tracking for which package installed which binary.
@@ -521,16 +520,14 @@ pub fn path_source(source_id: SourceId, config: &Config) -> CargoResult<PathSour
521520
}
522521

523522
/// Gets a Package based on command-line requirements.
524-
pub fn select_pkg<'a, T>(
525-
mut source: T,
526-
name: Option<&str>,
527-
vers: Option<&str>,
523+
pub fn select_dep_pkg<T>(
524+
source: &mut T,
525+
dep: Dependency,
528526
config: &Config,
529527
needs_update: bool,
530-
list_all: &mut dyn FnMut(&mut T) -> CargoResult<Vec<Package>>,
531528
) -> CargoResult<Package>
532529
where
533-
T: Source + 'a,
530+
T: Source,
534531
{
535532
// This operation may involve updating some sources or making a few queries
536533
// which may involve frobbing caches, as a result make sure we synchronize
@@ -541,83 +538,42 @@ where
541538
source.update()?;
542539
}
543540

544-
if let Some(name) = name {
545-
let vers = if let Some(v) = vers {
546-
// If the version begins with character <, >, =, ^, ~ parse it as a
547-
// version range, otherwise parse it as a specific version
548-
let first = v
549-
.chars()
550-
.next()
551-
.ok_or_else(|| format_err!("no version provided for the `--vers` flag"))?;
552-
553-
let is_req = "<>=^~".contains(first) || v.contains('*');
554-
if is_req {
555-
match v.parse::<VersionReq>() {
556-
Ok(v) => Some(v.to_string()),
557-
Err(_) => bail!(
558-
"the `--vers` provided, `{}`, is \
559-
not a valid semver version requirement\n\n\
560-
Please have a look at \
561-
https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \
562-
for the correct format",
563-
v
564-
),
565-
}
566-
} else {
567-
match v.to_semver() {
568-
Ok(v) => Some(format!("={}", v)),
569-
Err(e) => {
570-
let mut msg = format!(
571-
"the `--vers` provided, `{}`, is \
572-
not a valid semver version: {}\n",
573-
v, e
574-
);
575-
576-
// If it is not a valid version but it is a valid version
577-
// requirement, add a note to the warning
578-
if v.parse::<VersionReq>().is_ok() {
579-
msg.push_str(&format!(
580-
"\nif you want to specify semver range, \
581-
add an explicit qualifier, like ^{}",
582-
v
583-
));
584-
}
585-
bail!(msg);
586-
}
587-
}
588-
}
589-
} else {
590-
None
591-
};
592-
let vers = vers.as_deref();
593-
let vers_spec = if vers.is_none() && source.source_id().is_registry() {
594-
// Avoid pre-release versions from crate.io
595-
// unless explicitly asked for
596-
Some("*")
597-
} else {
598-
vers
599-
};
600-
let dep = Dependency::parse_no_deprecated(name, vers_spec, source.source_id())?;
601-
let deps = source.query_vec(&dep)?;
602-
match deps.iter().map(|p| p.package_id()).max() {
603-
Some(pkgid) => {
604-
let pkg = Box::new(&mut source).download_now(pkgid, config)?;
605-
Ok(pkg)
606-
}
607-
None => {
608-
let vers_info = vers
609-
.map(|v| format!(" with version `{}`", v))
610-
.unwrap_or_default();
611-
bail!(
612-
"could not find `{}` in {}{}",
613-
name,
614-
source.source_id(),
615-
vers_info
616-
)
617-
}
541+
let deps = source.query_vec(&dep)?;
542+
match deps.iter().map(|p| p.package_id()).max() {
543+
Some(pkgid) => {
544+
let pkg = Box::new(source).download_now(pkgid, config)?;
545+
Ok(pkg)
618546
}
547+
None => bail!(
548+
"could not find `{}` in {} with version `{}`",
549+
dep.package_name(),
550+
source.source_id(),
551+
dep.version_req(),
552+
),
553+
}
554+
}
555+
556+
pub fn select_pkg<T, F>(
557+
source: &mut T,
558+
dep: Option<Dependency>,
559+
mut list_all: F,
560+
config: &Config,
561+
) -> CargoResult<Package>
562+
where
563+
T: Source,
564+
F: FnMut(&mut T) -> CargoResult<Vec<Package>>,
565+
{
566+
// This operation may involve updating some sources or making a few queries
567+
// which may involve frobbing caches, as a result make sure we synchronize
568+
// with other global Cargos
569+
let _lock = config.acquire_package_cache_lock()?;
570+
571+
source.update()?;
572+
573+
return if let Some(dep) = dep {
574+
select_dep_pkg(source, dep, config, false)
619575
} else {
620-
let candidates = list_all(&mut source)?;
576+
let candidates = list_all(source)?;
621577
let binaries = candidates
622578
.iter()
623579
.filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
@@ -630,23 +586,23 @@ where
630586
Some(p) => p,
631587
None => bail!(
632588
"no packages found with binaries or \
633-
examples"
589+
examples"
634590
),
635591
},
636592
};
637-
return Ok(pkg.clone());
638-
639-
fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
640-
pkgs.sort_unstable_by_key(|a| a.name());
641-
format!(
642-
"multiple packages with {} found: {}",
643-
kind,
644-
pkgs.iter()
645-
.map(|p| p.name().as_str())
646-
.collect::<Vec<_>>()
647-
.join(", ")
648-
)
649-
}
593+
Ok(pkg.clone())
594+
};
595+
596+
fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
597+
pkgs.sort_unstable_by_key(|a| a.name());
598+
format!(
599+
"multiple packages with {} found: {}",
600+
kind,
601+
pkgs.iter()
602+
.map(|p| p.name().as_str())
603+
.collect::<Vec<_>>()
604+
.join(", ")
605+
)
650606
}
651607
}
652608

‎tests/testsuite/install.rs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ fn multiple_pkgs() {
7575
[FINISHED] release [optimized] target(s) in [..]
7676
[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
7777
[INSTALLED] package `bar v0.0.2` (executable `bar[EXE]`)
78-
[ERROR] could not find `baz` in registry `[..]`
78+
[ERROR] could not find `baz` in registry `[..]` with version `*`
7979
[SUMMARY] Successfully installed foo, bar! Failed to install baz (see error(s) above).
8080
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
8181
[ERROR] some crates failed to install
@@ -147,7 +147,7 @@ fn missing() {
147147
.with_stderr(
148148
"\
149149
[UPDATING] [..] index
150-
[ERROR] could not find `bar` in registry `[..]`
150+
[ERROR] could not find `bar` in registry `[..]` with version `*`
151151
",
152152
)
153153
.run();
@@ -175,7 +175,7 @@ fn bad_version() {
175175
.with_stderr(
176176
"\
177177
[UPDATING] [..] index
178-
[ERROR] could not find `foo` in registry `[..]` with version `=0.2.0`
178+
[ERROR] could not find `foo` in registry `[..]` with version `= 0.2.0`
179179
",
180180
)
181181
.run();

‎tests/testsuite/install_upgrade.rs‎

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,21 @@ use cargo_test_support::{
1414
basic_manifest, cargo_process, cross_compile, execs, git, process, project, Execs,
1515
};
1616

17-
// Helper for publishing a package.
18-
fn pkg(name: &str, vers: &str) {
17+
fn pkg_maybe_yanked(name: &str, vers: &str, yanked: bool) {
1918
Package::new(name, vers)
19+
.yanked(yanked)
2020
.file(
2121
"src/main.rs",
2222
r#"fn main() { println!("{}", env!("CARGO_PKG_VERSION")) }"#,
2323
)
2424
.publish();
2525
}
2626

27+
// Helper for publishing a package.
28+
fn pkg(name: &str, vers: &str) {
29+
pkg_maybe_yanked(name, vers, false)
30+
}
31+
2732
fn v1_path() -> PathBuf {
2833
cargo_home().join(".crates.toml")
2934
}
@@ -225,7 +230,6 @@ fn ambiguous_version_no_longer_allowed() {
225230
cargo_process("install foo --version=1.0")
226231
.with_stderr(
227232
"\
228-
[UPDATING] `[..]` index
229233
[ERROR] the `--vers` provided, `1.0`, is not a valid semver version: cannot parse '1.0' as a semver
230234
231235
if you want to specify semver range, add an explicit qualifier, like ^1.0
@@ -746,3 +750,111 @@ fn deletes_orphaned() {
746750
// 0.1.0 should not have any entries.
747751
validate_trackers("foo", "0.1.0", &[]);
748752
}
753+
754+
#[cargo_test]
755+
fn already_installed_exact_does_not_update() {
756+
pkg("foo", "1.0.0");
757+
cargo_process("install foo --version=1.0.0").run();
758+
cargo_process("install foo --version=1.0.0")
759+
.with_stderr(
760+
"\
761+
[IGNORED] package `foo v1.0.0` is already installed[..]
762+
[WARNING] be sure to add [..]
763+
",
764+
)
765+
.run();
766+
767+
cargo_process("install foo --version=>=1.0.0")
768+
.with_stderr(
769+
"\
770+
[UPDATING] `[..]` index
771+
[IGNORED] package `foo v1.0.0` is already installed[..]
772+
[WARNING] be sure to add [..]
773+
",
774+
)
775+
.run();
776+
pkg("foo", "1.0.1");
777+
cargo_process("install foo --version=>=1.0.0")
778+
.with_stderr(
779+
"\
780+
[UPDATING] `[..]` index
781+
[DOWNLOADING] crates ...
782+
[DOWNLOADED] foo v1.0.1 (registry [..])
783+
[INSTALLING] foo v1.0.1
784+
[COMPILING] foo v1.0.1
785+
[FINISHED] release [optimized] target(s) in [..]
786+
[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
787+
[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
788+
[WARNING] be sure to add [..]
789+
",
790+
)
791+
.run();
792+
}
793+
794+
#[cargo_test]
795+
fn already_installed_updates_yank_status_on_upgrade() {
796+
pkg("foo", "1.0.0");
797+
pkg_maybe_yanked("foo", "1.0.1", true);
798+
cargo_process("install foo --version=1.0.0").run();
799+
800+
cargo_process("install foo --version=1.0.1")
801+
.with_status(101)
802+
.with_stderr(
803+
"\
804+
[UPDATING] `[..]` index
805+
[ERROR] could not find `foo` in registry `[..]` with version `= 1.0.1`
806+
",
807+
)
808+
.run();
809+
810+
pkg_maybe_yanked("foo", "1.0.1", false);
811+
812+
pkg("foo", "1.0.1");
813+
cargo_process("install foo --version=1.0.1")
814+
.with_stderr(
815+
"\
816+
[UPDATING] `[..]` index
817+
[DOWNLOADING] crates ...
818+
[DOWNLOADED] foo v1.0.1 (registry [..])
819+
[INSTALLING] foo v1.0.1
820+
[COMPILING] foo v1.0.1
821+
[FINISHED] release [optimized] target(s) in [..]
822+
[REPLACING] [CWD]/home/.cargo/bin/foo[EXE]
823+
[REPLACED] package `foo v1.0.0` with `foo v1.0.1` (executable `foo[EXE]`)
824+
[WARNING] be sure to add [..]
825+
",
826+
)
827+
.run();
828+
}
829+
830+
#[cargo_test]
831+
fn partially_already_installed_does_one_update() {
832+
pkg("foo", "1.0.0");
833+
cargo_process("install foo --version=1.0.0").run();
834+
pkg("bar", "1.0.0");
835+
pkg("baz", "1.0.0");
836+
cargo_process("install foo bar baz --version=1.0.0")
837+
.with_stderr(
838+
"\
839+
[IGNORED] package `foo v1.0.0` is already installed[..]
840+
[UPDATING] `[..]` index
841+
[DOWNLOADING] crates ...
842+
[DOWNLOADED] bar v1.0.0 (registry [..])
843+
[INSTALLING] bar v1.0.0
844+
[COMPILING] bar v1.0.0
845+
[FINISHED] release [optimized] target(s) in [..]
846+
[INSTALLING] [CWD]/home/.cargo/bin/bar[EXE]
847+
[INSTALLED] package `bar v1.0.0` (executable `bar[EXE]`)
848+
[DOWNLOADING] crates ...
849+
[DOWNLOADED] baz v1.0.0 (registry [..])
850+
[INSTALLING] baz v1.0.0
851+
[COMPILING] baz v1.0.0
852+
[FINISHED] release [optimized] target(s) in [..]
853+
[INSTALLING] [CWD]/home/.cargo/bin/baz[EXE]
854+
[INSTALLED] package `baz v1.0.0` (executable `baz[EXE]`)
855+
[SUMMARY] Successfully installed foo, bar, baz!
856+
[WARNING] be sure to add [..]
857+
",
858+
)
859+
.run();
860+
}

0 commit comments

Comments
 (0)
Please sign in to comment.