]> git.proxmox.com Git - cargo.git/blob - src/cargo/ops/cargo_install.rs
Upgrade to Rust 2018
[cargo.git] / src / cargo / ops / cargo_install.rs
1 use std::collections::btree_map::Entry;
2 use std::collections::{BTreeMap, BTreeSet};
3 use std::io::prelude::*;
4 use std::io::SeekFrom;
5 use std::path::{Path, PathBuf};
6 use std::sync::Arc;
7 use std::{env, fs};
8
9 use semver::{Version, VersionReq};
10 use tempfile::Builder as TempFileBuilder;
11 use toml;
12
13 use crate::core::compiler::{DefaultExecutor, Executor};
14 use crate::core::package::PackageSet;
15 use crate::core::source::SourceMap;
16 use crate::core::{Dependency, Edition, Package, PackageIdSpec, Source, SourceId};
17 use crate::core::{PackageId, Workspace};
18 use crate::ops::{self, CompileFilter};
19 use crate::sources::{GitSource, PathSource, SourceConfigMap};
20 use crate::util::errors::{CargoResult, CargoResultExt};
21 use crate::util::paths;
22 use crate::util::{internal, Config};
23 use crate::util::{FileLock, Filesystem};
24
25 #[derive(Deserialize, Serialize)]
26 #[serde(untagged)]
27 enum CrateListing {
28 V1(CrateListingV1),
29 Empty(Empty),
30 }
31
32 #[derive(Deserialize, Serialize)]
33 #[serde(deny_unknown_fields)]
34 struct Empty {}
35
36 #[derive(Deserialize, Serialize)]
37 struct CrateListingV1 {
38 v1: BTreeMap<PackageId, BTreeSet<String>>,
39 }
40
41 struct Transaction {
42 bins: Vec<PathBuf>,
43 }
44
45 impl Transaction {
46 fn success(mut self) {
47 self.bins.clear();
48 }
49 }
50
51 impl Drop for Transaction {
52 fn drop(&mut self) {
53 for bin in self.bins.iter() {
54 let _ = paths::remove_file(bin);
55 }
56 }
57 }
58
59 pub fn install(
60 root: Option<&str>,
61 krates: Vec<&str>,
62 source_id: SourceId,
63 from_cwd: bool,
64 vers: Option<&str>,
65 opts: &ops::CompileOptions,
66 force: bool,
67 ) -> CargoResult<()> {
68 let root = resolve_root(root, opts.config)?;
69 let map = SourceConfigMap::new(opts.config)?;
70
71 let (installed_anything, scheduled_error) = if krates.len() <= 1 {
72 install_one(
73 &root,
74 &map,
75 krates.into_iter().next(),
76 source_id,
77 from_cwd,
78 vers,
79 opts,
80 force,
81 true,
82 )?;
83 (true, false)
84 } else {
85 let mut succeeded = vec![];
86 let mut failed = vec![];
87 let mut first = true;
88 for krate in krates {
89 let root = root.clone();
90 let map = map.clone();
91 match install_one(
92 &root,
93 &map,
94 Some(krate),
95 source_id,
96 from_cwd,
97 vers,
98 opts,
99 force,
100 first,
101 ) {
102 Ok(()) => succeeded.push(krate),
103 Err(e) => {
104 crate::handle_error(&e, &mut opts.config.shell());
105 failed.push(krate)
106 }
107 }
108 first = false;
109 }
110
111 let mut summary = vec![];
112 if !succeeded.is_empty() {
113 summary.push(format!("Successfully installed {}!", succeeded.join(", ")));
114 }
115 if !failed.is_empty() {
116 summary.push(format!(
117 "Failed to install {} (see error(s) above).",
118 failed.join(", ")
119 ));
120 }
121 if !succeeded.is_empty() || !failed.is_empty() {
122 opts.config.shell().status("Summary", summary.join(" "))?;
123 }
124
125 (!succeeded.is_empty(), !failed.is_empty())
126 };
127
128 if installed_anything {
129 // Print a warning that if this directory isn't in PATH that they won't be
130 // able to run these commands.
131 let dst = metadata(opts.config, &root)?.parent().join("bin");
132 let path = env::var_os("PATH").unwrap_or_default();
133 for path in env::split_paths(&path) {
134 if path == dst {
135 return Ok(());
136 }
137 }
138
139 opts.config.shell().warn(&format!(
140 "be sure to add `{}` to your PATH to be \
141 able to run the installed binaries",
142 dst.display()
143 ))?;
144 }
145
146 if scheduled_error {
147 bail!("some crates failed to install");
148 }
149
150 Ok(())
151 }
152
153 fn install_one(
154 root: &Filesystem,
155 map: &SourceConfigMap,
156 krate: Option<&str>,
157 source_id: SourceId,
158 from_cwd: bool,
159 vers: Option<&str>,
160 opts: &ops::CompileOptions,
161 force: bool,
162 is_first_install: bool,
163 ) -> CargoResult<()> {
164 let config = opts.config;
165
166 let (pkg, source) = if source_id.is_git() {
167 select_pkg(
168 GitSource::new(source_id, config)?,
169 krate,
170 vers,
171 config,
172 true,
173 &mut |git| git.read_packages(),
174 )?
175 } else if source_id.is_path() {
176 let mut src = path_source(source_id, config)?;
177 src.update().chain_err(|| {
178 format_err!(
179 "`{}` is not a crate root; specify a crate to \
180 install from crates.io, or use --path or --git to \
181 specify an alternate source",
182 src.path().display()
183 )
184 })?;
185 select_pkg(src, krate, vers, config, false, &mut |path| {
186 path.read_packages()
187 })?
188 } else {
189 select_pkg(
190 map.load(source_id)?,
191 krate,
192 vers,
193 config,
194 is_first_install,
195 &mut |_| {
196 bail!(
197 "must specify a crate to install from \
198 crates.io, or use --path or --git to \
199 specify alternate source"
200 )
201 },
202 )?
203 };
204
205 let mut td_opt = None;
206 let mut needs_cleanup = false;
207 let overidden_target_dir = if source_id.is_path() {
208 None
209 } else if let Some(dir) = config.target_dir()? {
210 Some(dir)
211 } else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() {
212 let p = td.path().to_owned();
213 td_opt = Some(td);
214 Some(Filesystem::new(p))
215 } else {
216 needs_cleanup = true;
217 Some(Filesystem::new(config.cwd().join("target-install")))
218 };
219
220 let ws = match overidden_target_dir {
221 Some(dir) => Workspace::ephemeral(pkg, config, Some(dir), false)?,
222 None => {
223 let mut ws = Workspace::new(pkg.manifest_path(), config)?;
224 ws.set_require_optional_deps(false);
225 ws
226 }
227 };
228 let pkg = ws.current()?;
229
230 if from_cwd {
231 match pkg.manifest().edition() {
232 Edition::Edition2015 => config.shell().warn(
233 "Using `cargo install` to install the binaries for the \
234 package in current working directory is deprecated, \
235 use `cargo install --path .` instead. \
236 Use `cargo build` if you want to simply build the package.",
237 )?,
238 Edition::Edition2018 => bail!(
239 "Using `cargo install` to install the binaries for the \
240 package in current working directory is no longer supported, \
241 use `cargo install --path .` instead. \
242 Use `cargo build` if you want to simply build the package."
243 ),
244 }
245 };
246
247 config.shell().status("Installing", pkg)?;
248
249 // Preflight checks to check up front whether we'll overwrite something.
250 // We have to check this again afterwards, but may as well avoid building
251 // anything if we're gonna throw it away anyway.
252 {
253 let metadata = metadata(config, root)?;
254 let list = read_crate_list(&metadata)?;
255 let dst = metadata.parent().join("bin");
256 check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
257 }
258
259 let exec: Arc<Executor> = Arc::new(DefaultExecutor);
260 let compile = ops::compile_ws(&ws, Some(source), opts, &exec).chain_err(|| {
261 if let Some(td) = td_opt.take() {
262 // preserve the temporary directory, so the user can inspect it
263 td.into_path();
264 }
265
266 format_err!(
267 "failed to compile `{}`, intermediate artifacts can be \
268 found at `{}`",
269 pkg,
270 ws.target_dir().display()
271 )
272 })?;
273 let binaries: Vec<(&str, &Path)> = compile
274 .binaries
275 .iter()
276 .map(|bin| {
277 let name = bin.file_name().unwrap();
278 if let Some(s) = name.to_str() {
279 Ok((s, bin.as_ref()))
280 } else {
281 bail!("Binary `{:?}` name can't be serialized into string", name)
282 }
283 })
284 .collect::<CargoResult<_>>()?;
285 if binaries.is_empty() {
286 bail!(
287 "no binaries are available for install using the selected \
288 features"
289 );
290 }
291
292 let metadata = metadata(config, root)?;
293 let mut list = read_crate_list(&metadata)?;
294 let dst = metadata.parent().join("bin");
295 let duplicates = check_overwrites(&dst, pkg, &opts.filter, &list, force)?;
296
297 fs::create_dir_all(&dst)?;
298
299 // Copy all binaries to a temporary directory under `dst` first, catching
300 // some failure modes (e.g. out of space) before touching the existing
301 // binaries. This directory will get cleaned up via RAII.
302 let staging_dir = TempFileBuilder::new()
303 .prefix("cargo-install")
304 .tempdir_in(&dst)?;
305 for &(bin, src) in binaries.iter() {
306 let dst = staging_dir.path().join(bin);
307 // Try to move if `target_dir` is transient.
308 if !source_id.is_path() && fs::rename(src, &dst).is_ok() {
309 continue;
310 }
311 fs::copy(src, &dst).chain_err(|| {
312 format_err!("failed to copy `{}` to `{}`", src.display(), dst.display())
313 })?;
314 }
315
316 let (to_replace, to_install): (Vec<&str>, Vec<&str>) = binaries
317 .iter()
318 .map(|&(bin, _)| bin)
319 .partition(|&bin| duplicates.contains_key(bin));
320
321 let mut installed = Transaction { bins: Vec::new() };
322
323 // Move the temporary copies into `dst` starting with new binaries.
324 for bin in to_install.iter() {
325 let src = staging_dir.path().join(bin);
326 let dst = dst.join(bin);
327 config.shell().status("Installing", dst.display())?;
328 fs::rename(&src, &dst).chain_err(|| {
329 format_err!("failed to move `{}` to `{}`", src.display(), dst.display())
330 })?;
331 installed.bins.push(dst);
332 }
333
334 // Repeat for binaries which replace existing ones but don't pop the error
335 // up until after updating metadata.
336 let mut replaced_names = Vec::new();
337 let result = {
338 let mut try_install = || -> CargoResult<()> {
339 for &bin in to_replace.iter() {
340 let src = staging_dir.path().join(bin);
341 let dst = dst.join(bin);
342 config.shell().status("Replacing", dst.display())?;
343 fs::rename(&src, &dst).chain_err(|| {
344 format_err!("failed to move `{}` to `{}`", src.display(), dst.display())
345 })?;
346 replaced_names.push(bin);
347 }
348 Ok(())
349 };
350 try_install()
351 };
352
353 // Update records of replaced binaries.
354 for &bin in replaced_names.iter() {
355 if let Some(&Some(ref p)) = duplicates.get(bin) {
356 if let Some(set) = list.v1.get_mut(p) {
357 set.remove(bin);
358 }
359 }
360 // Failsafe to force replacing metadata for git packages
361 // https://github.com/rust-lang/cargo/issues/4582
362 if let Some(set) = list.v1.remove(&pkg.package_id()) {
363 list.v1.insert(pkg.package_id(), set);
364 }
365 list.v1
366 .entry(pkg.package_id())
367 .or_insert_with(BTreeSet::new)
368 .insert(bin.to_string());
369 }
370
371 // Remove empty metadata lines.
372 let pkgs = list
373 .v1
374 .iter()
375 .filter_map(|(&p, set)| if set.is_empty() { Some(p) } else { None })
376 .collect::<Vec<_>>();
377 for p in pkgs.iter() {
378 list.v1.remove(p);
379 }
380
381 // If installation was successful record newly installed binaries.
382 if result.is_ok() {
383 list.v1
384 .entry(pkg.package_id())
385 .or_insert_with(BTreeSet::new)
386 .extend(to_install.iter().map(|s| s.to_string()));
387 }
388
389 let write_result = write_crate_list(&metadata, list);
390 match write_result {
391 // Replacement error (if any) isn't actually caused by write error
392 // but this seems to be the only way to show both.
393 Err(err) => result.chain_err(|| err)?,
394 Ok(_) => result?,
395 }
396
397 // Reaching here means all actions have succeeded. Clean up.
398 installed.success();
399 if needs_cleanup {
400 // Don't bother grabbing a lock as we're going to blow it all away
401 // anyway.
402 let target_dir = ws.target_dir().into_path_unlocked();
403 paths::remove_dir_all(&target_dir)?;
404 }
405
406 Ok(())
407 }
408
409 fn path_source<'a>(source_id: SourceId, config: &'a Config) -> CargoResult<PathSource<'a>> {
410 let path = source_id
411 .url()
412 .to_file_path()
413 .map_err(|()| format_err!("path sources must have a valid path"))?;
414 Ok(PathSource::new(&path, source_id, config))
415 }
416
417 fn select_pkg<'a, T>(
418 mut source: T,
419 name: Option<&str>,
420 vers: Option<&str>,
421 config: &Config,
422 needs_update: bool,
423 list_all: &mut FnMut(&mut T) -> CargoResult<Vec<Package>>,
424 ) -> CargoResult<(Package, Box<Source + 'a>)>
425 where
426 T: Source + 'a,
427 {
428 if needs_update {
429 source.update()?;
430 }
431
432 match name {
433 Some(name) => {
434 let vers = match vers {
435 Some(v) => {
436 // If the version begins with character <, >, =, ^, ~ parse it as a
437 // version range, otherwise parse it as a specific version
438 let first = v
439 .chars()
440 .nth(0)
441 .ok_or_else(|| format_err!("no version provided for the `--vers` flag"))?;
442
443 match first {
444 '<' | '>' | '=' | '^' | '~' => match v.parse::<VersionReq>() {
445 Ok(v) => Some(v.to_string()),
446 Err(_) => bail!(
447 "the `--vers` provided, `{}`, is \
448 not a valid semver version requirement\n\n
449 Please have a look at \
450 http://doc.crates.io/specifying-dependencies.html \
451 for the correct format",
452 v
453 ),
454 },
455 _ => match v.parse::<Version>() {
456 Ok(v) => Some(format!("={}", v)),
457 Err(_) => {
458 let mut msg = format!(
459 "\
460 the `--vers` provided, `{}`, is \
461 not a valid semver version\n\n\
462 historically Cargo treated this \
463 as a semver version requirement \
464 accidentally\nand will continue \
465 to do so, but this behavior \
466 will be removed eventually",
467 v
468 );
469
470 // If it is not a valid version but it is a valid version
471 // requirement, add a note to the warning
472 if v.parse::<VersionReq>().is_ok() {
473 msg.push_str(&format!(
474 "\nif you want to specify semver range, \
475 add an explicit qualifier, like ^{}",
476 v
477 ));
478 }
479 config.shell().warn(&msg)?;
480 Some(v.to_string())
481 }
482 },
483 }
484 }
485 None => None,
486 };
487 let vers = vers.as_ref().map(|s| &**s);
488 let vers_spec = if vers.is_none() && source.source_id().is_registry() {
489 // Avoid pre-release versions from crate.io
490 // unless explicitly asked for
491 Some("*")
492 } else {
493 vers
494 };
495 let dep = Dependency::parse_no_deprecated(name, vers_spec, source.source_id())?;
496 let deps = source.query_vec(&dep)?;
497 let pkgid = match deps.iter().map(|p| p.package_id()).max() {
498 Some(pkgid) => pkgid,
499 None => {
500 let vers_info = vers
501 .map(|v| format!(" with version `{}`", v))
502 .unwrap_or_default();
503 bail!(
504 "could not find `{}` in {}{}",
505 name,
506 source.source_id(),
507 vers_info
508 )
509 }
510 };
511
512 let pkg = {
513 let mut map = SourceMap::new();
514 map.insert(Box::new(&mut source));
515 PackageSet::new(&[pkgid], map, config)?
516 .get_one(pkgid)?
517 .clone()
518 };
519 Ok((pkg, Box::new(source)))
520 }
521 None => {
522 let candidates = list_all(&mut source)?;
523 let binaries = candidates
524 .iter()
525 .filter(|cand| cand.targets().iter().filter(|t| t.is_bin()).count() > 0);
526 let examples = candidates
527 .iter()
528 .filter(|cand| cand.targets().iter().filter(|t| t.is_example()).count() > 0);
529 let pkg = match one(binaries, |v| multi_err("binaries", v))? {
530 Some(p) => p,
531 None => match one(examples, |v| multi_err("examples", v))? {
532 Some(p) => p,
533 None => bail!(
534 "no packages found with binaries or \
535 examples"
536 ),
537 },
538 };
539 return Ok((pkg.clone(), Box::new(source)));
540
541 fn multi_err(kind: &str, mut pkgs: Vec<&Package>) -> String {
542 pkgs.sort_unstable_by_key(|a| a.name());
543 format!(
544 "multiple packages with {} found: {}",
545 kind,
546 pkgs.iter()
547 .map(|p| p.name().as_str())
548 .collect::<Vec<_>>()
549 .join(", ")
550 )
551 }
552 }
553 }
554 }
555
556 fn one<I, F>(mut i: I, f: F) -> CargoResult<Option<I::Item>>
557 where
558 I: Iterator,
559 F: FnOnce(Vec<I::Item>) -> String,
560 {
561 match (i.next(), i.next()) {
562 (Some(i1), Some(i2)) => {
563 let mut v = vec![i1, i2];
564 v.extend(i);
565 Err(format_err!("{}", f(v)))
566 }
567 (Some(i), None) => Ok(Some(i)),
568 (None, _) => Ok(None),
569 }
570 }
571
572 fn check_overwrites(
573 dst: &Path,
574 pkg: &Package,
575 filter: &ops::CompileFilter,
576 prev: &CrateListingV1,
577 force: bool,
578 ) -> CargoResult<BTreeMap<String, Option<PackageId>>> {
579 // If explicit --bin or --example flags were passed then those'll
580 // get checked during cargo_compile, we only care about the "build
581 // everything" case here
582 if !filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
583 bail!("specified package has no binaries")
584 }
585 let duplicates = find_duplicates(dst, pkg, filter, prev);
586 if force || duplicates.is_empty() {
587 return Ok(duplicates);
588 }
589 // Format the error message.
590 let mut msg = String::new();
591 for (bin, p) in duplicates.iter() {
592 msg.push_str(&format!("binary `{}` already exists in destination", bin));
593 if let Some(p) = p.as_ref() {
594 msg.push_str(&format!(" as part of `{}`\n", p));
595 } else {
596 msg.push_str("\n");
597 }
598 }
599 msg.push_str("Add --force to overwrite");
600 Err(format_err!("{}", msg))
601 }
602
603 fn find_duplicates(
604 dst: &Path,
605 pkg: &Package,
606 filter: &ops::CompileFilter,
607 prev: &CrateListingV1,
608 ) -> BTreeMap<String, Option<PackageId>> {
609 let check = |name: String| {
610 // Need to provide type, works around Rust Issue #93349
611 let name = format!("{}{}", name, env::consts::EXE_SUFFIX);
612 if fs::metadata(dst.join(&name)).is_err() {
613 None
614 } else if let Some((&p, _)) = prev.v1.iter().find(|&(_, v)| v.contains(&name)) {
615 Some((name, Some(p)))
616 } else {
617 Some((name, None))
618 }
619 };
620 match *filter {
621 CompileFilter::Default { .. } => pkg
622 .targets()
623 .iter()
624 .filter(|t| t.is_bin())
625 .filter_map(|t| check(t.name().to_string()))
626 .collect(),
627 CompileFilter::Only {
628 ref bins,
629 ref examples,
630 ..
631 } => {
632 let all_bins: Vec<String> = bins.try_collect().unwrap_or_else(|| {
633 pkg.targets()
634 .iter()
635 .filter(|t| t.is_bin())
636 .map(|t| t.name().to_string())
637 .collect()
638 });
639 let all_examples: Vec<String> = examples.try_collect().unwrap_or_else(|| {
640 pkg.targets()
641 .iter()
642 .filter(|t| t.is_bin_example())
643 .map(|t| t.name().to_string())
644 .collect()
645 });
646
647 all_bins
648 .iter()
649 .chain(all_examples.iter())
650 .filter_map(|t| check(t.clone()))
651 .collect::<BTreeMap<String, Option<PackageId>>>()
652 }
653 }
654 }
655
656 fn read_crate_list(file: &FileLock) -> CargoResult<CrateListingV1> {
657 let listing = (|| -> CargoResult<_> {
658 let mut contents = String::new();
659 file.file().read_to_string(&mut contents)?;
660 let listing =
661 toml::from_str(&contents).chain_err(|| internal("invalid TOML found for metadata"))?;
662 match listing {
663 CrateListing::V1(v1) => Ok(v1),
664 CrateListing::Empty(_) => Ok(CrateListingV1 {
665 v1: BTreeMap::new(),
666 }),
667 }
668 })()
669 .chain_err(|| {
670 format_err!(
671 "failed to parse crate metadata at `{}`",
672 file.path().to_string_lossy()
673 )
674 })?;
675 Ok(listing)
676 }
677
678 fn write_crate_list(file: &FileLock, listing: CrateListingV1) -> CargoResult<()> {
679 (|| -> CargoResult<_> {
680 let mut file = file.file();
681 file.seek(SeekFrom::Start(0))?;
682 file.set_len(0)?;
683 let data = toml::to_string(&CrateListing::V1(listing))?;
684 file.write_all(data.as_bytes())?;
685 Ok(())
686 })()
687 .chain_err(|| {
688 format_err!(
689 "failed to write crate metadata at `{}`",
690 file.path().to_string_lossy()
691 )
692 })?;
693 Ok(())
694 }
695
696 pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> {
697 let dst = resolve_root(dst, config)?;
698 let dst = metadata(config, &dst)?;
699 let list = read_crate_list(&dst)?;
700 for (k, v) in list.v1.iter() {
701 println!("{}:", k);
702 for bin in v {
703 println!(" {}", bin);
704 }
705 }
706 Ok(())
707 }
708
709 pub fn uninstall(
710 root: Option<&str>,
711 specs: Vec<&str>,
712 bins: &[String],
713 config: &Config,
714 ) -> CargoResult<()> {
715 if specs.len() > 1 && !bins.is_empty() {
716 bail!("A binary can only be associated with a single installed package, specifying multiple specs with --bin is redundant.");
717 }
718
719 let root = resolve_root(root, config)?;
720 let scheduled_error = if specs.len() == 1 {
721 uninstall_one(&root, specs[0], bins, config)?;
722 false
723 } else if specs.is_empty() {
724 uninstall_cwd(&root, bins, config)?;
725 false
726 } else {
727 let mut succeeded = vec![];
728 let mut failed = vec![];
729 for spec in specs {
730 let root = root.clone();
731 match uninstall_one(&root, spec, bins, config) {
732 Ok(()) => succeeded.push(spec),
733 Err(e) => {
734 crate::handle_error(&e, &mut config.shell());
735 failed.push(spec)
736 }
737 }
738 }
739
740 let mut summary = vec![];
741 if !succeeded.is_empty() {
742 summary.push(format!(
743 "Successfully uninstalled {}!",
744 succeeded.join(", ")
745 ));
746 }
747 if !failed.is_empty() {
748 summary.push(format!(
749 "Failed to uninstall {} (see error(s) above).",
750 failed.join(", ")
751 ));
752 }
753
754 if !succeeded.is_empty() || !failed.is_empty() {
755 config.shell().status("Summary", summary.join(" "))?;
756 }
757
758 !failed.is_empty()
759 };
760
761 if scheduled_error {
762 bail!("some packages failed to uninstall");
763 }
764
765 Ok(())
766 }
767
768 pub fn uninstall_one(
769 root: &Filesystem,
770 spec: &str,
771 bins: &[String],
772 config: &Config,
773 ) -> CargoResult<()> {
774 let crate_metadata = metadata(config, root)?;
775 let metadata = read_crate_list(&crate_metadata)?;
776 let pkgid = PackageIdSpec::query_str(spec, metadata.v1.keys().cloned())?;
777 uninstall_pkgid(&crate_metadata, metadata, pkgid, bins, config)
778 }
779
780 fn uninstall_cwd(root: &Filesystem, bins: &[String], config: &Config) -> CargoResult<()> {
781 let crate_metadata = metadata(config, root)?;
782 let metadata = read_crate_list(&crate_metadata)?;
783 let source_id = SourceId::for_path(config.cwd())?;
784 let src = path_source(source_id, config)?;
785 let (pkg, _source) = select_pkg(src, None, None, config, true, &mut |path| {
786 path.read_packages()
787 })?;
788 let pkgid = pkg.package_id();
789 uninstall_pkgid(&crate_metadata, metadata, pkgid, bins, config)
790 }
791
792 fn uninstall_pkgid(
793 crate_metadata: &FileLock,
794 mut metadata: CrateListingV1,
795 pkgid: PackageId,
796 bins: &[String],
797 config: &Config,
798 ) -> CargoResult<()> {
799 let mut to_remove = Vec::new();
800 {
801 let mut installed = match metadata.v1.entry(pkgid) {
802 Entry::Occupied(e) => e,
803 Entry::Vacant(..) => bail!("package `{}` is not installed", pkgid),
804 };
805 let dst = crate_metadata.parent().join("bin");
806 for bin in installed.get() {
807 let bin = dst.join(bin);
808 if fs::metadata(&bin).is_err() {
809 bail!(
810 "corrupt metadata, `{}` does not exist when it should",
811 bin.display()
812 )
813 }
814 }
815
816 let bins = bins
817 .iter()
818 .map(|s| {
819 if s.ends_with(env::consts::EXE_SUFFIX) {
820 s.to_string()
821 } else {
822 format!("{}{}", s, env::consts::EXE_SUFFIX)
823 }
824 })
825 .collect::<Vec<_>>();
826
827 for bin in bins.iter() {
828 if !installed.get().contains(bin) {
829 bail!("binary `{}` not installed as part of `{}`", bin, pkgid)
830 }
831 }
832
833 if bins.is_empty() {
834 to_remove.extend(installed.get().iter().map(|b| dst.join(b)));
835 installed.get_mut().clear();
836 } else {
837 for bin in bins.iter() {
838 to_remove.push(dst.join(bin));
839 installed.get_mut().remove(bin);
840 }
841 }
842 if installed.get().is_empty() {
843 installed.remove();
844 }
845 }
846 write_crate_list(&crate_metadata, metadata)?;
847 for bin in to_remove {
848 config.shell().status("Removing", bin.display())?;
849 paths::remove_file(bin)?;
850 }
851
852 Ok(())
853 }
854
855 fn metadata(config: &Config, root: &Filesystem) -> CargoResult<FileLock> {
856 root.open_rw(Path::new(".crates.toml"), config, "crate metadata")
857 }
858
859 fn resolve_root(flag: Option<&str>, config: &Config) -> CargoResult<Filesystem> {
860 let config_root = config.get_path("install.root")?;
861 Ok(flag
862 .map(PathBuf::from)
863 .or_else(|| env::var_os("CARGO_INSTALL_ROOT").map(PathBuf::from))
864 .or_else(move || config_root.map(|v| v.val))
865 .map(Filesystem::new)
866 .unwrap_or_else(|| config.home().clone()))
867 }