Skip to content

Commit 0f1fbc1

Browse files
committed
Self delete
1 parent 75c2622 commit 0f1fbc1

File tree

6 files changed

+59
-32
lines changed

6 files changed

+59
-32
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-tool/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ impl InstalledTools {
184184
environment_path.user_display()
185185
);
186186

187+
// TODO(charlie): On Windows, if the current executable is in the directory,
188+
// we need to use `safe_delete`.
187189
fs_err::remove_dir_all(environment_path)?;
188190

189191
Ok(())

crates/uv-virtualenv/src/virtualenv.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub(crate) fn create(
8585
if allow_existing {
8686
debug!("Allowing existing directory");
8787
} else if location.join("pyvenv.cfg").is_file() {
88+
// TODO(charlie): On Windows, if the current executable is in the directory,
89+
// we need to use `safe_delete`.
8890
debug!("Removing existing directory");
8991
fs::remove_dir_all(location)?;
9092
fs::create_dir_all(location)?;

crates/uv/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ walkdir = { workspace = true }
9898
which = { workspace = true }
9999
zip = { workspace = true }
100100

101+
[target.'cfg(target_os = "windows")'.dependencies]
102+
same-file = { workspace = true }
103+
self-replace = { workspace = true }
104+
101105
[dev-dependencies]
102106
assert_cmd = { version = "2.0.16" }
103107
assert_fs = { version = "1.1.2" }

crates/uv/src/commands/tool/common.rs

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -123,47 +123,52 @@ pub(crate) fn install_executables(
123123
return Ok(ExitStatus::Failure);
124124
}
125125

126-
// Check if they exist, before installing
127-
let mut existing_entry_points = target_entry_points
128-
.iter()
129-
.filter(|(_, _, target_path)| target_path.exists())
130-
.peekable();
126+
// Error if we're overwriting an existing entrypoint, unless the user passed `--force`.
127+
if !force {
128+
let mut existing_entry_points = target_entry_points
129+
.iter()
130+
.filter(|(_, _, target_path)| target_path.exists())
131+
.peekable();
132+
if existing_entry_points.peek().is_some() {
133+
// Clean up the environment we just created
134+
installed_tools.remove_environment(name)?;
131135

132-
// Ignore any existing entrypoints if the user passed `--force`, or the existing recept was
133-
// broken.
134-
if force {
135-
for (name, _, target) in existing_entry_points {
136-
debug!("Removing existing executable: `{name}`");
137-
fs_err::remove_file(target)?;
136+
let existing_entry_points = existing_entry_points
137+
// SAFETY: We know the target has a filename because we just constructed it above
138+
.map(|(_, _, target)| target.file_name().unwrap().to_string_lossy())
139+
.collect::<Vec<_>>();
140+
let (s, exists) = if existing_entry_points.len() == 1 {
141+
("", "exists")
142+
} else {
143+
("s", "exist")
144+
};
145+
bail!(
146+
"Executable{s} already {exists}: {} (use `--force` to overwrite)",
147+
existing_entry_points
148+
.iter()
149+
.map(|name| name.bold())
150+
.join(", ")
151+
)
138152
}
139-
} else if existing_entry_points.peek().is_some() {
140-
// Clean up the environment we just created
141-
installed_tools.remove_environment(name)?;
142-
143-
let existing_entry_points = existing_entry_points
144-
// SAFETY: We know the target has a filename because we just constructed it above
145-
.map(|(_, _, target)| target.file_name().unwrap().to_string_lossy())
146-
.collect::<Vec<_>>();
147-
let (s, exists) = if existing_entry_points.len() == 1 {
148-
("", "exists")
149-
} else {
150-
("s", "exist")
151-
};
152-
bail!(
153-
"Executable{s} already {exists}: {} (use `--force` to overwrite)",
154-
existing_entry_points
155-
.iter()
156-
.map(|name| name.bold())
157-
.join(", ")
158-
)
159153
}
160154

155+
#[cfg(windows)]
156+
let itself = std::env::current_exe().ok();
157+
161158
for (name, source_path, target_path) in &target_entry_points {
162159
debug!("Installing executable: `{name}`");
160+
163161
#[cfg(unix)]
164162
replace_symlink(source_path, target_path).context("Failed to install executable")?;
163+
165164
#[cfg(windows)]
166-
fs_err::copy(source_path, target_path).context("Failed to install entrypoint")?;
165+
if itself.as_ref().is_some_and(|itself| {
166+
std::path::absolute(target_path).is_ok_and(|target| *itself == target)
167+
}) {
168+
self_replace::self_replace(source_path).context("Failed to install entrypoint")?;
169+
} else {
170+
fs_err::copy(source_path, target_path).context("Failed to install entrypoint")?;
171+
}
167172
}
168173

169174
let s = if target_entry_points.len() == 1 {

crates/uv/src/commands/tool/uninstall.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,25 @@ async fn uninstall_tool(
180180
// Remove the tool itself.
181181
tools.remove_environment(name)?;
182182

183+
#[cfg(windows)]
184+
let itself = std::env::current_exe().ok();
185+
183186
// Remove the tool's entrypoints.
184187
let entrypoints = receipt.entrypoints();
185188
for entrypoint in entrypoints {
186189
debug!(
187190
"Removing executable: {}",
188191
entrypoint.install_path.user_display()
189192
);
193+
194+
#[cfg(windows)]
195+
if itself.as_ref().is_some_and(|itself| {
196+
std::path::absolute(&entrypoint.install_path).is_ok_and(|target| *itself == target)
197+
}) {
198+
self_replace::self_delete()?;
199+
continue;
200+
}
201+
190202
match fs_err::tokio::remove_file(&entrypoint.install_path).await {
191203
Ok(()) => {}
192204
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {

0 commit comments

Comments
 (0)