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