1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4use std::{env, fs};
5
6use crate::core::compiler::{CompileKind, DefaultExecutor, Executor, UnitOutput};
7use crate::core::{Dependency, Edition, Package, PackageId, SourceId, Target, Workspace};
8use crate::ops::{CompileFilter, Packages};
9use crate::ops::{FilterRule, common_for_install_and_uninstall::*};
10use crate::sources::source::Source;
11use crate::sources::{GitSource, PathSource, SourceConfigMap};
12use crate::util::context::FeatureUnification;
13use crate::util::errors::CargoResult;
14use crate::util::{Filesystem, GlobalContext, Rustc};
15use crate::{drop_println, ops};
16
17use anyhow::{Context as _, bail};
18use cargo_util::paths;
19use cargo_util_schemas::core::PartialVersion;
20use itertools::Itertools;
21use semver::VersionReq;
22use tempfile::Builder as TempFileBuilder;
23
24struct Transaction {
25 bins: Vec<PathBuf>,
26}
27
28impl Transaction {
29 fn success(mut self) {
30 self.bins.clear();
31 }
32}
33
34impl Drop for Transaction {
35 fn drop(&mut self) {
36 for bin in self.bins.iter() {
37 let _ = paths::remove_file(bin);
38 }
39 }
40}
41
42struct InstallablePackage<'gctx> {
43 gctx: &'gctx GlobalContext,
44 opts: ops::CompileOptions,
45 root: Filesystem,
46 source_id: SourceId,
47 vers: Option<VersionReq>,
48 force: bool,
49 no_track: bool,
50 pkg: Package,
51 ws: Workspace<'gctx>,
52 rustc: Rustc,
53 target: String,
54}
55
56impl<'gctx> InstallablePackage<'gctx> {
57 pub fn new(
59 gctx: &'gctx GlobalContext,
60 root: Filesystem,
61 map: SourceConfigMap<'_>,
62 krate: Option<&str>,
63 source_id: SourceId,
64 from_cwd: bool,
65 vers: Option<&VersionReq>,
66 original_opts: &ops::CompileOptions,
67 force: bool,
68 no_track: bool,
69 needs_update_if_source_is_index: bool,
70 current_rust_version: Option<&PartialVersion>,
71 ) -> CargoResult<Option<Self>> {
72 if let Some(name) = krate {
73 if name == "." {
74 bail!(
75 "to install the binaries for the package in current working \
76 directory use `cargo install --path .`. \n\
77 use `cargo build` if you want to simply build the package."
78 )
79 }
80 }
81
82 let dst = root.join("bin").into_path_unlocked();
83 let pkg = {
84 let dep = {
85 if let Some(krate) = krate {
86 let vers = if let Some(vers) = vers {
87 Some(vers.to_string())
88 } else if source_id.is_registry() {
89 Some(String::from("*"))
92 } else {
93 None
94 };
95 Some(Dependency::parse(krate, vers.as_deref(), source_id)?)
96 } else {
97 None
98 }
99 };
100
101 if source_id.is_git() {
102 let mut source = GitSource::new(source_id, gctx)?;
103 select_pkg(
104 &mut source,
105 dep,
106 |git: &mut GitSource<'_>| git.read_packages(),
107 gctx,
108 current_rust_version,
109 )?
110 } else if source_id.is_path() {
111 let mut src = path_source(source_id, gctx)?;
112 if !src.path().is_dir() {
113 bail!(
114 "`{}` is not a directory. \
115 --path must point to a directory containing a Cargo.toml file.",
116 src.path().display()
117 )
118 }
119 if !src.path().join("Cargo.toml").exists() {
120 if from_cwd {
121 bail!(
122 "`{}` is not a crate root; specify a crate to \
123 install from crates.io, or use --path or --git to \
124 specify an alternate source",
125 src.path().display()
126 );
127 } else if src.path().join("cargo.toml").exists() {
128 bail!(
129 "`{}` does not contain a Cargo.toml file, but found cargo.toml please try to rename it to Cargo.toml. \
130 --path must point to a directory containing a Cargo.toml file.",
131 src.path().display()
132 )
133 } else {
134 bail!(
135 "`{}` does not contain a Cargo.toml file. \
136 --path must point to a directory containing a Cargo.toml file.",
137 src.path().display()
138 )
139 }
140 }
141 select_pkg(
142 &mut src,
143 dep,
144 |path: &mut PathSource<'_>| path.root_package().map(|p| vec![p]),
145 gctx,
146 current_rust_version,
147 )?
148 } else if let Some(dep) = dep {
149 let mut source = map.load(source_id, &HashSet::new())?;
150 if let Ok(Some(pkg)) = installed_exact_package(
151 dep.clone(),
152 &mut source,
153 gctx,
154 original_opts,
155 &root,
156 &dst,
157 force,
158 ) {
159 let msg = format!(
160 "package `{}` is already installed, use --force to override",
161 pkg
162 );
163 gctx.shell().status("Ignored", &msg)?;
164 return Ok(None);
165 }
166 select_dep_pkg(
167 &mut source,
168 dep,
169 gctx,
170 needs_update_if_source_is_index,
171 current_rust_version,
172 )?
173 } else {
174 bail!(
175 "must specify a crate to install from \
176 crates.io, or use --path or --git to \
177 specify alternate source"
178 )
179 }
180 };
181
182 let (ws, rustc, target) =
183 make_ws_rustc_target(gctx, &original_opts, &source_id, pkg.clone())?;
184
185 if !gctx.lock_update_allowed() {
186 if let Some(requested_lockfile_path) = ws.requested_lockfile_path() {
189 if !requested_lockfile_path.is_file() {
190 bail!(
191 "no Cargo.lock file found in the requested path {}",
192 requested_lockfile_path.display()
193 );
194 }
195 } else if !ws.root().join("Cargo.lock").exists() {
198 gctx.shell()
199 .warn(format!("no Cargo.lock file published in {}", pkg))?;
200 }
201 }
202 let pkg = if source_id.is_git() {
203 pkg
206 } else {
207 ws.current()?.clone()
208 };
209
210 let mut opts = original_opts.clone();
215 let pkgidspec = ws.current()?.package_id().to_spec();
219 opts.spec = Packages::Packages(vec![pkgidspec.to_string()]);
220
221 if from_cwd {
222 if pkg.manifest().edition() == Edition::Edition2015 {
223 gctx.shell().warn(
224 "using `cargo install` to install the binaries from the \
225 package in current working directory is deprecated, \
226 use `cargo install --path .` instead. \
227 note: use `cargo build` if you want to simply build the package.",
228 )?
229 } else {
230 bail!(
231 "using `cargo install` to install the binaries from the \
232 package in current working directory is no longer supported, \
233 use `cargo install --path .` instead. \
234 note: use `cargo build` if you want to simply build the package."
235 )
236 }
237 };
238
239 if !opts.filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
243 bail!(
244 "there is nothing to install in `{}`, because it has no binaries\n\
245 `cargo install` is only for installing programs, and can't be used with libraries.\n\
246 To use a library crate, add it as a dependency to a Cargo project with `cargo add`.",
247 pkg,
248 );
249 }
250
251 let ip = InstallablePackage {
252 gctx,
253 opts,
254 root,
255 source_id,
256 vers: vers.cloned(),
257 force,
258 no_track,
259 pkg,
260 ws,
261 rustc,
262 target,
263 };
264
265 if no_track {
268 ip.no_track_duplicates(&dst)?;
270 } else if is_installed(
271 &ip.pkg, gctx, &ip.opts, &ip.rustc, &ip.target, &ip.root, &dst, force,
272 )? {
273 let msg = format!(
274 "package `{}` is already installed, use --force to override",
275 ip.pkg
276 );
277 gctx.shell().status("Ignored", &msg)?;
278 return Ok(None);
279 }
280
281 Ok(Some(ip))
282 }
283
284 fn no_track_duplicates(&self, dst: &Path) -> CargoResult<BTreeMap<String, Option<PackageId>>> {
285 let duplicates: BTreeMap<String, Option<PackageId>> =
287 exe_names(&self.pkg, &self.opts.filter)
288 .into_iter()
289 .filter(|name| dst.join(name).exists())
290 .map(|name| (name, None))
291 .collect();
292 if !self.force && !duplicates.is_empty() {
293 let mut msg: Vec<String> = duplicates
294 .iter()
295 .map(|(name, _)| {
296 format!(
297 "binary `{}` already exists in destination `{}`",
298 name,
299 dst.join(name).to_string_lossy()
300 )
301 })
302 .collect();
303 msg.push("Add --force to overwrite".to_string());
304 bail!("{}", msg.join("\n"));
305 }
306 Ok(duplicates)
307 }
308
309 fn install_one(mut self, dry_run: bool) -> CargoResult<bool> {
310 self.gctx.shell().status("Installing", &self.pkg)?;
311
312 let dst = self.root.join("bin").into_path_unlocked();
315 let cwd = self.gctx.cwd();
316 let dst = if dst.is_absolute() {
317 paths::normalize_path(dst.as_path())
318 } else {
319 paths::normalize_path(&cwd.join(&dst))
320 };
321
322 let mut td_opt = None;
323 let mut needs_cleanup = false;
324 if !self.source_id.is_path() {
325 let target_dir = if let Some(dir) = self.gctx.target_dir()? {
326 dir
327 } else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() {
328 let p = td.path().to_owned();
329 td_opt = Some(td);
330 Filesystem::new(p)
331 } else {
332 needs_cleanup = true;
333 Filesystem::new(self.gctx.cwd().join("target-install"))
334 };
335 self.ws.set_target_dir(target_dir);
336 }
337
338 self.check_yanked_install()?;
339
340 let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor);
341 self.opts.build_config.dry_run = dry_run;
342 let compile = ops::compile_ws(&self.ws, &self.opts, &exec).with_context(|| {
343 if let Some(td) = td_opt.take() {
344 drop(td.keep());
346 }
347
348 format!(
349 "failed to compile `{}`, intermediate artifacts can be \
350 found at `{}`.\nTo reuse those artifacts with a future \
351 compilation, set the environment variable \
352 `CARGO_BUILD_BUILD_DIR` to that path.",
353 self.pkg,
354 self.ws.build_dir().display()
355 )
356 })?;
357 let mut binaries: Vec<(&str, &Path)> = compile
358 .binaries
359 .iter()
360 .map(|UnitOutput { path, .. }| {
361 let name = path.file_name().unwrap();
362 if let Some(s) = name.to_str() {
363 Ok((s, path.as_ref()))
364 } else {
365 bail!("Binary `{:?}` name can't be serialized into string", name)
366 }
367 })
368 .collect::<CargoResult<_>>()?;
369 if binaries.is_empty() {
370 if let CompileFilter::Only { bins, examples, .. } = &self.opts.filter {
379 let mut any_specific = false;
380 if let FilterRule::Just(v) = bins {
381 if !v.is_empty() {
382 any_specific = true;
383 }
384 }
385 if let FilterRule::Just(v) = examples {
386 if !v.is_empty() {
387 any_specific = true;
388 }
389 }
390 if any_specific {
391 bail!("no binaries are available for install using the selected features");
392 }
393 }
394
395 let binaries: Vec<_> = self
401 .pkg
402 .targets()
403 .iter()
404 .filter(|t| t.is_executable())
405 .collect();
406 if !binaries.is_empty() {
407 self.gctx
408 .shell()
409 .warn(make_warning_about_missing_features(&binaries))?;
410 }
411
412 return Ok(false);
413 }
414 binaries.sort_unstable();
416
417 let (tracker, duplicates) = if self.no_track {
418 (None, self.no_track_duplicates(&dst)?)
419 } else {
420 let tracker = InstallTracker::load(self.gctx, &self.root)?;
421 let (_freshness, duplicates) = tracker.check_upgrade(
422 &dst,
423 &self.pkg,
424 self.force,
425 &self.opts,
426 &self.target,
427 &self.rustc.verbose_version,
428 )?;
429 (Some(tracker), duplicates)
430 };
431
432 paths::create_dir_all(&dst)?;
433
434 let staging_dir = TempFileBuilder::new()
438 .prefix("cargo-install")
439 .tempdir_in(&dst)?;
440 if !dry_run {
441 for &(bin, src) in binaries.iter() {
442 let dst = staging_dir.path().join(bin);
443 if !self.source_id.is_path() && fs::rename(src, &dst).is_ok() {
445 continue;
446 }
447 paths::copy(src, &dst)?;
448 }
449 }
450
451 let (to_replace, to_install): (Vec<&str>, Vec<&str>) = binaries
452 .iter()
453 .map(|&(bin, _)| bin)
454 .partition(|&bin| duplicates.contains_key(bin));
455
456 let mut installed = Transaction { bins: Vec::new() };
457 let mut successful_bins = BTreeSet::new();
458
459 for bin in to_install.iter() {
461 let src = staging_dir.path().join(bin);
462 let dst = dst.join(bin);
463 self.gctx.shell().status("Installing", dst.display())?;
464 if !dry_run {
465 fs::rename(&src, &dst).with_context(|| {
466 format!("failed to move `{}` to `{}`", src.display(), dst.display())
467 })?;
468 installed.bins.push(dst);
469 successful_bins.insert(bin.to_string());
470 }
471 }
472
473 let replace_result = {
476 let mut try_install = || -> CargoResult<()> {
477 for &bin in to_replace.iter() {
478 let src = staging_dir.path().join(bin);
479 let dst = dst.join(bin);
480 self.gctx.shell().status("Replacing", dst.display())?;
481 if !dry_run {
482 fs::rename(&src, &dst).with_context(|| {
483 format!("failed to move `{}` to `{}`", src.display(), dst.display())
484 })?;
485 successful_bins.insert(bin.to_string());
486 }
487 }
488 Ok(())
489 };
490 try_install()
491 };
492
493 if let Some(mut tracker) = tracker {
494 tracker.mark_installed(
495 &self.pkg,
496 &successful_bins,
497 self.vers.map(|s| s.to_string()),
498 &self.opts,
499 &self.target,
500 &self.rustc.verbose_version,
501 );
502
503 if let Err(e) = remove_orphaned_bins(
504 &self.ws,
505 &mut tracker,
506 &duplicates,
507 &self.pkg,
508 &dst,
509 dry_run,
510 ) {
511 self.gctx
513 .shell()
514 .warn(format!("failed to remove orphan: {:?}", e))?;
515 }
516
517 match tracker.save() {
518 Err(err) => replace_result.with_context(|| err)?,
519 Ok(_) => replace_result?,
520 }
521 }
522
523 installed.success();
525 if needs_cleanup {
526 let target_dir = self.ws.target_dir().into_path_unlocked();
529 paths::remove_dir_all(&target_dir)?;
530 }
531
532 fn executables<T: AsRef<str>>(mut names: impl Iterator<Item = T> + Clone) -> String {
534 if names.clone().count() == 1 {
535 format!("(executable `{}`)", names.next().unwrap().as_ref())
536 } else {
537 format!(
538 "(executables {})",
539 names
540 .map(|b| format!("`{}`", b.as_ref()))
541 .collect::<Vec<_>>()
542 .join(", ")
543 )
544 }
545 }
546
547 if dry_run {
548 self.gctx.shell().warn("aborting install due to dry run")?;
549 Ok(true)
550 } else if duplicates.is_empty() {
551 self.gctx.shell().status(
552 "Installed",
553 format!(
554 "package `{}` {}",
555 self.pkg,
556 executables(successful_bins.iter())
557 ),
558 )?;
559 Ok(true)
560 } else {
561 if !to_install.is_empty() {
562 self.gctx.shell().status(
563 "Installed",
564 format!("package `{}` {}", self.pkg, executables(to_install.iter())),
565 )?;
566 }
567 let mut pkg_map = BTreeMap::new();
569 for (bin_name, opt_pkg_id) in &duplicates {
570 let key =
571 opt_pkg_id.map_or_else(|| "unknown".to_string(), |pkg_id| pkg_id.to_string());
572 pkg_map.entry(key).or_insert_with(Vec::new).push(bin_name);
573 }
574 for (pkg_descr, bin_names) in &pkg_map {
575 self.gctx.shell().status(
576 "Replaced",
577 format!(
578 "package `{}` with `{}` {}",
579 pkg_descr,
580 self.pkg,
581 executables(bin_names.iter())
582 ),
583 )?;
584 }
585 Ok(true)
586 }
587 }
588
589 fn check_yanked_install(&self) -> CargoResult<()> {
590 if self.ws.ignore_lock() || !self.ws.root().join("Cargo.lock").exists() {
591 return Ok(());
592 }
593 let dry_run = false;
597 let (pkg_set, resolve) = ops::resolve_ws(&self.ws, dry_run)?;
598 ops::check_yanked(
599 self.ws.gctx(),
600 &pkg_set,
601 &resolve,
602 "consider running without --locked",
603 )
604 }
605}
606
607fn make_warning_about_missing_features(binaries: &[&Target]) -> String {
608 let max_targets_listed = 7;
609 let target_features_message = binaries
610 .iter()
611 .take(max_targets_listed)
612 .map(|b| {
613 let name = b.description_named();
614 let features = b
615 .required_features()
616 .unwrap_or(&Vec::new())
617 .iter()
618 .map(|f| format!("`{f}`"))
619 .join(", ");
620 format!(" {name} requires the features: {features}")
621 })
622 .join("\n");
623
624 let additional_bins_message = if binaries.len() > max_targets_listed {
625 format!(
626 "\n{} more targets also requires features not enabled. See them in the Cargo.toml file.",
627 binaries.len() - max_targets_listed
628 )
629 } else {
630 "".into()
631 };
632
633 let example_features = binaries[0]
634 .required_features()
635 .map(|f| f.join(" "))
636 .unwrap_or_default();
637
638 format!(
639 "\
640none of the package's binaries are available for install using the selected features
641{target_features_message}{additional_bins_message}
642Consider enabling some of the needed features by passing, e.g., `--features=\"{example_features}\"`"
643 )
644}
645
646pub fn install(
647 gctx: &GlobalContext,
648 root: Option<&str>,
649 krates: Vec<(String, Option<VersionReq>)>,
650 source_id: SourceId,
651 from_cwd: bool,
652 opts: &ops::CompileOptions,
653 force: bool,
654 no_track: bool,
655 dry_run: bool,
656) -> CargoResult<()> {
657 let root = resolve_root(root, gctx)?;
658 let dst = root.join("bin").into_path_unlocked();
661 let cwd = gctx.cwd();
662 let dst = if dst.is_absolute() {
663 paths::normalize_path(dst.as_path())
664 } else {
665 paths::normalize_path(&cwd.join(&dst))
666 };
667 let map = SourceConfigMap::new(gctx)?;
668
669 let current_rust_version = if opts.honor_rust_version.unwrap_or(true) {
670 let rustc = gctx.load_global_rustc(None)?;
671 Some(rustc.version.clone().into())
672 } else {
673 None
674 };
675
676 let (installed_anything, scheduled_error) = if krates.len() <= 1 {
677 let (krate, vers) = krates
678 .iter()
679 .next()
680 .map(|(k, v)| (Some(k.as_str()), v.as_ref()))
681 .unwrap_or((None, None));
682 let installable_pkg = InstallablePackage::new(
683 gctx,
684 root,
685 map,
686 krate,
687 source_id,
688 from_cwd,
689 vers,
690 opts,
691 force,
692 no_track,
693 true,
694 current_rust_version.as_ref(),
695 )?;
696 let mut installed_anything = true;
697 if let Some(installable_pkg) = installable_pkg {
698 installed_anything = installable_pkg.install_one(dry_run)?;
699 }
700 (installed_anything, false)
701 } else {
702 let mut succeeded = vec![];
703 let mut failed = vec![];
704 let mut did_update = false;
707
708 let pkgs_to_install: Vec<_> = krates
709 .iter()
710 .filter_map(|(krate, vers)| {
711 let root = root.clone();
712 let map = map.clone();
713 match InstallablePackage::new(
714 gctx,
715 root,
716 map,
717 Some(krate.as_str()),
718 source_id,
719 from_cwd,
720 vers.as_ref(),
721 opts,
722 force,
723 no_track,
724 !did_update,
725 current_rust_version.as_ref(),
726 ) {
727 Ok(Some(installable_pkg)) => {
728 did_update = true;
729 Some((krate, installable_pkg))
730 }
731 Ok(None) => {
732 succeeded.push(krate.as_str());
734 None
735 }
736 Err(e) => {
737 crate::display_error(&e, &mut gctx.shell());
738 failed.push(krate.as_str());
739 did_update = true;
741 None
742 }
743 }
744 })
745 .collect();
746
747 let install_results: Vec<_> = pkgs_to_install
748 .into_iter()
749 .map(|(krate, installable_pkg)| (krate, installable_pkg.install_one(dry_run)))
750 .collect();
751
752 for (krate, result) in install_results {
753 match result {
754 Ok(installed) => {
755 if installed {
756 succeeded.push(krate);
757 }
758 }
759 Err(e) => {
760 crate::display_error(&e, &mut gctx.shell());
761 failed.push(krate);
762 }
763 }
764 }
765
766 let mut summary = vec![];
767 if !succeeded.is_empty() {
768 summary.push(format!("Successfully installed {}!", succeeded.join(", ")));
769 }
770 if !failed.is_empty() {
771 summary.push(format!(
772 "Failed to install {} (see error(s) above).",
773 failed.join(", ")
774 ));
775 }
776 if !succeeded.is_empty() || !failed.is_empty() {
777 gctx.shell().status("Summary", summary.join(" "))?;
778 }
779
780 (!succeeded.is_empty(), !failed.is_empty())
781 };
782
783 if installed_anything {
784 let path = gctx.get_env_os("PATH").unwrap_or_default();
787 let dst_in_path = env::split_paths(&path).any(|path| path == dst);
788
789 if !dst_in_path {
790 gctx.shell().warn(&format!(
791 "be sure to add `{}` to your PATH to be \
792 able to run the installed binaries",
793 dst.display()
794 ))?;
795 }
796 }
797
798 if scheduled_error {
799 bail!("some crates failed to install");
800 }
801
802 Ok(())
803}
804
805fn is_installed(
806 pkg: &Package,
807 gctx: &GlobalContext,
808 opts: &ops::CompileOptions,
809 rustc: &Rustc,
810 target: &str,
811 root: &Filesystem,
812 dst: &Path,
813 force: bool,
814) -> CargoResult<bool> {
815 let tracker = InstallTracker::load(gctx, root)?;
816 let (freshness, _duplicates) =
817 tracker.check_upgrade(dst, pkg, force, opts, target, &rustc.verbose_version)?;
818 Ok(freshness.is_fresh())
819}
820
821fn installed_exact_package<T>(
825 dep: Dependency,
826 source: &mut T,
827 gctx: &GlobalContext,
828 opts: &ops::CompileOptions,
829 root: &Filesystem,
830 dst: &Path,
831 force: bool,
832) -> CargoResult<Option<Package>>
833where
834 T: Source,
835{
836 if !dep.version_req().is_exact() {
837 return Ok(None);
840 }
841 if let Ok(pkg) = select_dep_pkg(source, dep, gctx, false, None) {
846 let (_ws, rustc, target) =
847 make_ws_rustc_target(gctx, opts, &source.source_id(), pkg.clone())?;
848 if let Ok(true) = is_installed(&pkg, gctx, opts, &rustc, &target, root, dst, force) {
849 return Ok(Some(pkg));
850 }
851 }
852 Ok(None)
853}
854
855fn make_ws_rustc_target<'gctx>(
856 gctx: &'gctx GlobalContext,
857 opts: &ops::CompileOptions,
858 source_id: &SourceId,
859 pkg: Package,
860) -> CargoResult<(Workspace<'gctx>, Rustc, String)> {
861 let mut ws = if source_id.is_git() || source_id.is_path() {
862 Workspace::new(pkg.manifest_path(), gctx)?
863 } else {
864 let mut ws = Workspace::ephemeral(pkg, gctx, None, false)?;
865 ws.set_resolve_honors_rust_version(Some(false));
866 ws
867 };
868 ws.set_resolve_feature_unification(FeatureUnification::Selected);
869 ws.set_ignore_lock(gctx.lock_update_allowed());
870 if ws.requested_lockfile_path().is_some() {
872 ws.set_ignore_lock(false);
873 }
874 ws.set_require_optional_deps(false);
875
876 let rustc = gctx.load_global_rustc(Some(&ws))?;
877 let target = match &opts.build_config.single_requested_kind()? {
878 CompileKind::Host => rustc.host.as_str().to_owned(),
879 CompileKind::Target(target) => target.short_name().to_owned(),
880 };
881
882 Ok((ws, rustc, target))
883}
884
885pub fn install_list(dst: Option<&str>, gctx: &GlobalContext) -> CargoResult<()> {
887 let root = resolve_root(dst, gctx)?;
888 let tracker = InstallTracker::load(gctx, &root)?;
889 for (k, v) in tracker.all_installed_bins() {
890 drop_println!(gctx, "{}:", k);
891 for bin in v {
892 drop_println!(gctx, " {}", bin);
893 }
894 }
895 Ok(())
896}
897
898fn remove_orphaned_bins(
901 ws: &Workspace<'_>,
902 tracker: &mut InstallTracker,
903 duplicates: &BTreeMap<String, Option<PackageId>>,
904 pkg: &Package,
905 dst: &Path,
906 dry_run: bool,
907) -> CargoResult<()> {
908 let filter = ops::CompileFilter::new_all_targets();
909 let all_self_names = exe_names(pkg, &filter);
910 let mut to_remove: HashMap<PackageId, BTreeSet<String>> = HashMap::new();
911 for other_pkg in duplicates.values().flatten() {
913 if other_pkg.name() == pkg.name() {
915 if let Some(installed) = tracker.installed_bins(*other_pkg) {
917 for installed_name in installed {
920 if !all_self_names.contains(installed_name.as_str()) {
921 to_remove
922 .entry(*other_pkg)
923 .or_default()
924 .insert(installed_name.clone());
925 }
926 }
927 }
928 }
929 }
930
931 for (old_pkg, bins) in to_remove {
932 tracker.remove(old_pkg, &bins);
933 for bin in bins {
934 let full_path = dst.join(bin);
935 if full_path.exists() {
936 ws.gctx().shell().status(
937 "Removing",
938 format!(
939 "executable `{}` from previous version {}",
940 full_path.display(),
941 old_pkg
942 ),
943 )?;
944 if !dry_run {
945 paths::remove_file(&full_path)
946 .with_context(|| format!("failed to remove {:?}", full_path))?;
947 }
948 }
949 }
950 }
951 Ok(())
952}