1 use std
::collections
::btree_map
::Entry
;
2 use std
::collections
::{BTreeMap, BTreeSet}
;
3 use std
::io
::prelude
::*;
5 use std
::path
::{Path, PathBuf}
;
9 use semver
::{Version, VersionReq}
;
10 use tempfile
::Builder
as TempFileBuilder
;
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}
;
25 #[derive(Deserialize, Serialize)]
32 #[derive(Deserialize, Serialize)]
33 #[serde(deny_unknown_fields)]
36 #[derive(Deserialize, Serialize)]
37 struct CrateListingV1
{
38 v1
: BTreeMap
<PackageId
, BTreeSet
<String
>>,
46 fn success(mut self) {
51 impl Drop
for Transaction
{
53 for bin
in self.bins
.iter() {
54 let _
= paths
::remove_file(bin
);
65 opts
: &ops
::CompileOptions
,
67 ) -> CargoResult
<()> {
68 let root
= resolve_root(root
, opts
.config
)?
;
69 let map
= SourceConfigMap
::new(opts
.config
)?
;
71 let (installed_anything
, scheduled_error
) = if krates
.len() <= 1 {
75 krates
.into_iter().next(),
85 let mut succeeded
= vec
![];
86 let mut failed
= vec
![];
89 let root
= root
.clone();
90 let map
= map
.clone();
102 Ok(()) => succeeded
.push(krate
),
104 crate::handle_error(&e
, &mut opts
.config
.shell());
111 let mut summary
= vec
![];
112 if !succeeded
.is_empty() {
113 summary
.push(format
!("Successfully installed {}!", succeeded
.join(", ")));
115 if !failed
.is_empty() {
116 summary
.push(format
!(
117 "Failed to install {} (see error(s) above).",
121 if !succeeded
.is_empty() || !failed
.is_empty() {
122 opts
.config
.shell().status("Summary", summary
.join(" "))?
;
125 (!succeeded
.is_empty(), !failed
.is_empty())
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
) {
139 opts
.config
.shell().warn(&format
!(
140 "be sure to add `{}` to your PATH to be \
141 able to run the installed binaries",
147 bail
!("some crates failed to install");
155 map
: &SourceConfigMap
,
160 opts
: &ops
::CompileOptions
,
162 is_first_install
: bool
,
163 ) -> CargoResult
<()> {
164 let config
= opts
.config
;
166 let (pkg
, source
) = if source_id
.is_git() {
168 GitSource
::new(source_id
, config
)?
,
173 &mut |git
| git
.read_packages(),
175 } else if source_id
.is_path() {
176 let mut src
= path_source(source_id
, config
)?
;
177 src
.update().chain_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",
185 select_pkg(src
, krate
, vers
, config
, false, &mut |path
| {
190 map
.load(source_id
)?
,
197 "must specify a crate to install from \
198 crates.io, or use --path or --git to \
199 specify alternate source"
205 let mut td_opt
= None
;
206 let mut needs_cleanup
= false;
207 let overidden_target_dir
= if source_id
.is_path() {
209 } else if let Some(dir
) = config
.target_dir()?
{
211 } else if let Ok(td
) = TempFileBuilder
::new().prefix("cargo-install").tempdir() {
212 let p
= td
.path().to_owned();
214 Some(Filesystem
::new(p
))
216 needs_cleanup
= true;
217 Some(Filesystem
::new(config
.cwd().join("target-install")))
220 let ws
= match overidden_target_dir
{
221 Some(dir
) => Workspace
::ephemeral(pkg
, config
, Some(dir
), false)?
,
223 let mut ws
= Workspace
::new(pkg
.manifest_path(), config
)?
;
224 ws
.set_require_optional_deps(false);
228 let pkg
= ws
.current()?
;
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.",
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."
247 config
.shell().status("Installing", pkg
)?
;
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.
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
)?
;
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
267 "failed to compile `{}`, intermediate artifacts can be \
270 ws
.target_dir().display()
273 let binaries
: Vec
<(&str, &Path
)> = compile
277 let name
= bin
.file_name().unwrap();
278 if let Some(s
) = name
.to_str() {
279 Ok((s
, bin
.as_ref()))
281 bail
!("Binary `{:?}` name can't be serialized into string", name
)
284 .collect
::<CargoResult
<_
>>()?
;
285 if binaries
.is_empty() {
287 "no binaries are available for install using the selected \
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
)?
;
297 fs
::create_dir_all(&dst
)?
;
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")
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() {
311 fs
::copy(src
, &dst
).chain_err(|| {
312 format_err
!("failed to copy `{}` to `{}`", src
.display(), dst
.display())
316 let (to_replace
, to_install
): (Vec
<&str>, Vec
<&str>) = binaries
318 .map(|&(bin
, _
)| bin
)
319 .partition(|&bin
| duplicates
.contains_key(bin
));
321 let mut installed
= Transaction { bins: Vec::new() }
;
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())
331 installed
.bins
.push(dst
);
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();
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())
346 replaced_names
.push(bin
);
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
) {
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
);
366 .entry(pkg
.package_id())
367 .or_insert_with(BTreeSet
::new
)
368 .insert(bin
.to_string());
371 // Remove empty metadata lines.
375 .filter_map(|(&p
, set
)| if set
.is_empty() { Some(p) }
else { None }
)
376 .collect
::<Vec
<_
>>();
377 for p
in pkgs
.iter() {
381 // If installation was successful record newly installed binaries.
384 .entry(pkg
.package_id())
385 .or_insert_with(BTreeSet
::new
)
386 .extend(to_install
.iter().map(|s
| s
.to_string()));
389 let write_result
= write_crate_list(&metadata
, list
);
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
)?
,
397 // Reaching here means all actions have succeeded. Clean up.
400 // Don't bother grabbing a lock as we're going to blow it all away
402 let target_dir
= ws
.target_dir().into_path_unlocked();
403 paths
::remove_dir_all(&target_dir
)?
;
409 fn path_source
<'a
>(source_id
: SourceId
, config
: &'a Config
) -> CargoResult
<PathSource
<'a
>> {
413 .map_err(|()| format_err
!("path sources must have a valid path"))?
;
414 Ok(PathSource
::new(&path
, source_id
, config
))
417 fn select_pkg
<'a
, T
>(
423 list_all
: &mut FnMut(&mut T
) -> CargoResult
<Vec
<Package
>>,
424 ) -> CargoResult
<(Package
, Box
<Source
+ 'a
>)>
434 let vers
= match vers
{
436 // If the version begins with character <, >, =, ^, ~ parse it as a
437 // version range, otherwise parse it as a specific version
441 .ok_or_else(|| format_err
!("no version provided for the `--vers` flag"))?
;
444 '
<'
| '
>'
| '
='
| '
^' | '
~'
=> match v
.parse
::<VersionReq
>() {
445 Ok(v
) => Some(v
.to_string()),
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",
455 _
=> match v
.parse
::<Version
>() {
456 Ok(v
) => Some(format
!("={}", v
)),
458 let mut msg
= format
!(
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",
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 ^{}",
479 config
.shell().warn(&msg
)?
;
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
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
,
501 .map(|v
| format
!(" with version `{}`", v
))
502 .unwrap_or_default();
504 "could not find `{}` in {}{}",
513 let mut map
= SourceMap
::new();
514 map
.insert(Box
::new(&mut source
));
515 PackageSet
::new(&[pkgid
], map
, config
)?
519 Ok((pkg
, Box
::new(source
)))
522 let candidates
= list_all(&mut source
)?
;
523 let binaries
= candidates
525 .filter(|cand
| cand
.targets().iter().filter(|t
| t
.is_bin()).count() > 0);
526 let examples
= candidates
528 .filter(|cand
| cand
.targets().iter().filter(|t
| t
.is_example()).count() > 0);
529 let pkg
= match one(binaries
, |v
| multi_err("binaries", v
))?
{
531 None
=> match one(examples
, |v
| multi_err("examples", v
))?
{
534 "no packages found with binaries or \
539 return Ok((pkg
.clone(), Box
::new(source
)));
541 fn multi_err(kind
: &str, mut pkgs
: Vec
<&Package
>) -> String
{
542 pkgs
.sort_unstable_by_key(|a
| a
.name());
544 "multiple packages with {} found: {}",
547 .map(|p
| p
.name().as_str())
556 fn one
<I
, F
>(mut i
: I
, f
: F
) -> CargoResult
<Option
<I
::Item
>>
559 F
: FnOnce(Vec
<I
::Item
>) -> String
,
561 match (i
.next(), i
.next()) {
562 (Some(i1
), Some(i2
)) => {
563 let mut v
= vec
![i1
, i2
];
565 Err(format_err
!("{}", f(v
)))
567 (Some(i
), None
) => Ok(Some(i
)),
568 (None
, _
) => Ok(None
),
575 filter
: &ops
::CompileFilter
,
576 prev
: &CrateListingV1
,
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")
585 let duplicates
= find_duplicates(dst
, pkg
, filter
, prev
);
586 if force
|| duplicates
.is_empty() {
587 return Ok(duplicates
);
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
));
599 msg
.push_str("Add --force to overwrite");
600 Err(format_err
!("{}", msg
))
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() {
614 } else if let Some((&p
, _
)) = prev
.v1
.iter().find(|&(_
, v
)| v
.contains(&name
)) {
615 Some((name
, Some(p
)))
621 CompileFilter
::Default { .. }
=> pkg
624 .filter(|t
| t
.is_bin())
625 .filter_map(|t
| check(t
.name().to_string()))
627 CompileFilter
::Only
{
632 let all_bins
: Vec
<String
> = bins
.try_collect().unwrap_or_else(|| {
635 .filter(|t
| t
.is_bin())
636 .map(|t
| t
.name().to_string())
639 let all_examples
: Vec
<String
> = examples
.try_collect().unwrap_or_else(|| {
642 .filter(|t
| t
.is_bin_example())
643 .map(|t
| t
.name().to_string())
649 .chain(all_examples
.iter())
650 .filter_map(|t
| check(t
.clone()))
651 .collect
::<BTreeMap
<String
, Option
<PackageId
>>>()
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
)?
;
661 toml
::from_str(&contents
).chain_err(|| internal("invalid TOML found for metadata"))?
;
663 CrateListing
::V1(v1
) => Ok(v1
),
664 CrateListing
::Empty(_
) => Ok(CrateListingV1
{
671 "failed to parse crate metadata at `{}`",
672 file
.path().to_string_lossy()
678 fn write_crate_list(file
: &FileLock
, listing
: CrateListingV1
) -> CargoResult
<()> {
679 (|| -> CargoResult
<_
> {
680 let mut file
= file
.file();
681 file
.seek(SeekFrom
::Start(0))?
;
683 let data
= toml
::to_string(&CrateListing
::V1(listing
))?
;
684 file
.write_all(data
.as_bytes())?
;
689 "failed to write crate metadata at `{}`",
690 file
.path().to_string_lossy()
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() {
703 println
!(" {}", bin
);
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.");
719 let root
= resolve_root(root
, config
)?
;
720 let scheduled_error
= if specs
.len() == 1 {
721 uninstall_one(&root
, specs
[0], bins
, config
)?
;
723 } else if specs
.is_empty() {
724 uninstall_cwd(&root
, bins
, config
)?
;
727 let mut succeeded
= vec
![];
728 let mut failed
= vec
![];
730 let root
= root
.clone();
731 match uninstall_one(&root
, spec
, bins
, config
) {
732 Ok(()) => succeeded
.push(spec
),
734 crate::handle_error(&e
, &mut config
.shell());
740 let mut summary
= vec
![];
741 if !succeeded
.is_empty() {
742 summary
.push(format
!(
743 "Successfully uninstalled {}!",
747 if !failed
.is_empty() {
748 summary
.push(format
!(
749 "Failed to uninstall {} (see error(s) above).",
754 if !succeeded
.is_empty() || !failed
.is_empty() {
755 config
.shell().status("Summary", summary
.join(" "))?
;
762 bail
!("some packages failed to uninstall");
768 pub fn uninstall_one(
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
)
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
| {
788 let pkgid
= pkg
.package_id();
789 uninstall_pkgid(&crate_metadata
, metadata
, pkgid
, bins
, config
)
793 crate_metadata
: &FileLock
,
794 mut metadata
: CrateListingV1
,
798 ) -> CargoResult
<()> {
799 let mut to_remove
= Vec
::new();
801 let mut installed
= match metadata
.v1
.entry(pkgid
) {
802 Entry
::Occupied(e
) => e
,
803 Entry
::Vacant(..) => bail
!("package `{}` is not installed", pkgid
),
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() {
810 "corrupt metadata, `{}` does not exist when it should",
819 if s
.ends_with(env
::consts
::EXE_SUFFIX
) {
822 format
!("{}{}", s
, env
::consts
::EXE_SUFFIX
)
825 .collect
::<Vec
<_
>>();
827 for bin
in bins
.iter() {
828 if !installed
.get().contains(bin
) {
829 bail
!("binary `{}` not installed as part of `{}`", bin
, pkgid
)
834 to_remove
.extend(installed
.get().iter().map(|b
| dst
.join(b
)));
835 installed
.get_mut().clear();
837 for bin
in bins
.iter() {
838 to_remove
.push(dst
.join(bin
));
839 installed
.get_mut().remove(bin
);
842 if installed
.get().is_empty() {
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
)?
;
855 fn metadata(config
: &Config
, root
: &Filesystem
) -> CargoResult
<FileLock
> {
856 root
.open_rw(Path
::new(".crates.toml"), config
, "crate metadata")
859 fn resolve_root(flag
: Option
<&str>, config
: &Config
) -> CargoResult
<Filesystem
> {
860 let config_root
= config
.get_path("install.root")?
;
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()))