]>
Commit | Line | Data |
---|---|---|
5a59b809 | 1 | use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; |
bc60f64b | 2 | use std::path::{Path, PathBuf}; |
1ab19a5a | 3 | use std::sync::Arc; |
e5a11190 | 4 | use std::{env, fs}; |
f8308bc5 | 5 | |
e023a667 | 6 | use failure::{bail, format_err}; |
8daf81e1 | 7 | use tempfile::Builder as TempFileBuilder; |
bc60f64b | 8 | |
e9428cba | 9 | use crate::core::compiler::Freshness; |
04ddd4d0 | 10 | use crate::core::compiler::{DefaultExecutor, Executor}; |
e26ef017 | 11 | use crate::core::resolver::ResolveOpts; |
5a59b809 | 12 | use crate::core::{Edition, Package, PackageId, PackageIdSpec, Source, SourceId, Workspace}; |
e023a667 | 13 | use crate::ops; |
6a197fb3 | 14 | use crate::ops::common_for_install_and_uninstall::*; |
f51ac74f | 15 | use crate::sources::{GitSource, SourceConfigMap}; |
04ddd4d0 | 16 | use crate::util::errors::{CargoResult, CargoResultExt}; |
e9428cba | 17 | use crate::util::{paths, Config, Filesystem}; |
bc60f64b AC |
18 | |
19 | struct Transaction { | |
20 | bins: Vec<PathBuf>, | |
21 | } | |
22 | ||
9ea1f2af GK |
23 | impl Transaction { |
24 | fn success(mut self) { | |
25 | self.bins.clear(); | |
26 | } | |
27 | } | |
28 | ||
bc60f64b AC |
29 | impl Drop for Transaction { |
30 | fn drop(&mut self) { | |
31 | for bin in self.bins.iter() { | |
c933673e | 32 | let _ = paths::remove_file(bin); |
bc60f64b AC |
33 | } |
34 | } | |
35 | } | |
36 | ||
1e682848 AC |
37 | pub fn install( |
38 | root: Option<&str>, | |
39 | krates: Vec<&str>, | |
e5a11190 | 40 | source_id: SourceId, |
d6786057 | 41 | from_cwd: bool, |
1e682848 | 42 | vers: Option<&str>, |
b8b7faee | 43 | opts: &ops::CompileOptions<'_>, |
1e682848 | 44 | force: bool, |
e023a667 | 45 | no_track: bool, |
1e682848 | 46 | ) -> CargoResult<()> { |
497c2975 AB |
47 | let root = resolve_root(root, opts.config)?; |
48 | let map = SourceConfigMap::new(opts.config)?; | |
49 | ||
ce2d69d2 | 50 | let (installed_anything, scheduled_error) = if krates.len() <= 1 { |
1e682848 AC |
51 | install_one( |
52 | &root, | |
53 | &map, | |
54 | krates.into_iter().next(), | |
55 | source_id, | |
d6786057 | 56 | from_cwd, |
1e682848 AC |
57 | vers, |
58 | opts, | |
59 | force, | |
e023a667 | 60 | no_track, |
1e682848 AC |
61 | true, |
62 | )?; | |
ce2d69d2 | 63 | (true, false) |
497c2975 | 64 | } else { |
aa201ab7 AB |
65 | let mut succeeded = vec![]; |
66 | let mut failed = vec![]; | |
daf4eabb | 67 | let mut first = true; |
497c2975 AB |
68 | for krate in krates { |
69 | let root = root.clone(); | |
70 | let map = map.clone(); | |
1e682848 AC |
71 | match install_one( |
72 | &root, | |
73 | &map, | |
74 | Some(krate), | |
75 | source_id, | |
d6786057 | 76 | from_cwd, |
1e682848 AC |
77 | vers, |
78 | opts, | |
79 | force, | |
e023a667 | 80 | no_track, |
1e682848 AC |
81 | first, |
82 | ) { | |
aa201ab7 AB |
83 | Ok(()) => succeeded.push(krate), |
84 | Err(e) => { | |
04ddd4d0 | 85 | crate::handle_error(&e, &mut opts.config.shell()); |
aa201ab7 AB |
86 | failed.push(krate) |
87 | } | |
497c2975 | 88 | } |
daf4eabb | 89 | first = false; |
497c2975 AB |
90 | } |
91 | ||
f78fc7c2 | 92 | let mut summary = vec![]; |
aa201ab7 | 93 | if !succeeded.is_empty() { |
f78fc7c2 | 94 | summary.push(format!("Successfully installed {}!", succeeded.join(", "))); |
aa201ab7 AB |
95 | } |
96 | if !failed.is_empty() { | |
1e682848 AC |
97 | summary.push(format!( |
98 | "Failed to install {} (see error(s) above).", | |
99 | failed.join(", ") | |
100 | )); | |
f78fc7c2 AB |
101 | } |
102 | if !succeeded.is_empty() || !failed.is_empty() { | |
37cffbe0 | 103 | opts.config.shell().status("Summary", summary.join(" "))?; |
aa201ab7 | 104 | } |
497c2975 | 105 | |
ce2d69d2 | 106 | (!succeeded.is_empty(), !failed.is_empty()) |
f78fc7c2 AB |
107 | }; |
108 | ||
109 | if installed_anything { | |
110 | // Print a warning that if this directory isn't in PATH that they won't be | |
111 | // able to run these commands. | |
e023a667 | 112 | let dst = root.join("bin").into_path_unlocked(); |
23591fe5 | 113 | let path = env::var_os("PATH").unwrap_or_default(); |
f78fc7c2 AB |
114 | for path in env::split_paths(&path) { |
115 | if path == dst { | |
1e682848 | 116 | return Ok(()); |
f78fc7c2 | 117 | } |
daf4eabb | 118 | } |
daf4eabb | 119 | |
1e682848 AC |
120 | opts.config.shell().warn(&format!( |
121 | "be sure to add `{}` to your PATH to be \ | |
122 | able to run the installed binaries", | |
123 | dst.display() | |
124 | ))?; | |
f78fc7c2 | 125 | } |
daf4eabb | 126 | |
ce2d69d2 | 127 | if scheduled_error { |
e023a667 | 128 | bail!("some crates failed to install"); |
ce2d69d2 AB |
129 | } |
130 | ||
daf4eabb | 131 | Ok(()) |
497c2975 AB |
132 | } |
133 | ||
1e682848 AC |
134 | fn install_one( |
135 | root: &Filesystem, | |
b8b7faee | 136 | map: &SourceConfigMap<'_>, |
1e682848 | 137 | krate: Option<&str>, |
e5a11190 | 138 | source_id: SourceId, |
d6786057 | 139 | from_cwd: bool, |
1e682848 | 140 | vers: Option<&str>, |
b8b7faee | 141 | opts: &ops::CompileOptions<'_>, |
1e682848 | 142 | force: bool, |
e023a667 | 143 | no_track: bool, |
1e682848 AC |
144 | is_first_install: bool, |
145 | ) -> CargoResult<()> { | |
f8308bc5 | 146 | let config = opts.config; |
497c2975 | 147 | |
8a301589 | 148 | let pkg = if source_id.is_git() { |
1e682848 AC |
149 | select_pkg( |
150 | GitSource::new(source_id, config)?, | |
151 | krate, | |
152 | vers, | |
153 | config, | |
494fdabe | 154 | true, |
1e682848 AC |
155 | &mut |git| git.read_packages(), |
156 | )? | |
bc60f64b | 157 | } else if source_id.is_path() { |
8921abd7 | 158 | let mut src = path_source(source_id, config)?; |
3378a5a1 | 159 | if !src.path().is_dir() { |
e023a667 | 160 | bail!( |
3378a5a1 EH |
161 | "`{}` is not a directory. \ |
162 | --path must point to a directory containing a Cargo.toml file.", | |
8921abd7 | 163 | src.path().display() |
1e682848 | 164 | ) |
3378a5a1 EH |
165 | } |
166 | if !src.path().join("Cargo.toml").exists() { | |
167 | if from_cwd { | |
e023a667 | 168 | bail!( |
3378a5a1 EH |
169 | "`{}` is not a crate root; specify a crate to \ |
170 | install from crates.io, or use --path or --git to \ | |
171 | specify an alternate source", | |
172 | src.path().display() | |
173 | ); | |
174 | } else { | |
e023a667 | 175 | bail!( |
3378a5a1 EH |
176 | "`{}` does not contain a Cargo.toml file. \ |
177 | --path must point to a directory containing a Cargo.toml file.", | |
178 | src.path().display() | |
179 | ) | |
180 | } | |
181 | } | |
182 | src.update()?; | |
e5a11190 E |
183 | select_pkg(src, krate, vers, config, false, &mut |path| { |
184 | path.read_packages() | |
185 | })? | |
bc60f64b | 186 | } else { |
1e682848 | 187 | select_pkg( |
192073bf | 188 | map.load(source_id, &HashSet::new())?, |
1e682848 AC |
189 | krate, |
190 | vers, | |
191 | config, | |
192 | is_first_install, | |
193 | &mut |_| { | |
e023a667 | 194 | bail!( |
1e682848 AC |
195 | "must specify a crate to install from \ |
196 | crates.io, or use --path or --git to \ | |
197 | specify alternate source" | |
198 | ) | |
199 | }, | |
200 | )? | |
bc60f64b | 201 | }; |
3c9b362b | 202 | |
3c9b362b | 203 | let mut td_opt = None; |
a81b1437 | 204 | let mut needs_cleanup = false; |
3c9b362b AK |
205 | let overidden_target_dir = if source_id.is_path() { |
206 | None | |
695bc5e4 | 207 | } else if let Some(dir) = config.target_dir()? { |
a81b1437 | 208 | Some(dir) |
8daf81e1 | 209 | } else if let Ok(td) = TempFileBuilder::new().prefix("cargo-install").tempdir() { |
3c9b362b AK |
210 | let p = td.path().to_owned(); |
211 | td_opt = Some(td); | |
212 | Some(Filesystem::new(p)) | |
213 | } else { | |
a81b1437 | 214 | needs_cleanup = true; |
3c9b362b AK |
215 | Some(Filesystem::new(config.cwd().join("target-install"))) |
216 | }; | |
217 | ||
eae89007 EH |
218 | let mut ws = match overidden_target_dir { |
219 | Some(dir) => Workspace::ephemeral(pkg, config, Some(dir), false)?, | |
d1ef031a AC |
220 | None => { |
221 | let mut ws = Workspace::new(pkg.manifest_path(), config)?; | |
222 | ws.set_require_optional_deps(false); | |
223 | ws | |
224 | } | |
225 | }; | |
eae89007 | 226 | ws.set_ignore_lock(config.lock_update_allowed()); |
82655b46 | 227 | let pkg = ws.current()?; |
bc60f64b | 228 | |
d6786057 | 229 | if from_cwd { |
b765fad1 DW |
230 | if pkg.manifest().edition() == Edition::Edition2015 { |
231 | config.shell().warn( | |
fc93b0f9 | 232 | "Using `cargo install` to install the binaries for the \ |
3492a390 | 233 | package in current working directory is deprecated, \ |
fc93b0f9 DW |
234 | use `cargo install --path .` instead. \ |
235 | Use `cargo build` if you want to simply build the package.", | |
b765fad1 DW |
236 | )? |
237 | } else { | |
e023a667 | 238 | bail!( |
fc93b0f9 | 239 | "Using `cargo install` to install the binaries for the \ |
3492a390 | 240 | package in current working directory is no longer supported, \ |
fc93b0f9 | 241 | use `cargo install --path .` instead. \ |
3e6de57a | 242 | Use `cargo build` if you want to simply build the package." |
b765fad1 | 243 | ) |
4676820f DW |
244 | } |
245 | }; | |
246 | ||
9ecdfe04 EH |
247 | // For bare `cargo install` (no `--bin` or `--example`), check if there is |
248 | // *something* to install. Explicit `--bin` or `--example` flags will be | |
249 | // checked at the start of `compile_ws`. | |
e023a667 EH |
250 | if !opts.filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) { |
251 | bail!("specified package `{}` has no binaries", pkg); | |
252 | } | |
82a8f6f7 | 253 | |
8eac1d62 AC |
254 | // Preflight checks to check up front whether we'll overwrite something. |
255 | // We have to check this again afterwards, but may as well avoid building | |
256 | // anything if we're gonna throw it away anyway. | |
e023a667 EH |
257 | let dst = root.join("bin").into_path_unlocked(); |
258 | let rustc = config.load_global_rustc(Some(&ws))?; | |
259 | let target = opts | |
260 | .build_config | |
261 | .requested_target | |
262 | .as_ref() | |
263 | .unwrap_or(&rustc.host) | |
264 | .clone(); | |
265 | ||
266 | // Helper for --no-track flag to make sure it doesn't overwrite anything. | |
267 | let no_track_duplicates = || -> CargoResult<BTreeMap<String, Option<PackageId>>> { | |
268 | let duplicates: BTreeMap<String, Option<PackageId>> = exe_names(pkg, &opts.filter) | |
269 | .into_iter() | |
270 | .filter(|name| dst.join(name).exists()) | |
271 | .map(|name| (name, None)) | |
272 | .collect(); | |
273 | if !force && !duplicates.is_empty() { | |
274 | let mut msg: Vec<String> = duplicates | |
275 | .iter() | |
276 | .map(|(name, _)| format!("binary `{}` already exists in destination", name)) | |
277 | .collect(); | |
278 | msg.push("Add --force to overwrite".to_string()); | |
279 | bail!("{}", msg.join("\n")); | |
280 | } | |
281 | Ok(duplicates) | |
282 | }; | |
283 | ||
5a9ff8a6 EH |
284 | // WARNING: no_track does not perform locking, so there is no protection |
285 | // of concurrent installs. | |
e023a667 EH |
286 | if no_track { |
287 | // Check for conflicts. | |
288 | no_track_duplicates()?; | |
289 | } else { | |
290 | let tracker = InstallTracker::load(config, root)?; | |
291 | let (freshness, _duplicates) = | |
292 | tracker.check_upgrade(&dst, pkg, force, opts, &target, &rustc.verbose_version)?; | |
293 | if freshness == Freshness::Fresh { | |
294 | let msg = format!( | |
295 | "package `{}` is already installed, use --force to override", | |
296 | pkg | |
297 | ); | |
298 | config.shell().status("Ignored", &msg)?; | |
299 | return Ok(()); | |
300 | } | |
301 | // Unlock while building. | |
302 | drop(tracker); | |
8eac1d62 | 303 | } |
bc60f64b | 304 | |
e023a667 EH |
305 | config.shell().status("Installing", pkg)?; |
306 | ||
5f616eb1 EH |
307 | check_yanked_install(&ws)?; |
308 | ||
b8b7faee | 309 | let exec: Arc<dyn Executor> = Arc::new(DefaultExecutor); |
8a301589 | 310 | let compile = ops::compile_ws(&ws, opts, &exec).chain_err(|| { |
e5a11190 E |
311 | if let Some(td) = td_opt.take() { |
312 | // preserve the temporary directory, so the user can inspect it | |
313 | td.into_path(); | |
314 | } | |
1a6bad8e | 315 | |
e023a667 | 316 | format_err!( |
e5a11190 E |
317 | "failed to compile `{}`, intermediate artifacts can be \ |
318 | found at `{}`", | |
319 | pkg, | |
320 | ws.target_dir().display() | |
321 | ) | |
322 | })?; | |
e023a667 | 323 | let mut binaries: Vec<(&str, &Path)> = compile |
1e682848 AC |
324 | .binaries |
325 | .iter() | |
326 | .map(|bin| { | |
327 | let name = bin.file_name().unwrap(); | |
328 | if let Some(s) = name.to_str() { | |
329 | Ok((s, bin.as_ref())) | |
330 | } else { | |
e023a667 | 331 | bail!("Binary `{:?}` name can't be serialized into string", name) |
1e682848 AC |
332 | } |
333 | }) | |
334 | .collect::<CargoResult<_>>()?; | |
d43fd2ea | 335 | if binaries.is_empty() { |
e023a667 | 336 | bail!("no binaries are available for install using the selected features"); |
d43fd2ea | 337 | } |
e023a667 EH |
338 | // This is primarily to make testing easier. |
339 | binaries.sort_unstable(); | |
bc60f64b | 340 | |
e023a667 EH |
341 | let (tracker, duplicates) = if no_track { |
342 | (None, no_track_duplicates()?) | |
343 | } else { | |
344 | let tracker = InstallTracker::load(config, root)?; | |
345 | let (_freshness, duplicates) = | |
346 | tracker.check_upgrade(&dst, pkg, force, opts, &target, &rustc.verbose_version)?; | |
347 | (Some(tracker), duplicates) | |
348 | }; | |
8eac1d62 | 349 | |
5102de2b | 350 | paths::create_dir_all(&dst)?; |
9ea1f2af GK |
351 | |
352 | // Copy all binaries to a temporary directory under `dst` first, catching | |
f7c91ba6 | 353 | // some failure modes (e.g., out of space) before touching the existing |
9ea1f2af | 354 | // binaries. This directory will get cleaned up via RAII. |
8daf81e1 BO |
355 | let staging_dir = TempFileBuilder::new() |
356 | .prefix("cargo-install") | |
357 | .tempdir_in(&dst)?; | |
9ea1f2af GK |
358 | for &(bin, src) in binaries.iter() { |
359 | let dst = staging_dir.path().join(bin); | |
9f0fa249 | 360 | // Try to move if `target_dir` is transient. |
23591fe5 | 361 | if !source_id.is_path() && fs::rename(src, &dst).is_ok() { |
1e682848 | 362 | continue; |
9f0fa249 | 363 | } |
e95044e3 | 364 | fs::copy(src, &dst).chain_err(|| { |
e023a667 | 365 | format_err!("failed to copy `{}` to `{}`", src.display(), dst.display()) |
82655b46 | 366 | })?; |
9ea1f2af GK |
367 | } |
368 | ||
1e682848 AC |
369 | let (to_replace, to_install): (Vec<&str>, Vec<&str>) = binaries |
370 | .iter() | |
371 | .map(|&(bin, _)| bin) | |
372 | .partition(|&bin| duplicates.contains_key(bin)); | |
9ea1f2af GK |
373 | |
374 | let mut installed = Transaction { bins: Vec::new() }; | |
e023a667 | 375 | let mut successful_bins = BTreeSet::new(); |
9ea1f2af GK |
376 | |
377 | // Move the temporary copies into `dst` starting with new binaries. | |
378 | for bin in to_install.iter() { | |
379 | let src = staging_dir.path().join(bin); | |
380 | let dst = dst.join(bin); | |
82655b46 | 381 | config.shell().status("Installing", dst.display())?; |
e95044e3 | 382 | fs::rename(&src, &dst).chain_err(|| { |
e023a667 | 383 | format_err!("failed to move `{}` to `{}`", src.display(), dst.display()) |
82655b46 | 384 | })?; |
9ea1f2af | 385 | installed.bins.push(dst); |
e023a667 | 386 | successful_bins.insert(bin.to_string()); |
9ea1f2af GK |
387 | } |
388 | ||
389 | // Repeat for binaries which replace existing ones but don't pop the error | |
390 | // up until after updating metadata. | |
e023a667 | 391 | let replace_result = { |
9ea1f2af GK |
392 | let mut try_install = || -> CargoResult<()> { |
393 | for &bin in to_replace.iter() { | |
394 | let src = staging_dir.path().join(bin); | |
395 | let dst = dst.join(bin); | |
82655b46 | 396 | config.shell().status("Replacing", dst.display())?; |
e95044e3 | 397 | fs::rename(&src, &dst).chain_err(|| { |
e023a667 | 398 | format_err!("failed to move `{}` to `{}`", src.display(), dst.display()) |
82655b46 | 399 | })?; |
e023a667 | 400 | successful_bins.insert(bin.to_string()); |
9ea1f2af GK |
401 | } |
402 | Ok(()) | |
403 | }; | |
404 | try_install() | |
405 | }; | |
406 | ||
9ecdfe04 | 407 | if let Some(mut tracker) = tracker { |
e023a667 EH |
408 | tracker.mark_installed( |
409 | pkg, | |
410 | &successful_bins, | |
411 | vers.map(|s| s.to_string()), | |
412 | opts, | |
593a02f2 AC |
413 | &target, |
414 | &rustc.verbose_version, | |
e023a667 | 415 | ); |
9ea1f2af | 416 | |
5a59b809 EH |
417 | if let Err(e) = remove_orphaned_bins(&ws, &mut tracker, &duplicates, pkg, &dst) { |
418 | // Don't hard error on remove. | |
419 | config | |
420 | .shell() | |
421 | .warn(format!("failed to remove orphan: {:?}", e))?; | |
422 | } | |
423 | ||
e023a667 EH |
424 | match tracker.save() { |
425 | Err(err) => replace_result.chain_err(|| err)?, | |
426 | Ok(_) => replace_result?, | |
427 | } | |
bc60f64b | 428 | } |
958f9d92 | 429 | |
9ea1f2af GK |
430 | // Reaching here means all actions have succeeded. Clean up. |
431 | installed.success(); | |
a81b1437 | 432 | if needs_cleanup { |
a9fd1c2c AC |
433 | // Don't bother grabbing a lock as we're going to blow it all away |
434 | // anyway. | |
3c9b362b | 435 | let target_dir = ws.target_dir().into_path_unlocked(); |
c933673e | 436 | paths::remove_dir_all(&target_dir)?; |
958f9d92 | 437 | } |
bc60f64b | 438 | |
e023a667 EH |
439 | // Helper for creating status messages. |
440 | fn executables<T: AsRef<str>>(mut names: impl Iterator<Item = T> + Clone) -> String { | |
441 | if names.clone().count() == 1 { | |
442 | format!("(executable `{}`)", names.next().unwrap().as_ref()) | |
9ea1f2af | 443 | } else { |
e023a667 EH |
444 | format!( |
445 | "(executables {})", | |
446 | names | |
447 | .map(|b| format!("`{}`", b.as_ref())) | |
448 | .collect::<Vec<_>>() | |
449 | .join(", ") | |
450 | ) | |
9ea1f2af GK |
451 | } |
452 | } | |
9ea1f2af | 453 | |
e023a667 EH |
454 | if duplicates.is_empty() { |
455 | config.shell().status( | |
456 | "Installed", | |
457 | format!("package `{}` {}", pkg, executables(successful_bins.iter())), | |
458 | )?; | |
459 | Ok(()) | |
460 | } else { | |
461 | if !to_install.is_empty() { | |
462 | config.shell().status( | |
463 | "Installed", | |
464 | format!("package `{}` {}", pkg, executables(to_install.iter())), | |
465 | )?; | |
bc60f64b | 466 | } |
e023a667 EH |
467 | // Invert the duplicate map. |
468 | let mut pkg_map = BTreeMap::new(); | |
469 | for (bin_name, opt_pkg_id) in &duplicates { | |
470 | let key = opt_pkg_id.map_or_else(|| "unknown".to_string(), |pkg_id| pkg_id.to_string()); | |
ea40fc4f | 471 | pkg_map.entry(key).or_insert_with(Vec::new).push(bin_name); |
e023a667 EH |
472 | } |
473 | for (pkg_descr, bin_names) in &pkg_map { | |
474 | config.shell().status( | |
475 | "Replaced", | |
476 | format!( | |
477 | "package `{}` with `{}` {}", | |
478 | pkg_descr, | |
479 | pkg, | |
480 | executables(bin_names.iter()) | |
481 | ), | |
482 | )?; | |
bc60f64b | 483 | } |
e023a667 | 484 | Ok(()) |
bc60f64b | 485 | } |
bc60f64b AC |
486 | } |
487 | ||
5f616eb1 EH |
488 | fn check_yanked_install(ws: &Workspace<'_>) -> CargoResult<()> { |
489 | if ws.ignore_lock() || !ws.root().join("Cargo.lock").exists() { | |
490 | return Ok(()); | |
491 | } | |
492 | let specs = vec![PackageIdSpec::from_package_id(ws.current()?.package_id())]; | |
493 | // It would be best if `source` could be passed in here to avoid a | |
494 | // duplicate "Updating", but since `source` is taken by value, then it | |
495 | // wouldn't be available for `compile_ws`. | |
e26ef017 | 496 | let (pkg_set, resolve) = ops::resolve_ws_with_opts(ws, ResolveOpts::everything(), &specs)?; |
5f616eb1 | 497 | let mut sources = pkg_set.sources_mut(); |
5217280e | 498 | |
e26ef017 | 499 | // Checking the yanked status involves taking a look at the registry and |
5217280e AC |
500 | // maybe updating files, so be sure to lock it here. |
501 | let _lock = ws.config().acquire_package_cache_lock()?; | |
502 | ||
5f616eb1 EH |
503 | for pkg_id in resolve.iter() { |
504 | if let Some(source) = sources.get_mut(pkg_id.source_id()) { | |
505 | if source.is_yanked(pkg_id)? { | |
506 | ws.config().shell().warn(format!( | |
507 | "package `{}` in Cargo.lock is yanked in registry `{}`, \ | |
508 | consider running without --locked", | |
509 | pkg_id, | |
510 | pkg_id.source_id().display_registry_name() | |
511 | ))?; | |
512 | } | |
513 | } | |
514 | } | |
515 | ||
516 | Ok(()) | |
517 | } | |
518 | ||
e023a667 | 519 | /// Display a list of installed binaries. |
bc60f64b | 520 | pub fn install_list(dst: Option<&str>, config: &Config) -> CargoResult<()> { |
e023a667 EH |
521 | let root = resolve_root(dst, config)?; |
522 | let tracker = InstallTracker::load(config, &root)?; | |
523 | for (k, v) in tracker.all_installed_bins() { | |
f8fb0a02 | 524 | println!("{}:", k); |
bc60f64b | 525 | for bin in v { |
f8fb0a02 | 526 | println!(" {}", bin); |
bc60f64b AC |
527 | } |
528 | } | |
529 | Ok(()) | |
530 | } | |
5a59b809 EH |
531 | |
532 | /// Removes executables that are no longer part of a package that was | |
533 | /// previously installed. | |
534 | fn remove_orphaned_bins( | |
535 | ws: &Workspace<'_>, | |
536 | tracker: &mut InstallTracker, | |
537 | duplicates: &BTreeMap<String, Option<PackageId>>, | |
538 | pkg: &Package, | |
539 | dst: &Path, | |
540 | ) -> CargoResult<()> { | |
541 | let filter = ops::CompileFilter::new_all_targets(); | |
542 | let all_self_names = exe_names(pkg, &filter); | |
543 | let mut to_remove: HashMap<PackageId, BTreeSet<String>> = HashMap::new(); | |
544 | // For each package that we stomped on. | |
545 | for other_pkg in duplicates.values() { | |
546 | // Only for packages with the same name. | |
547 | if let Some(other_pkg) = other_pkg { | |
548 | if other_pkg.name() == pkg.name() { | |
549 | // Check what the old package had installed. | |
550 | if let Some(installed) = tracker.installed_bins(*other_pkg) { | |
551 | // If the old install has any names that no longer exist, | |
552 | // add them to the list to remove. | |
553 | for installed_name in installed { | |
554 | if !all_self_names.contains(installed_name.as_str()) { | |
555 | to_remove | |
556 | .entry(*other_pkg) | |
557 | .or_default() | |
558 | .insert(installed_name.clone()); | |
559 | } | |
560 | } | |
561 | } | |
562 | } | |
563 | } | |
564 | } | |
565 | ||
566 | for (old_pkg, bins) in to_remove { | |
567 | tracker.remove(old_pkg, &bins); | |
568 | for bin in bins { | |
569 | let full_path = dst.join(bin); | |
570 | if full_path.exists() { | |
571 | ws.config().shell().status( | |
572 | "Removing", | |
573 | format!( | |
574 | "executable `{}` from previous version {}", | |
575 | full_path.display(), | |
576 | old_pkg | |
577 | ), | |
578 | )?; | |
579 | paths::remove_file(&full_path) | |
580 | .chain_err(|| format!("failed to remove {:?}", full_path))?; | |
581 | } | |
582 | } | |
583 | } | |
584 | Ok(()) | |
585 | } |