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