]> git.proxmox.com Git - rustc.git/blob - src/tools/cargo/src/cargo/ops/cargo_install.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / src / tools / cargo / src / cargo / ops / cargo_install.rs
1 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
2 use std::path::{Path, PathBuf};
3 use std::sync::Arc;
4 use std::{env, fs};
5
6 use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, UnitOutput};
7 use crate::core::{
8 Dependency, Edition, Package, PackageId, PackageIdSpec, Source, SourceId, Target, Workspace,
9 };
10 use crate::ops::{common_for_install_and_uninstall::*, FilterRule};
11 use crate::ops::{CompileFilter, Packages};
12 use crate::sources::{GitSource, PathSource, SourceConfigMap};
13 use crate::util::errors::CargoResult;
14 use crate::util::{Config, Filesystem, Rustc, ToSemver, VersionReqExt};
15 use crate::{drop_println, ops};
16
17 use anyhow::{bail, format_err, Context as _};
18 use cargo_util::paths;
19 use itertools::Itertools;
20 use semver::VersionReq;
21 use tempfile::Builder as TempFileBuilder;
22
23 struct Transaction {
24 bins: Vec<PathBuf>,
25 }
26
27 impl Transaction {
28 fn success(mut self) {
29 self.bins.clear();
30 }
31 }
32
33 impl Drop for Transaction {
34 fn drop(&mut self) {
35 for bin in self.bins.iter() {
36 let _ = paths::remove_file(bin);
37 }
38 }
39 }
40
41 struct InstallablePackage<'cfg, 'a> {
42 config: &'cfg Config,
43 opts: ops::CompileOptions,
44 root: Filesystem,
45 source_id: SourceId,
46 vers: Option<&'a str>,
47 force: bool,
48 no_track: bool,
49
50 pkg: Package,
51 ws: Workspace<'cfg>,
52 rustc: Rustc,
53 target: String,
54 }
55
56 impl<'cfg, 'a> InstallablePackage<'cfg, 'a> {
57 // Returns pkg to install. None if pkg is already installed
58 pub fn new(
59 config: &'cfg Config,
60 root: Filesystem,
61 map: SourceConfigMap<'_>,
62 krate: Option<&str>,
63 source_id: SourceId,
64 from_cwd: bool,
65 vers: Option<&'a str>,
66 original_opts: &'a ops::CompileOptions,
67 force: bool,
68 no_track: bool,
69 needs_update_if_source_is_index: bool,
70 ) -> CargoResult<Option<InstallablePackage<'cfg, 'a>>> {
71 if let Some(name) = krate {
72 if name == "." {
73 bail!(
74 "To install the binaries for the package in current working \
75 directory use `cargo install --path .`. \n\
76 Use `cargo build` if you want to simply build the package."
77 )
78 }
79 }
80
81 let dst = root.join("bin").into_path_unlocked();
82 let pkg = {
83 let dep = {
84 if let Some(krate) = krate {
85 let vers = if let Some(vers_flag) = vers {
86 Some(parse_semver_flag(vers_flag)?.to_string())
87 } else if source_id.is_registry() {
88 // Avoid pre-release versions from crate.io
89 // unless explicitly asked for
90 Some(String::from("*"))
91 } else {
92 None
93 };
94 Some(Dependency::parse(krate, vers.as_deref(), source_id)?)
95 } else {
96 None
97 }
98 };
99
100 if source_id.is_git() {
101 let mut source = GitSource::new(source_id, config)?;
102 select_pkg(
103 &mut source,
104 dep,
105 |git: &mut GitSource<'_>| git.read_packages(),
106 config,
107 )?
108 } else if source_id.is_path() {
109 let mut src = path_source(source_id, config)?;
110 if !src.path().is_dir() {
111 bail!(
112 "`{}` is not a directory. \
113 --path must point to a directory containing a Cargo.toml file.",
114 src.path().display()
115 )
116 }
117 if !src.path().join("Cargo.toml").exists() {
118 if from_cwd {
119 bail!(
120 "`{}` is not a crate root; specify a crate to \
121 install from crates.io, or use --path or --git to \
122 specify an alternate source",
123 src.path().display()
124 );
125 } else if src.path().join("cargo.toml").exists() {
126 bail!(
127 "`{}` does not contain a Cargo.toml file, but found cargo.toml please try to rename it to Cargo.toml. \
128 --path must point to a directory containing a Cargo.toml file.",
129 src.path().display()
130 )
131 } else {
132 bail!(
133 "`{}` does not contain a Cargo.toml file. \
134 --path must point to a directory containing a Cargo.toml file.",
135 src.path().display()
136 )
137 }
138 }
139 select_pkg(
140 &mut src,
141 dep,
142 |path: &mut PathSource<'_>| path.read_packages(),
143 config,
144 )?
145 } else if let Some(dep) = dep {
146 let mut source = map.load(source_id, &HashSet::new())?;
147 if let Ok(Some(pkg)) = installed_exact_package(
148 dep.clone(),
149 &mut source,
150 config,
151 original_opts,
152 &root,
153 &dst,
154 force,
155 ) {
156 let msg = format!(
157 "package `{}` is already installed, use --force to override",
158 pkg
159 );
160 config.shell().status("Ignored", &msg)?;
161 return Ok(None);
162 }
163 select_dep_pkg(&mut source, dep, config, needs_update_if_source_is_index)?
164 } else {
165 bail!(
166 "must specify a crate to install from \
167 crates.io, or use --path or --git to \
168 specify alternate source"
169 )
170 }
171 };
172
173 let (ws, rustc, target) =
174 make_ws_rustc_target(config, &original_opts, &source_id, pkg.clone())?;
175 // If we're installing in --locked mode and there's no `Cargo.lock` published
176 // ie. the bin was published before https://github.com/rust-lang/cargo/pull/7026
177 if config.locked() && !ws.root().join("Cargo.lock").exists() {
178 config.shell().warn(format!(
179 "no Cargo.lock file published in {}",
180 pkg.to_string()
181 ))?;
182 }
183 let pkg = if source_id.is_git() {
184 // Don't use ws.current() in order to keep the package source as a git source so that
185 // install tracking uses the correct source.
186 pkg
187 } else {
188 ws.current()?.clone()
189 };
190
191 // When we build this package, we want to build the *specified* package only,
192 // and avoid building e.g. workspace default-members instead. Do so by constructing
193 // specialized compile options specific to the identified package.
194 // See test `path_install_workspace_root_despite_default_members`.
195 let mut opts = original_opts.clone();
196 // For cargo install tracking, we retain the source git url in `pkg`, but for the build spec
197 // we need to unconditionally use `ws.current()` to correctly address the path where we
198 // locally cloned that repo.
199 let pkgidspec = PackageIdSpec::from_package_id(ws.current()?.package_id());
200 opts.spec = Packages::Packages(vec![pkgidspec.to_string()]);
201
202 if from_cwd {
203 if pkg.manifest().edition() == Edition::Edition2015 {
204 config.shell().warn(
205 "Using `cargo install` to install the binaries for the \
206 package in current working directory is deprecated, \
207 use `cargo install --path .` instead. \
208 Use `cargo build` if you want to simply build the package.",
209 )?
210 } else {
211 bail!(
212 "Using `cargo install` to install the binaries for the \
213 package in current working directory is no longer supported, \
214 use `cargo install --path .` instead. \
215 Use `cargo build` if you want to simply build the package."
216 )
217 }
218 };
219
220 // For bare `cargo install` (no `--bin` or `--example`), check if there is
221 // *something* to install. Explicit `--bin` or `--example` flags will be
222 // checked at the start of `compile_ws`.
223 if !opts.filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
224 bail!(
225 "there is nothing to install in `{}`, because it has no binaries\n\
226 `cargo install` is only for installing programs, and can't be used with libraries.\n\
227 To use a library crate, add it as a dependency to a Cargo project with `cargo add`.",
228 pkg,
229 );
230 }
231
232 let ip = InstallablePackage {
233 config,
234 opts,
235 root,
236 source_id,
237 vers,
238 force,
239 no_track,
240
241 pkg,
242 ws,
243 rustc,
244 target,
245 };
246
247 // WARNING: no_track does not perform locking, so there is no protection
248 // of concurrent installs.
249 if no_track {
250 // Check for conflicts.
251 ip.no_track_duplicates(&dst)?;
252 } else if is_installed(
253 &ip.pkg, config, &ip.opts, &ip.rustc, &ip.target, &ip.root, &dst, force,
254 )? {
255 let msg = format!(
256 "package `{}` is already installed, use --force to override",
257 ip.pkg
258 );
259 config.shell().status("Ignored", &msg)?;
260 return Ok(None);
261 }
262
263 Ok(Some(ip))
264 }
265
266 fn no_track_duplicates(&self, dst: &Path) -> CargoResult<BTreeMap<String, Option<PackageId>>> {
267 // Helper for --no-track flag to make sure it doesn't overwrite anything.
268 let duplicates: BTreeMap<String, Option<PackageId>> =
269 exe_names(&self.pkg, &self.opts.filter)
270 .into_iter()
271 .filter(|name| dst.join(name).exists())
272 .map(|name| (name, None))
273 .collect();
274 if !self.force && !duplicates.is_empty() {
275 let mut msg: Vec<String> = duplicates
276 .iter()
277 .map(|(name, _)| {
278 format!(
279 "binary `{}` already exists in destination `{}`",
280 name,
281 dst.join(name).to_string_lossy()
282 )
283 })
284 .collect();
285 msg.push("Add --force to overwrite".to_string());
286 bail!("{}", msg.join("\n"));
287 }
288 Ok(duplicates)
289 }
290
291 fn install_one(mut self) -> CargoResult<bool> {
292 self.config.shell().status("Installing", &self.pkg)?;
293
294 let dst = self.root.join("bin").into_path_unlocked();
295
296 let mut td_opt = None;
297 let mut needs_cleanup = false;
298 if !self.source_id.is_path() {
299 let target_dir = if let Some(dir) = self.config.target_dir()? {
300 dir
301 } else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() {
302 let p = td.path().to_owned();
303 td_opt = Some(td);
304 Filesystem::new(p)
305 } else {
306 needs_cleanup = true;
307 Filesystem::new(self.config.cwd().join("target-install"))
308 };
309 self.ws.set_target_dir(target_dir);
310 }
311
312 self.check_yanked_install()?;
313
314 let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
315 let compile = ops::compile_ws(&self.ws, &self.opts, &exec).with_context(|| {
316 if let Some(td) = td_opt.take() {
317 // preserve the temporary directory, so the user can inspect it
318 drop(td.into_path());
319 }
320
321 format!(
322 "failed to compile `{}`, intermediate artifacts can be \
323 found at `{}`.\nTo reuse those artifacts with a future \
324 compilation, set the environment variable \
325 `CARGO_TARGET_DIR` to that path.",
326 self.pkg,
327 self.ws.target_dir().display()
328 )
329 })?;
330 let mut binaries: Vec<(&str, &Path)> = compile
331 .binaries
332 .iter()
333 .map(|UnitOutput { path, .. }| {
334 let name = path.file_name().unwrap();
335 if let Some(s) = name.to_str() {
336 Ok((s, path.as_ref()))
337 } else {
338 bail!("Binary `{:?}` name can't be serialized into string", name)
339 }
340 })
341 .collect::<CargoResult<_>>()?;
342 if binaries.is_empty() {
343 // Cargo already warns the user if they use a target specifier that matches nothing,
344 // but we want to error if the user asked for a _particular_ binary to be installed,
345 // and we didn't end up installing it.
346 //
347 // NOTE: This _should_ be impossible to hit since --bin=does_not_exist will fail on
348 // target selection, and --bin=requires_a without --features=a will fail with "target
349 // .. requires the features ..". But rather than assume that's the case, we define the
350 // behavior for this fallback case as well.
351 if let CompileFilter::Only { bins, examples, .. } = &self.opts.filter {
352 let mut any_specific = false;
353 if let FilterRule::Just(ref v) = bins {
354 if !v.is_empty() {
355 any_specific = true;
356 }
357 }
358 if let FilterRule::Just(ref v) = examples {
359 if !v.is_empty() {
360 any_specific = true;
361 }
362 }
363 if any_specific {
364 bail!("no binaries are available for install using the selected features");
365 }
366 }
367
368 // If there _are_ binaries available, but none were selected given the current set of
369 // features, let the user know.
370 //
371 // Note that we know at this point that _if_ bins or examples is set to `::Just`,
372 // they're `::Just([])`, which is `FilterRule::none()`.
373 let binaries: Vec<_> = self
374 .pkg
375 .targets()
376 .iter()
377 .filter(|t| t.is_executable())
378 .collect();
379 if !binaries.is_empty() {
380 self.config
381 .shell()
382 .warn(make_warning_about_missing_features(&binaries))?;
383 }
384
385 return Ok(false);
386 }
387 // This is primarily to make testing easier.
388 binaries.sort_unstable();
389
390 let (tracker, duplicates) = if self.no_track {
391 (None, self.no_track_duplicates(&dst)?)
392 } else {
393 let tracker = InstallTracker::load(self.config, &self.root)?;
394 let (_freshness, duplicates) = tracker.check_upgrade(
395 &dst,
396 &self.pkg,
397 self.force,
398 &self.opts,
399 &self.target,
400 &self.rustc.verbose_version,
401 )?;
402 (Some(tracker), duplicates)
403 };
404
405 paths::create_dir_all(&dst)?;
406
407 // Copy all binaries to a temporary directory under `dst` first, catching
408 // some failure modes (e.g., out of space) before touching the existing
409 // binaries. This directory will get cleaned up via RAII.
410 let staging_dir = TempFileBuilder::new()
411 .prefix("cargo-install")
412 .tempdir_in(&dst)?;
413 for &(bin, src) in binaries.iter() {
414 let dst = staging_dir.path().join(bin);
415 // Try to move if `target_dir` is transient.
416 if !self.source_id.is_path() && fs::rename(src, &dst).is_ok() {
417 continue;
418 }
419 paths::copy(src, &dst)?;
420 }
421
422 let (to_replace, to_install): (Vec<&str>, Vec<&str>) = binaries
423 .iter()
424 .map(|&(bin, _)| bin)
425 .partition(|&bin| duplicates.contains_key(bin));
426
427 let mut installed = Transaction { bins: Vec::new() };
428 let mut successful_bins = BTreeSet::new();
429
430 // Move the temporary copies into `dst` starting with new binaries.
431 for bin in to_install.iter() {
432 let src = staging_dir.path().join(bin);
433 let dst = dst.join(bin);
434 self.config.shell().status("Installing", dst.display())?;
435 fs::rename(&src, &dst).with_context(|| {
436 format!("failed to move `{}` to `{}`", src.display(), dst.display())
437 })?;
438 installed.bins.push(dst);
439 successful_bins.insert(bin.to_string());
440 }
441
442 // Repeat for binaries which replace existing ones but don't pop the error
443 // up until after updating metadata.
444 let replace_result = {
445 let mut try_install = || -> CargoResult<()> {
446 for &bin in to_replace.iter() {
447 let src = staging_dir.path().join(bin);
448 let dst = dst.join(bin);
449 self.config.shell().status("Replacing", dst.display())?;
450 fs::rename(&src, &dst).with_context(|| {
451 format!("failed to move `{}` to `{}`", src.display(), dst.display())
452 })?;
453 successful_bins.insert(bin.to_string());
454 }
455 Ok(())
456 };
457 try_install()
458 };
459
460 if let Some(mut tracker) = tracker {
461 tracker.mark_installed(
462 &self.pkg,
463 &successful_bins,
464 self.vers.map(|s| s.to_string()),
465 &self.opts,
466 &self.target,
467 &self.rustc.verbose_version,
468 );
469
470 if let Err(e) =
471 remove_orphaned_bins(&self.ws, &mut tracker, &duplicates, &self.pkg, &dst)
472 {
473 // Don't hard error on remove.
474 self.config
475 .shell()
476 .warn(format!("failed to remove orphan: {:?}", e))?;
477 }
478
479 match tracker.save() {
480 Err(err) => replace_result.with_context(|| err)?,
481 Ok(_) => replace_result?,
482 }
483 }
484
485 // Reaching here means all actions have succeeded. Clean up.
486 installed.success();
487 if needs_cleanup {
488 // Don't bother grabbing a lock as we're going to blow it all away
489 // anyway.
490 let target_dir = self.ws.target_dir().into_path_unlocked();
491 paths::remove_dir_all(&target_dir)?;
492 }
493
494 // Helper for creating status messages.
495 fn executables<T: AsRef<str>>(mut names: impl Iterator<Item = T> + Clone) -> String {
496 if names.clone().count() == 1 {
497 format!("(executable `{}`)", names.next().unwrap().as_ref())
498 } else {
499 format!(
500 "(executables {})",
501 names
502 .map(|b| format!("`{}`", b.as_ref()))
503 .collect::<Vec<_>>()
504 .join(", ")
505 )
506 }
507 }
508
509 if duplicates.is_empty() {
510 self.config.shell().status(
511 "Installed",
512 format!(
513 "package `{}` {}",
514 self.pkg,
515 executables(successful_bins.iter())
516 ),
517 )?;
518 Ok(true)
519 } else {
520 if !to_install.is_empty() {
521 self.config.shell().status(
522 "Installed",
523 format!("package `{}` {}", self.pkg, executables(to_install.iter())),
524 )?;
525 }
526 // Invert the duplicate map.
527 let mut pkg_map = BTreeMap::new();
528 for (bin_name, opt_pkg_id) in &duplicates {
529 let key =
530 opt_pkg_id.map_or_else(|| "unknown".to_string(), |pkg_id| pkg_id.to_string());
531 pkg_map.entry(key).or_insert_with(Vec::new).push(bin_name);
532 }
533 for (pkg_descr, bin_names) in &pkg_map {
534 self.config.shell().status(
535 "Replaced",
536 format!(
537 "package `{}` with `{}` {}",
538 pkg_descr,
539 self.pkg,
540 executables(bin_names.iter())
541 ),
542 )?;
543 }
544 Ok(true)
545 }
546 }
547
548 fn check_yanked_install(&self) -> CargoResult<()> {
549 if self.ws.ignore_lock() || !self.ws.root().join("Cargo.lock").exists() {
550 return Ok(());
551 }
552 // It would be best if `source` could be passed in here to avoid a
553 // duplicate "Updating", but since `source` is taken by value, then it
554 // wouldn't be available for `compile_ws`.
555 let (pkg_set, resolve) = ops::resolve_ws(&self.ws)?;
556 ops::check_yanked(
557 self.ws.config(),
558 &pkg_set,
559 &resolve,
560 "consider running without --locked",
561 )
562 }
563 }
564
565 fn make_warning_about_missing_features(binaries: &[&Target]) -> String {
566 let max_targets_listed = 7;
567 let target_features_message = binaries
568 .iter()
569 .take(max_targets_listed)
570 .map(|b| {
571 let name = b.description_named();
572 let features = b
573 .required_features()
574 .unwrap_or(&Vec::new())
575 .iter()
576 .map(|f| format!("`{f}`"))
577 .join(", ");
578 format!(" {name} requires the features: {features}")
579 })
580 .join("\n");
581
582 let additional_bins_message = if binaries.len() > max_targets_listed {
583 format!(
584 "\n{} more targets also requires features not enabled. See them in the Cargo.toml file.",
585 binaries.len() - max_targets_listed
586 )
587 } else {
588 "".into()
589 };
590
591 let example_features = binaries[0]
592 .required_features()
593 .map(|f| f.join(" "))
594 .unwrap_or_default();
595
596 format!(
597 "\
598 none of the package's binaries are available for install using the selected features
599 {target_features_message}{additional_bins_message}
600 Consider enabling some of the needed features by passing, e.g., `--features=\"{example_features}\"`"
601 )
602 }
603
604 pub fn install(
605 config: &Config,
606 root: Option<&str>,
607 krates: Vec<(&str, Option<&str>)>,
608 source_id: SourceId,
609 from_cwd: bool,
610 opts: &ops::CompileOptions,
611 force: bool,
612 no_track: bool,
613 ) -> CargoResult<()> {
614 let root = resolve_root(root, config)?;
615 let dst = root.join("bin").into_path_unlocked();
616 let map = SourceConfigMap::new(config)?;
617
618 let (installed_anything, scheduled_error) = if krates.len() <= 1 {
619 let (krate, vers) = krates
620 .into_iter()
621 .next()
622 .map(|(k, v)| (Some(k), v))
623 .unwrap_or((None, None));
624 let installable_pkg = InstallablePackage::new(
625 config, root, map, krate, source_id, from_cwd, vers, opts, force, no_track, true,
626 )?;
627 let mut installed_anything = true;
628 if let Some(installable_pkg) = installable_pkg {
629 installed_anything = installable_pkg.install_one()?;
630 }
631 (installed_anything, false)
632 } else {
633 let mut succeeded = vec![];
634 let mut failed = vec![];
635 // "Tracks whether or not the source (such as a registry or git repo) has been updated.
636 // This is used to avoid updating it multiple times when installing multiple crates.
637 let mut did_update = false;
638
639 let pkgs_to_install: Vec<_> = krates
640 .into_iter()
641 .filter_map(|(krate, vers)| {
642 let root = root.clone();
643 let map = map.clone();
644 match InstallablePackage::new(
645 config,
646 root,
647 map,
648 Some(krate),
649 source_id,
650 from_cwd,
651 vers,
652 opts,
653 force,
654 no_track,
655 !did_update,
656 ) {
657 Ok(Some(installable_pkg)) => {
658 did_update = true;
659 Some((krate, installable_pkg))
660 }
661 Ok(None) => {
662 // Already installed
663 succeeded.push(krate);
664 None
665 }
666 Err(e) => {
667 crate::display_error(&e, &mut config.shell());
668 failed.push(krate);
669 // We assume an update was performed if we got an error.
670 did_update = true;
671 None
672 }
673 }
674 })
675 .collect();
676
677 let install_results: Vec<_> = pkgs_to_install
678 .into_iter()
679 .map(|(krate, installable_pkg)| (krate, installable_pkg.install_one()))
680 .collect();
681
682 for (krate, result) in install_results {
683 match result {
684 Ok(installed) => {
685 if installed {
686 succeeded.push(krate);
687 }
688 }
689 Err(e) => {
690 crate::display_error(&e, &mut config.shell());
691 failed.push(krate);
692 }
693 }
694 }
695
696 let mut summary = vec![];
697 if !succeeded.is_empty() {
698 summary.push(format!("Successfully installed {}!", succeeded.join(", ")));
699 }
700 if !failed.is_empty() {
701 summary.push(format!(
702 "Failed to install {} (see error(s) above).",
703 failed.join(", ")
704 ));
705 }
706 if !succeeded.is_empty() || !failed.is_empty() {
707 config.shell().status("Summary", summary.join(" "))?;
708 }
709
710 (!succeeded.is_empty(), !failed.is_empty())
711 };
712
713 if installed_anything {
714 // Print a warning that if this directory isn't in PATH that they won't be
715 // able to run these commands.
716 let path = config.get_env_os("PATH").unwrap_or_default();
717 let dst_in_path = env::split_paths(&path).any(|path| path == dst);
718
719 if !dst_in_path {
720 config.shell().warn(&format!(
721 "be sure to add `{}` to your PATH to be \
722 able to run the installed binaries",
723 dst.display()
724 ))?;
725 }
726 }
727
728 if scheduled_error {
729 bail!("some crates failed to install");
730 }
731
732 Ok(())
733 }
734
735 fn is_installed(
736 pkg: &Package,
737 config: &Config,
738 opts: &ops::CompileOptions,
739 rustc: &Rustc,
740 target: &str,
741 root: &Filesystem,
742 dst: &Path,
743 force: bool,
744 ) -> CargoResult<bool> {
745 let tracker = InstallTracker::load(config, root)?;
746 let (freshness, _duplicates) =
747 tracker.check_upgrade(dst, pkg, force, opts, target, &rustc.verbose_version)?;
748 Ok(freshness.is_fresh())
749 }
750
751 /// Checks if vers can only be satisfied by exactly one version of a package in a registry, and it's
752 /// already installed. If this is the case, we can skip interacting with a registry to check if
753 /// newer versions may be installable, as no newer version can exist.
754 fn installed_exact_package<T>(
755 dep: Dependency,
756 source: &mut T,
757 config: &Config,
758 opts: &ops::CompileOptions,
759 root: &Filesystem,
760 dst: &Path,
761 force: bool,
762 ) -> CargoResult<Option<Package>>
763 where
764 T: Source,
765 {
766 if !dep.version_req().is_exact() {
767 // If the version isn't exact, we may need to update the registry and look for a newer
768 // version - we can't know if the package is installed without doing so.
769 return Ok(None);
770 }
771 // Try getting the package from the registry without updating it, to avoid a potentially
772 // expensive network call in the case that the package is already installed.
773 // If this fails, the caller will possibly do an index update and try again, this is just a
774 // best-effort check to see if we can avoid hitting the network.
775 if let Ok(pkg) = select_dep_pkg(source, dep, config, false) {
776 let (_ws, rustc, target) =
777 make_ws_rustc_target(config, opts, &source.source_id(), pkg.clone())?;
778 if let Ok(true) = is_installed(&pkg, config, opts, &rustc, &target, root, dst, force) {
779 return Ok(Some(pkg));
780 }
781 }
782 Ok(None)
783 }
784
785 fn make_ws_rustc_target<'cfg>(
786 config: &'cfg Config,
787 opts: &ops::CompileOptions,
788 source_id: &SourceId,
789 pkg: Package,
790 ) -> CargoResult<(Workspace<'cfg>, Rustc, String)> {
791 let mut ws = if source_id.is_git() || source_id.is_path() {
792 Workspace::new(pkg.manifest_path(), config)?
793 } else {
794 Workspace::ephemeral(pkg, config, None, false)?
795 };
796 ws.set_ignore_lock(config.lock_update_allowed());
797 ws.set_require_optional_deps(false);
798
799 let rustc = config.load_global_rustc(Some(&ws))?;
800 let target = match &opts.build_config.single_requested_kind()? {
801 CompileKind::Host => rustc.host.as_str().to_owned(),
802 CompileKind::Target(target) => target.short_name().to_owned(),
803 };
804
805 Ok((ws, rustc, target))
806 }
807
808 /// Parses x.y.z as if it were =x.y.z, and gives CLI-specific error messages in the case of invalid
809 /// values.
810 fn parse_semver_flag(v: &str) -> CargoResult<VersionReq> {
811 // If the version begins with character <, >, =, ^, ~ parse it as a
812 // version range, otherwise parse it as a specific version
813 let first = v
814 .chars()
815 .next()
816 .ok_or_else(|| format_err!("no version provided for the `--version` flag"))?;
817
818 let is_req = "<>=^~".contains(first) || v.contains('*');
819 if is_req {
820 match v.parse::<VersionReq>() {
821 Ok(v) => Ok(v),
822 Err(_) => bail!(
823 "the `--version` provided, `{}`, is \
824 not a valid semver version requirement\n\n\
825 Please have a look at \
826 https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html \
827 for the correct format",
828 v
829 ),
830 }
831 } else {
832 match v.to_semver() {
833 Ok(v) => Ok(VersionReq::exact(&v)),
834 Err(e) => {
835 let mut msg = format!(
836 "the `--version` provided, `{}`, is \
837 not a valid semver version: {}\n",
838 v, e
839 );
840
841 // If it is not a valid version but it is a valid version
842 // requirement, add a note to the warning
843 if v.parse::<VersionReq>().is_ok() {
844 msg.push_str(&format!(
845 "\nif you want to specify semver range, \
846 add an explicit qualifier, like ^{}",
847 v
848 ));
849 }
850 bail!(msg);
851 }
852 }
853 }
854 }
855
856 /// Display a list of installed binaries.
857 pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
858 let root = resolve_root(dst, config)?;
859 let tracker = InstallTracker::load(config, &root)?;
860 for (k, v) in tracker.all_installed_bins() {
861 drop_println!(config, "{}:", k);
862 for bin in v {
863 drop_println!(config, " {}", bin);
864 }
865 }
866 Ok(())
867 }
868
869 /// Removes executables that are no longer part of a package that was
870 /// previously installed.
871 fn remove_orphaned_bins(
872 ws: &Workspace<'_>,
873 tracker: &mut InstallTracker,
874 duplicates: &BTreeMap<String, Option<PackageId>>,
875 pkg: &Package,
876 dst: &Path,
877 ) -> CargoResult<()> {
878 let filter = ops::CompileFilter::new_all_targets();
879 let all_self_names = exe_names(pkg, &filter);
880 let mut to_remove: HashMap<PackageId, BTreeSet<String>> = HashMap::new();
881 // For each package that we stomped on.
882 for other_pkg in duplicates.values().flatten() {
883 // Only for packages with the same name.
884 if other_pkg.name() == pkg.name() {
885 // Check what the old package had installed.
886 if let Some(installed) = tracker.installed_bins(*other_pkg) {
887 // If the old install has any names that no longer exist,
888 // add them to the list to remove.
889 for installed_name in installed {
890 if !all_self_names.contains(installed_name.as_str()) {
891 to_remove
892 .entry(*other_pkg)
893 .or_default()
894 .insert(installed_name.clone());
895 }
896 }
897 }
898 }
899 }
900
901 for (old_pkg, bins) in to_remove {
902 tracker.remove(old_pkg, &bins);
903 for bin in bins {
904 let full_path = dst.join(bin);
905 if full_path.exists() {
906 ws.config().shell().status(
907 "Removing",
908 format!(
909 "executable `{}` from previous version {}",
910 full_path.display(),
911 old_pkg
912 ),
913 )?;
914 paths::remove_file(&full_path)
915 .with_context(|| format!("failed to remove {:?}", full_path))?;
916 }
917 }
918 }
919 Ok(())
920 }