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