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