Skip to content

Commit 8479589

Browse files
committed
Auto merge of #13053 - linyihai:cargo-uninstall-fixed, r=weihanglo
Fixed uninstall a running binary failed on Windows ### What does this PR try to resolve? Fixes #3364 The problem reproduce when you try to uninstall a running binary and it will failed on Windows, this is because that cargo had already remove the installed binary tracker information in disk, and next to remove the running binary but it is not allowed in Windows. And to the worst, you can not uninstall the binary already and only to reinstall it by the `--force` flag. ### How to solve the issue? This PR try to make the uninstall a binary more reasonable that only after remove the binary sucessfully then remove the tracker information on binary and make the remove binary one by one. ### How should we test and review this PR? Add testcase 0fd4fd3 - install the `foo` - run `foo` in another thread. - try to uninstall running `foo` and only failed in Windows. - wait the `foo` finish, uninstall `foo` - install the `foo` ### Additional information
2 parents e9ba4fe + db144c9 commit 8479589

File tree

3 files changed

+147
-15
lines changed

3 files changed

+147
-15
lines changed

src/cargo/ops/cargo_uninstall.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::util::errors::CargoResult;
66
use crate::util::Config;
77
use crate::util::Filesystem;
88
use anyhow::bail;
9-
use cargo_util::paths;
109
use std::collections::BTreeSet;
1110
use std::env;
1211

@@ -103,7 +102,6 @@ fn uninstall_pkgid(
103102
bins: &[String],
104103
config: &Config,
105104
) -> CargoResult<()> {
106-
let mut to_remove = Vec::new();
107105
let installed = match tracker.installed_bins(pkgid) {
108106
Some(bins) => bins.clone(),
109107
None => bail!("package `{}` is not installed", pkgid),
@@ -137,19 +135,18 @@ fn uninstall_pkgid(
137135
}
138136
}
139137

140-
if bins.is_empty() {
141-
to_remove.extend(installed.iter().map(|b| dst.join(b)));
142-
tracker.remove(pkgid, &installed);
143-
} else {
144-
for bin in bins.iter() {
145-
to_remove.push(dst.join(bin));
138+
let to_remove = {
139+
if bins.is_empty() {
140+
installed
141+
} else {
142+
bins
146143
}
147-
tracker.remove(pkgid, &bins);
148-
}
149-
tracker.save()?;
144+
};
145+
150146
for bin in to_remove {
151-
config.shell().status("Removing", bin.display())?;
152-
paths::remove_file(bin)?;
147+
let bin_path = dst.join(&bin);
148+
config.shell().status("Removing", bin_path.display())?;
149+
tracker.remove_bin_then_save(pkgid, &bin, &bin_path)?;
153150
}
154151

155152
Ok(())

src/cargo/ops/common_for_install_and_uninstall.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::rc::Rc;
77
use std::task::Poll;
88

99
use anyhow::{bail, format_err, Context as _};
10+
use cargo_util::paths;
1011
use ops::FilterRule;
1112
use serde::{Deserialize, Serialize};
1213

@@ -319,6 +320,20 @@ impl InstallTracker {
319320
self.v1.remove(pkg_id, bins);
320321
self.v2.remove(pkg_id, bins);
321322
}
323+
324+
/// Remove a bin after it successfully had been removed in disk and then save the tracker at last.
325+
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+
}
322337
}
323338

324339
impl CrateListingV1 {
@@ -359,6 +374,17 @@ impl CrateListingV1 {
359374
}
360375
}
361376

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+
362388
fn save(&self, lock: &FileLock) -> CargoResult<()> {
363389
let mut file = lock.file();
364390
file.seek(SeekFrom::Start(0))?;
@@ -468,6 +494,17 @@ impl CrateListingV2 {
468494
}
469495
}
470496

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+
471508
fn save(&self, lock: &FileLock) -> CargoResult<()> {
472509
let mut file = lock.file();
473510
file.seek(SeekFrom::Start(0))?;

tests/testsuite/install.rs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::fs::{self, OpenOptions};
44
use std::io::prelude::*;
55
use std::path::Path;
6+
use std::thread;
67

78
use cargo_test_support::compare;
89
use cargo_test_support::cross_compile;
@@ -11,10 +12,10 @@ use cargo_test_support::registry::{self, registry_path, Package};
1112
use cargo_test_support::{
1213
basic_manifest, cargo_process, no_such_file_err_msg, project, project_in, symlink_supported, t,
1314
};
14-
use cargo_util::ProcessError;
15+
use cargo_util::{ProcessBuilder, ProcessError};
1516

1617
use cargo_test_support::install::{
17-
assert_has_installed_exe, assert_has_not_installed_exe, cargo_home,
18+
assert_has_installed_exe, assert_has_not_installed_exe, cargo_home, exe,
1819
};
1920
use cargo_test_support::paths::{self, CargoPathExt};
2021
use std::env;
@@ -2507,3 +2508,100 @@ fn install_incompat_msrv() {
25072508
")
25082509
.with_status(101).run();
25092510
}
2511+
2512+
fn assert_tracker_noexistence(key: &str) {
2513+
let v1_data: toml::Value =
2514+
toml::from_str(&fs::read_to_string(cargo_home().join(".crates.toml")).unwrap()).unwrap();
2515+
let v2_data: serde_json::Value =
2516+
serde_json::from_str(&fs::read_to_string(cargo_home().join(".crates2.json")).unwrap())
2517+
.unwrap();
2518+
2519+
assert!(v1_data["v1"].get(key).is_none());
2520+
assert!(v2_data["installs"][key].is_null());
2521+
}
2522+
2523+
#[cargo_test]
2524+
fn uninstall_running_binary() {
2525+
Package::new("foo", "0.0.1")
2526+
.file("src/lib.rs", "")
2527+
.file(
2528+
"Cargo.toml",
2529+
r#"
2530+
[package]
2531+
name = "foo"
2532+
version = "0.0.1"
2533+
2534+
[[bin]]
2535+
name = "foo"
2536+
path = "src/main.rs"
2537+
"#,
2538+
)
2539+
.file(
2540+
"src/main.rs",
2541+
r#"
2542+
use std::{{thread, time}};
2543+
fn main() {
2544+
thread::sleep(time::Duration::from_secs(3));
2545+
}
2546+
"#,
2547+
)
2548+
.publish();
2549+
2550+
cargo_process("install foo")
2551+
.with_stderr(
2552+
"\
2553+
[UPDATING] `[..]` index
2554+
[DOWNLOADING] crates ...
2555+
[DOWNLOADED] foo v0.0.1 (registry [..])
2556+
[INSTALLING] foo v0.0.1
2557+
[COMPILING] foo v0.0.1
2558+
[FINISHED] release [optimized] target(s) in [..]
2559+
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
2560+
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
2561+
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
2562+
",
2563+
)
2564+
.run();
2565+
assert_has_installed_exe(cargo_home(), "foo");
2566+
2567+
let foo_bin = cargo_home().join("bin").join(exe("foo"));
2568+
let t = thread::spawn(|| ProcessBuilder::new(foo_bin).exec().unwrap());
2569+
let key = "foo 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)";
2570+
2571+
#[cfg(windows)]
2572+
{
2573+
cargo_process("uninstall foo")
2574+
.with_status(101)
2575+
.with_stderr_contains("[ERROR] failed to remove file `[CWD]/home/.cargo/bin/foo[EXE]`")
2576+
.run();
2577+
t.join().unwrap();
2578+
cargo_process("uninstall foo")
2579+
.with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
2580+
.run();
2581+
};
2582+
2583+
#[cfg(not(windows))]
2584+
{
2585+
cargo_process("uninstall foo")
2586+
.with_stderr("[REMOVING] [CWD]/home/.cargo/bin/foo[EXE]")
2587+
.run();
2588+
t.join().unwrap();
2589+
};
2590+
2591+
assert_has_not_installed_exe(cargo_home(), "foo");
2592+
assert_tracker_noexistence(key);
2593+
2594+
cargo_process("install foo")
2595+
.with_stderr(
2596+
"\
2597+
[UPDATING] `[..]` index
2598+
[INSTALLING] foo v0.0.1
2599+
[COMPILING] foo v0.0.1
2600+
[FINISHED] release [optimized] target(s) in [..]
2601+
[INSTALLING] [CWD]/home/.cargo/bin/foo[EXE]
2602+
[INSTALLED] package `foo v0.0.1` (executable `foo[EXE]`)
2603+
[WARNING] be sure to add `[..]` to your PATH to be able to run the installed binaries
2604+
",
2605+
)
2606+
.run();
2607+
}

0 commit comments

Comments
 (0)