1use std::collections::{btree_map, BTreeMap, BTreeSet};
2use std::env;
3use std::io::prelude::*;
4use std::io::SeekFrom;
5use std::path::{Path, PathBuf};
6use std::rc::Rc;
7use std::task::Poll;
8
9use anyhow::{bail, format_err, Context as _};
10use cargo_util::paths;
11use cargo_util_schemas::core::PartialVersion;
12use ops::FilterRule;
13use serde::{Deserialize, Serialize};
14
15use crate::core::compiler::{DirtyReason, Freshness};
16use crate::core::Target;
17use crate::core::{Dependency, FeatureValue, Package, PackageId, SourceId};
18use crate::ops::{self, CompileFilter, CompileOptions};
19use crate::sources::source::QueryKind;
20use crate::sources::source::Source;
21use crate::sources::PathSource;
22use crate::util::cache_lock::CacheLockMode;
23use crate::util::errors::CargoResult;
24use crate::util::GlobalContext;
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 = gctx.get_path("install.root")?;
549 Ok(flag
550 .map(PathBuf::from)
551 .or_else(|| gctx.get_env_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
552 .or_else(move || config_root.map(|v| v.val))
553 .map(Filesystem::new)
554 .unwrap_or_else(|| gctx.home().clone()))
555}
556
557pub fn path_source(source_id: SourceId, gctx: &GlobalContext) -> CargoResult<PathSource<'_>> {
559 let path = source_id
560 .url()
561 .to_file_path()
562 .map_err(|()| format_err!("path sources must have a valid path"))?;
563 Ok(PathSource::new(&path, source_id, gctx))
564}
565
566pub fn select_dep_pkg<T>(
568 source: &mut T,
569 dep: Dependency,
570 gctx: &GlobalContext,
571 needs_update: bool,
572 current_rust_version: Option<&PartialVersion>,
573) -> CargoResult<Package>
574where
575 T: Source,
576{
577 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
581
582 if needs_update {
583 source.invalidate_cache();
584 }
585
586 let deps = loop {
587 match source.query_vec(&dep, QueryKind::Exact)? {
588 Poll::Ready(deps) => break deps,
589 Poll::Pending => source.block_until_ready()?,
590 }
591 };
592 match deps
593 .iter()
594 .map(|s| s.as_summary())
595 .max_by_key(|p| p.package_id())
596 {
597 Some(summary) => {
598 if let (Some(current), Some(msrv)) = (current_rust_version, summary.rust_version()) {
599 if !msrv.is_compatible_with(current) {
600 let name = summary.name();
601 let ver = summary.version();
602 let extra = if dep.source_id().is_registry() {
603 let msrv_dep =
605 Dependency::parse(dep.package_name(), None, dep.source_id())?;
606 let msrv_deps = loop {
607 match source.query_vec(&msrv_dep, QueryKind::Exact)? {
608 Poll::Ready(deps) => break deps,
609 Poll::Pending => source.block_until_ready()?,
610 }
611 };
612 if let Some(alt) = msrv_deps
613 .iter()
614 .map(|s| s.as_summary())
615 .filter(|summary| {
616 summary
617 .rust_version()
618 .map(|msrv| msrv.is_compatible_with(current))
619 .unwrap_or(true)
620 })
621 .max_by_key(|s| s.package_id())
622 {
623 if let Some(rust_version) = alt.rust_version() {
624 format!(
625 "\n`{name} {}` supports rustc {rust_version}",
626 alt.version()
627 )
628 } else {
629 format!(
630 "\n`{name} {}` has an unspecified minimum rustc version",
631 alt.version()
632 )
633 }
634 } else {
635 String::new()
636 }
637 } else {
638 String::new()
639 };
640 bail!("\
641cannot install package `{name} {ver}`, it requires rustc {msrv} or newer, while the currently active rustc version is {current}{extra}"
642)
643 }
644 }
645 let pkg = Box::new(source).download_now(summary.package_id(), gctx)?;
646 Ok(pkg)
647 }
648 None => {
649 let is_yanked: bool = if dep.version_req().is_exact() {
650 let version: String = dep.version_req().to_string();
651 if let Ok(pkg_id) =
652 PackageId::try_new(dep.package_name(), &version[1..], source.source_id())
653 {
654 source.invalidate_cache();
655 loop {
656 match source.is_yanked(pkg_id) {
657 Poll::Ready(Ok(is_yanked)) => break is_yanked,
658 Poll::Ready(Err(_)) => break false,
659 Poll::Pending => source.block_until_ready()?,
660 }
661 }
662 } else {
663 false
664 }
665 } else {
666 false
667 };
668 if is_yanked {
669 bail!(
670 "cannot install package `{}`, it has been yanked from {}",
671 dep.package_name(),
672 source.source_id()
673 )
674 } else {
675 bail!(
676 "could not find `{}` in {} with version `{}`",
677 dep.package_name(),
678 source.source_id(),
679 dep.version_req(),
680 )
681 }
682 }
683 }
684}
685
686pub fn select_pkg<T, F>(
687 source: &mut T,
688 dep: Option<Dependency>,
689 mut list_all: F,
690 gctx: &GlobalContext,
691 current_rust_version: Option<&PartialVersion>,
692) -> CargoResult<Package>
693where
694 T: Source,
695 F: FnMut(&mut T) -> CargoResult<Vec<Package>>,
696{
697 let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
701
702 source.invalidate_cache();
703
704 return if let Some(dep) = dep {
705 select_dep_pkg(source, dep, gctx, false, current_rust_version)
706 } else {
707 let candidates = list_all(source)?;
708 let binaries = candidates
709 .iter()
710 .filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
711 let examples = candidates
712 .iter()
713 .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
714 let git_url = source.source_id().url().to_string();
715 let pkg = match one(binaries, |v| multi_err("binaries", &git_url, v))? {
716 Some(p) => p,
717 None => match one(examples, |v| multi_err("examples", &git_url, v))? {
718 Some(p) => p,
719 None => bail!(
720 "no packages found with binaries or \
721 examples"
722 ),
723 },
724 };
725 Ok(pkg.clone())
726 };
727
728 fn multi_err(kind: &str, git_url: &str, mut pkgs: Vec<&Package>) -> String {
729 pkgs.sort_unstable_by_key(|a| a.name());
730 let first_pkg = pkgs[0];
731 format!(
732 "multiple packages with {} found: {}. When installing a git repository, \
733 cargo will always search the entire repo for any Cargo.toml.\n\
734 Please specify a package, e.g. `cargo install --git {} {}`.",
735 kind,
736 pkgs.iter()
737 .map(|p| p.name().as_str())
738 .collect::<Vec<_>>()
739 .join(", "),
740 git_url,
741 first_pkg.name()
742 )
743 }
744}
745
746fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
750where
751 I: Iterator,
752 F: FnOnce(Vec<I::Item>) -> String,
753{
754 match (i.next(), i.next()) {
755 (Some(i1), Some(i2)) => {
756 let mut v = vec![i1, i2];
757 v.extend(i);
758 Err(format_err!("{}", f(v)))
759 }
760 (Some(i), None) => Ok(Some(i)),
761 (None, _) => Ok(None),
762 }
763}
764
765fn feature_set(features: &Rc<BTreeSet<FeatureValue>>) -> BTreeSet<String> {
767 features.iter().map(|s| s.to_string()).collect()
768}
769
770pub fn exe_names(pkg: &Package, filter: &ops::CompileFilter) -> BTreeSet<String> {
772 let to_exe = |name| format!("{}{}", name, env::consts::EXE_SUFFIX);
773 match filter {
774 CompileFilter::Default { .. } => pkg
775 .targets()
776 .iter()
777 .filter(|t| t.is_bin())
778 .map(|t| to_exe(t.name()))
779 .collect(),
780 CompileFilter::Only {
781 all_targets: true, ..
782 } => pkg
783 .targets()
784 .iter()
785 .filter(|target| target.is_executable())
786 .map(|target| to_exe(target.name()))
787 .collect(),
788 CompileFilter::Only {
789 ref bins,
790 ref examples,
791 ..
792 } => {
793 let collect = |rule: &_, f: fn(&Target) -> _| match rule {
794 FilterRule::All => pkg
795 .targets()
796 .iter()
797 .filter(|t| f(t))
798 .map(|t| t.name().into())
799 .collect(),
800 FilterRule::Just(targets) => targets.clone(),
801 };
802 let all_bins = collect(bins, Target::is_bin);
803 let all_examples = collect(examples, Target::is_exe_example);
804
805 all_bins
806 .iter()
807 .chain(all_examples.iter())
808 .map(|name| to_exe(name))
809 .collect()
810 }
811 }
812}