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