1use std::collections::{BTreeMap, BTreeSet, btree_map};
2use std::env;
3use std::io::SeekFrom;
4use std::io::prelude::*;
5use std::path::{Path, PathBuf};
6use std::rc::Rc;
7
8use anyhow::{Context as _, bail, format_err};
9use cargo_util::paths;
10use cargo_util_schemas::core::PartialVersion;
11use cargo_util_terminal::report::Level;
12use ops::FilterRule;
13use serde::{Deserialize, Serialize};
14
15use crate::core::compiler::{DirtyReason, Freshness};
16use crate::core::{Dependency, FeatureValue, Package, PackageId, SourceId};
17use crate::core::{PackageSet, Target};
18use crate::ops::{self, CompileFilter, CompileOptions};
19use crate::sources::PathSource;
20use crate::sources::source::{QueryKind, Source, SourceMap};
21use crate::util::GlobalContext;
22use crate::util::cache_lock::CacheLockMode;
23use crate::util::context::{ConfigRelativePath, Definition};
24use crate::util::errors::CargoResult;
25use crate::util::{FileLock, Filesystem};
26
27pub struct InstallTracker {
41 v1: CrateListingV1,
42 v2: CrateListingV2,
43 v1_lock: FileLock,
44 v2_lock: FileLock,
45}
46
47#[derive(Default, Deserialize, Serialize)]
49struct CrateListingV2 {
50 installs: BTreeMap<PackageId, InstallInfo>,
52 #[serde(flatten)]
55 other: BTreeMap<String, serde_json::Value>,
56}
57
58#[derive(Debug, Deserialize, Serialize)]
67struct InstallInfo {
68 version_req: Option<String>,
72 bins: BTreeSet<String>,
74 features: BTreeSet<String>,
76 all_features: bool,
77 no_default_features: bool,
78 profile: String,
80 target: Option<String>,
84 rustc: Option<String>,
88 #[serde(flatten)]
90 other: BTreeMap<String, serde_json::Value>,
91}
92
93#[derive(Default, Deserialize, Serialize)]
95pub struct CrateListingV1 {
96 v1: BTreeMap<PackageId, BTreeSet<String>>,
98}
99
100impl InstallTracker {
101 pub fn load(gctx: &GlobalContext, root: &Filesystem) -> CargoResult<InstallTracker> {
103 let v1_lock =
104 root.open_rw_exclusive_create(Path::new(".crates.toml"), gctx, "crate metadata")?;
105 let v2_lock =
106 root.open_rw_exclusive_create(Path::new(".crates2.json"), gctx, "crate metadata")?;
107
108 let v1 = (|| -> CargoResult<_> {
109 let mut contents = String::new();
110 v1_lock.file().read_to_string(&mut contents)?;
111 if contents.is_empty() {
112 Ok(CrateListingV1::default())
113 } else {
114 Ok(toml::from_str(&contents).context("invalid TOML found for metadata")?)
115 }
116 })()
117 .with_context(|| {
118 format!(
119 "failed to parse crate metadata at `{}`",
120 v1_lock.path().to_string_lossy()
121 )
122 })?;
123
124 let v2 = (|| -> CargoResult<_> {
125 let mut contents = String::new();
126 v2_lock.file().read_to_string(&mut contents)?;
127 let mut v2 = if contents.is_empty() {
128 CrateListingV2::default()
129 } else {
130 serde_json::from_str(&contents).context("invalid JSON found for metadata")?
131 };
132 v2.sync_v1(&v1);
133 Ok(v2)
134 })()
135 .with_context(|| {
136 format!(
137 "failed to parse crate metadata at `{}`",
138 v2_lock.path().to_string_lossy()
139 )
140 })?;
141
142 Ok(InstallTracker {
143 v1,
144 v2,
145 v1_lock,
146 v2_lock,
147 })
148 }
149
150 pub fn check_upgrade(
166 &self,
167 dst: &Path,
168 pkg: &Package,
169 force: bool,
170 opts: &CompileOptions,
171 target: &str,
172 _rustc: &str,
173 ) -> CargoResult<(Freshness, BTreeMap<String, Option<PackageId>>)> {
174 let exes = exe_names(pkg, &opts.filter);
175 let duplicates = self.find_duplicates(dst, &exes);
177 if force || duplicates.is_empty() {
178 return Ok((Freshness::Dirty(DirtyReason::Forced), duplicates));
179 }
180 let matching_duplicates: Vec<PackageId> = duplicates
194 .values()
195 .filter_map(|v| match v {
196 Some(dupe_pkg_id) if dupe_pkg_id.name() == pkg.name() => Some(*dupe_pkg_id),
197 _ => None,
198 })
199 .collect();
200
201 if matching_duplicates.len() == duplicates.len() {
204 let source_id = pkg.package_id().source_id();
206 if source_id.is_path() {
207 return Ok((Freshness::Dirty(DirtyReason::Forced), duplicates));
209 }
210 let is_up_to_date = |dupe_pkg_id| {
211 let info = self
212 .v2
213 .installs
214 .get(dupe_pkg_id)
215 .expect("dupes must be in sync");
216 let precise_equal = if source_id.is_git() {
217 dupe_pkg_id.source_id().has_same_precise_as(source_id)
220 } else {
221 true
222 };
223
224 dupe_pkg_id.version() == pkg.version()
225 && dupe_pkg_id.source_id() == source_id
226 && precise_equal
227 && info.is_up_to_date(opts, target, &exes)
228 };
229 if matching_duplicates.iter().all(is_up_to_date) {
230 Ok((Freshness::Fresh, duplicates))
231 } else {
232 Ok((Freshness::Dirty(DirtyReason::Forced), duplicates))
233 }
234 } else {
235 let mut msg = String::new();
237 for (bin, p) in duplicates.iter() {
238 msg.push_str(&format!("binary `{}` already exists in destination", bin));
239 if let Some(p) = p.as_ref() {
240 msg.push_str(&format!(" as part of `{}`\n", p));
241 } else {
242 msg.push('\n');
243 }
244 }
245 msg.push_str("Add --force to overwrite");
246 bail!("{}", msg);
247 }
248 }
249
250 fn find_duplicates(
256 &self,
257 dst: &Path,
258 exes: &BTreeSet<String>,
259 ) -> BTreeMap<String, Option<PackageId>> {
260 exes.iter()
261 .filter_map(|name| {
262 if !dst.join(&name).exists() {
263 None
264 } else {
265 let p = self.v2.package_for_bin(name);
266 Some((name.clone(), p))
267 }
268 })
269 .collect()
270 }
271
272 pub fn mark_installed(
274 &mut self,
275 package: &Package,
276 bins: &BTreeSet<String>,
277 version_req: Option<String>,
278 opts: &CompileOptions,
279 target: &str,
280 rustc: &str,
281 ) {
282 self.v2
283 .mark_installed(package, bins, version_req, opts, target, rustc);
284 self.v1.mark_installed(package, bins);
285 }
286
287 pub fn save(&self) -> CargoResult<()> {
289 self.v1.save(&self.v1_lock).with_context(|| {
290 format!(
291 "failed to write crate metadata at `{}`",
292 self.v1_lock.path().to_string_lossy()
293 )
294 })?;
295
296 self.v2.save(&self.v2_lock).with_context(|| {
297 format!(
298 "failed to write crate metadata at `{}`",
299 self.v2_lock.path().to_string_lossy()
300 )
301 })?;
302 Ok(())
303 }
304
305 pub fn all_installed_bins(&self) -> impl Iterator<Item = (&PackageId, &BTreeSet<String>)> {
309 self.v1.v1.iter()
310 }
311
312 pub fn installed_bins(&self, pkg_id: PackageId) -> Option<&BTreeSet<String>> {
315 self.v1.v1.get(&pkg_id)
316 }
317
318 pub fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
320 self.v1.remove(pkg_id, bins);
321 self.v2.remove(pkg_id, bins);
322 }
323
324 pub fn remove_bin_then_save(
326 &mut self,
327 pkg_id: PackageId,
328 bin: &str,
329 bin_path: &PathBuf,
330 ) -> CargoResult<()> {
331 paths::remove_file(bin_path)?;
332 self.v1.remove_bin(pkg_id, bin);
333 self.v2.remove_bin(pkg_id, bin);
334 self.save()?;
335 Ok(())
336 }
337}
338
339impl CrateListingV1 {
340 fn mark_installed(&mut self, pkg: &Package, bins: &BTreeSet<String>) {
341 for other_bins in self.v1.values_mut() {
343 for bin in bins {
344 other_bins.remove(bin);
345 }
346 }
347 let to_remove = self
349 .v1
350 .iter()
351 .filter_map(|(&p, set)| if set.is_empty() { Some(p) } else { None })
352 .collect::<Vec<_>>();
353 for p in to_remove.iter() {
354 self.v1.remove(p);
355 }
356 self.v1
358 .entry(pkg.package_id())
359 .or_insert_with(BTreeSet::new)
360 .append(&mut bins.clone());
361 }
362
363 fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
364 let mut installed = match self.v1.entry(pkg_id) {
365 btree_map::Entry::Occupied(e) => e,
366 btree_map::Entry::Vacant(..) => panic!("v1 unexpected missing `{}`", pkg_id),
367 };
368
369 for bin in bins {
370 installed.get_mut().remove(bin);
371 }
372 if installed.get().is_empty() {
373 installed.remove();
374 }
375 }
376
377 fn remove_bin(&mut self, pkg_id: PackageId, bin: &str) {
378 let mut installed = match self.v1.entry(pkg_id) {
379 btree_map::Entry::Occupied(e) => e,
380 btree_map::Entry::Vacant(..) => panic!("v1 unexpected missing `{}`", pkg_id),
381 };
382 installed.get_mut().remove(bin);
383 if installed.get().is_empty() {
384 installed.remove();
385 }
386 }
387
388 fn save(&self, lock: &FileLock) -> CargoResult<()> {
389 let mut file = lock.file();
390 file.seek(SeekFrom::Start(0))?;
391 file.set_len(0)?;
392 let data = toml::to_string_pretty(self)?;
393 file.write_all(data.as_bytes())?;
394 Ok(())
395 }
396}
397
398impl CrateListingV2 {
399 fn sync_v1(&mut self, v1: &CrateListingV1) {
405 for (pkg_id, bins) in &v1.v1 {
407 self.installs
408 .entry(*pkg_id)
409 .and_modify(|info| info.bins = bins.clone())
410 .or_insert_with(|| InstallInfo::from_v1(bins));
411 }
412 let to_remove: Vec<_> = self
414 .installs
415 .keys()
416 .filter(|pkg_id| !v1.v1.contains_key(pkg_id))
417 .cloned()
418 .collect();
419 for pkg_id in to_remove {
420 self.installs.remove(&pkg_id);
421 }
422 }
423
424 fn package_for_bin(&self, bin_name: &str) -> Option<PackageId> {
425 self.installs
426 .iter()
427 .find(|(_, info)| info.bins.contains(bin_name))
428 .map(|(pkg_id, _)| *pkg_id)
429 }
430
431 fn mark_installed(
432 &mut self,
433 pkg: &Package,
434 bins: &BTreeSet<String>,
435 version_req: Option<String>,
436 opts: &CompileOptions,
437 target: &str,
438 rustc: &str,
439 ) {
440 for info in &mut self.installs.values_mut() {
442 for bin in bins {
443 info.bins.remove(bin);
444 }
445 }
446 let to_remove = self
448 .installs
449 .iter()
450 .filter_map(|(&p, info)| if info.bins.is_empty() { Some(p) } else { None })
451 .collect::<Vec<_>>();
452 for p in to_remove.iter() {
453 self.installs.remove(p);
454 }
455 if let Some(info) = self.installs.get_mut(&pkg.package_id()) {
457 info.bins.append(&mut bins.clone());
458 info.version_req = version_req;
459 info.features = feature_set(&opts.cli_features.features);
460 info.all_features = opts.cli_features.all_features;
461 info.no_default_features = !opts.cli_features.uses_default_features;
462 info.profile = opts.build_config.requested_profile.to_string();
463 info.target = Some(target.to_string());
464 info.rustc = Some(rustc.to_string());
465 } else {
466 self.installs.insert(
467 pkg.package_id(),
468 InstallInfo {
469 version_req,
470 bins: bins.clone(),
471 features: feature_set(&opts.cli_features.features),
472 all_features: opts.cli_features.all_features,
473 no_default_features: !opts.cli_features.uses_default_features,
474 profile: opts.build_config.requested_profile.to_string(),
475 target: Some(target.to_string()),
476 rustc: Some(rustc.to_string()),
477 other: BTreeMap::new(),
478 },
479 );
480 }
481 }
482
483 fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
484 let mut info_entry = match self.installs.entry(pkg_id) {
485 btree_map::Entry::Occupied(e) => e,
486 btree_map::Entry::Vacant(..) => panic!("v2 unexpected missing `{}`", pkg_id),
487 };
488
489 for bin in bins {
490 info_entry.get_mut().bins.remove(bin);
491 }
492 if info_entry.get().bins.is_empty() {
493 info_entry.remove();
494 }
495 }
496
497 fn remove_bin(&mut self, pkg_id: PackageId, bin: &str) {
498 let mut info_entry = match self.installs.entry(pkg_id) {
499 btree_map::Entry::Occupied(e) => e,
500 btree_map::Entry::Vacant(..) => panic!("v1 unexpected missing `{}`", pkg_id),
501 };
502 info_entry.get_mut().bins.remove(bin);
503 if info_entry.get().bins.is_empty() {
504 info_entry.remove();
505 }
506 }
507
508 fn save(&self, lock: &FileLock) -> CargoResult<()> {
509 let mut file = lock.file();
510 file.seek(SeekFrom::Start(0))?;
511 file.set_len(0)?;
512 let data = serde_json::to_string(self)?;
513 file.write_all(data.as_bytes())?;
514 Ok(())
515 }
516}
517
518impl InstallInfo {
519 fn from_v1(set: &BTreeSet<String>) -> InstallInfo {
520 InstallInfo {
521 version_req: None,
522 bins: set.clone(),
523 features: BTreeSet::new(),
524 all_features: false,
525 no_default_features: false,
526 profile: "release".to_string(),
527 target: None,
528 rustc: None,
529 other: BTreeMap::new(),
530 }
531 }
532
533 fn is_up_to_date(&self, opts: &CompileOptions, target: &str, exes: &BTreeSet<String>) -> bool {
537 self.features == feature_set(&opts.cli_features.features)
538 && self.all_features == opts.cli_features.all_features
539 && self.no_default_features != opts.cli_features.uses_default_features
540 && self.profile.as_str() == opts.build_config.requested_profile.as_str()
541 && (self.target.is_none() || self.target.as_deref() == Some(target))
542 && &self.bins == exes
543 }
544}
545
546pub fn resolve_root(flag: Option<&str>, gctx: &GlobalContext) -> CargoResult<Filesystem> {
548 let config_root = match gctx.get::<Option<ConfigRelativePath>>("install.root")? {
549 Some(p) => {
550 let resolved = p.resolve_program(gctx);
551 if resolved.is_relative() {
552 let definition = p.value().definition.clone();
553 if matches!(definition, Definition::Path(_)) {
554 let suggested = format!("{}/", resolved.display());
555 let notes = [
556 Level::NOTE.message("a future version of Cargo will treat it as relative to the configuration directory"),
557 Level::HELP.message(format!("add a trailing slash (`{}`) to adopt the correct behavior and silence this warning", suggested)),
558 Level::NOTE.message("see more at https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths"),
559 ];
560 gctx.shell().print_report(
561 &[Level::WARNING
562 .secondary_title(format!(
563 "the `install.root` value `{}` defined in {} without a trailing slash is deprecated",
564 resolved.display(),
565 definition
566 ))
567 .elements(notes)],
568 false,
569 )?;
570 }
571 }
572 Some(resolved)
573 }
574 None => None,
575 };
576
577 Ok(flag
578 .map(PathBuf::from)
579 .or_else(|| gctx.get_env_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
580 .or_else(|| config_root)
581 .map(Filesystem::new)
582 .unwrap_or_else(|| gctx.home().clone()))
583}
584
585pub fn path_source(source_id: SourceId, gctx: &GlobalContext) -> CargoResult<PathSource<'_>> {
587 let path = source_id
588 .url()
589 .to_file_path()
590 .map_err(|()| format_err!("path sources must have a valid path"))?;
591 Ok(PathSource::new(&path, source_id, gctx))
592}
593
594pub fn select_dep_pkg(
596 source: &mut dyn Source,
597 dep: Dependency,
598 gctx: &GlobalContext,
599 needs_update: bool,
600 current_rust_version: Option<&PartialVersion>,
601) -> CargoResult<Package> {
602 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
606
607 if needs_update {
608 source.invalidate_cache();
609 }
610
611 let deps = crate::util::block_on(source.query_vec(&dep, QueryKind::Exact))?;
612 match deps
613 .iter()
614 .map(|s| s.as_summary())
615 .max_by_key(|p| p.package_id())
616 {
617 Some(summary) => {
618 if let (Some(current), Some(msrv)) = (current_rust_version, summary.rust_version()) {
619 if !msrv.is_compatible_with(current) {
620 let name = summary.name();
621 let ver = summary.version();
622 let extra = if dep.source_id().is_registry() {
623 let msrv_dep =
625 Dependency::parse(dep.package_name(), None, dep.source_id())?;
626 let msrv_deps =
627 crate::util::block_on(source.query_vec(&msrv_dep, QueryKind::Exact))?;
628 if let Some(alt) = msrv_deps
629 .iter()
630 .map(|s| s.as_summary())
631 .filter(|summary| {
632 summary
633 .rust_version()
634 .map(|msrv| msrv.is_compatible_with(current))
635 .unwrap_or(true)
636 })
637 .max_by_key(|s| s.package_id())
638 {
639 if let Some(rust_version) = alt.rust_version() {
640 format!(
641 "\n`{name} {}` supports rustc {rust_version}",
642 alt.version()
643 )
644 } else {
645 format!(
646 "\n`{name} {}` has an unspecified minimum rustc version",
647 alt.version()
648 )
649 }
650 } else {
651 String::new()
652 }
653 } else {
654 String::new()
655 };
656 bail!(
657 "\
658cannot install package `{name} {ver}`, it requires rustc {msrv} or newer, while the currently active rustc version is {current}{extra}"
659 )
660 }
661 }
662 let mut sources = SourceMap::new();
664 sources.insert(Box::new(source));
665 let pkg_set = PackageSet::new(&[summary.package_id()], sources, gctx)?;
666 Ok(pkg_set.get_one(summary.package_id())?.clone())
667 }
668 None => {
669 let is_yanked: bool = if dep.version_req().is_exact() {
670 let version: String = dep.version_req().to_string();
671 if let Ok(pkg_id) =
672 PackageId::try_new(dep.package_name(), &version[1..], source.source_id())
673 {
674 source.invalidate_cache();
675 crate::util::block_on(source.is_yanked(pkg_id)).unwrap_or_default()
676 } else {
677 false
678 }
679 } else {
680 false
681 };
682 if is_yanked {
683 bail!(
684 "cannot install package `{}`, it has been yanked from {}",
685 dep.package_name(),
686 source.source_id()
687 )
688 } else {
689 bail!(
690 "could not find `{}` in {} with version `{}`",
691 dep.package_name(),
692 source.source_id(),
693 dep.version_req(),
694 )
695 }
696 }
697 }
698}
699
700pub fn select_pkg<T, F>(
701 source: &mut T,
702 dep: Option<Dependency>,
703 mut list_all: F,
704 gctx: &GlobalContext,
705 current_rust_version: Option<&PartialVersion>,
706) -> CargoResult<Package>
707where
708 T: Source,
709 F: FnMut(&mut T) -> CargoResult<Vec<Package>>,
710{
711 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
715
716 source.invalidate_cache();
717
718 return if let Some(dep) = dep {
719 select_dep_pkg(source, dep, gctx, false, current_rust_version)
720 } else {
721 let candidates = list_all(source)?;
722 let binaries = candidates
723 .iter()
724 .filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
725 let examples = candidates
726 .iter()
727 .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
728 let git_url = source.source_id().url().to_string();
729 let pkg = match one(binaries, |v| multi_err("binaries", &git_url, v))? {
730 Some(p) => p,
731 None => match one(examples, |v| multi_err("examples", &git_url, v))? {
732 Some(p) => p,
733 None => bail!(
734 "no packages found with binaries or \
735 examples"
736 ),
737 },
738 };
739 Ok(pkg.clone())
740 };
741
742 fn multi_err(kind: &str, git_url: &str, mut pkgs: Vec<&Package>) -> String {
743 pkgs.sort_unstable_by_key(|a| a.name());
744 let first_pkg = pkgs[0];
745 format!(
746 "multiple packages with {} found: {}. When installing a git repository, \
747 cargo will always search the entire repo for any Cargo.toml.\n\
748 Please specify a package, e.g. `cargo install --git {} {}`.",
749 kind,
750 pkgs.iter()
751 .map(|p| p.name().as_str())
752 .collect::<Vec<_>>()
753 .join(", "),
754 git_url,
755 first_pkg.name()
756 )
757 }
758}
759
760fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
764where
765 I: Iterator,
766 F: FnOnce(Vec<I::Item>) -> String,
767{
768 match (i.next(), i.next()) {
769 (Some(i1), Some(i2)) => {
770 let mut v = vec![i1, i2];
771 v.extend(i);
772 Err(format_err!("{}", f(v)))
773 }
774 (Some(i), None) => Ok(Some(i)),
775 (None, _) => Ok(None),
776 }
777}
778
779fn feature_set(features: &Rc<BTreeSet<FeatureValue>>) -> BTreeSet<String> {
781 features.iter().map(|s| s.to_string()).collect()
782}
783
784pub fn exe_names(pkg: &Package, filter: &ops::CompileFilter) -> BTreeSet<String> {
786 let to_exe = |name| format!("{}{}", name, env::consts::EXE_SUFFIX);
787 match filter {
788 CompileFilter::Default { .. } => pkg
789 .targets()
790 .iter()
791 .filter(|t| t.is_bin())
792 .map(|t| to_exe(t.name()))
793 .collect(),
794 CompileFilter::Only {
795 all_targets: true, ..
796 } => pkg
797 .targets()
798 .iter()
799 .filter(|target| target.is_executable())
800 .map(|target| to_exe(target.name()))
801 .collect(),
802 CompileFilter::Only { bins, examples, .. } => {
803 let collect = |rule: &_, f: fn(&Target) -> _| match rule {
804 FilterRule::All => pkg
805 .targets()
806 .iter()
807 .filter(|t| f(t))
808 .map(|t| t.name().into())
809 .collect(),
810 FilterRule::Just(targets) => targets.clone(),
811 };
812 let all_bins = collect(bins, Target::is_bin);
813 let all_examples = collect(examples, Target::is_exe_example);
814
815 all_bins
816 .iter()
817 .chain(all_examples.iter())
818 .map(|name| to_exe(name))
819 .collect()
820 }
821 }
822}