mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-13 17:25:41 +00:00
Add fallback parent process detection to uv tool update-shell (#15356)
## Summary Closes #15355 This PR adds a fallback mechanism to `Shell::from_env()` that inspects the parent process when shell environment variables are not available on Unix-like systems. Currently, `uv tool update-shell` fails with "the current shell could not be determined" when environment variables like `ZSH_VERSION`, `BASH_VERSION`, or `SHELL` are not exported. This commonly occurs in automated environments such as GitHub Actions runners. The fallback approach: 1. Uses `nix::unistd::getppid()` to get the parent process ID 2. Reads `/proc/<ppid>/exe` to determine the parent executable path 3. Falls back to `/proc/<ppid>/comm` if the exe symlink fails 4. Uses existing `parse_shell_from_path()` to identify the shell type This maintains full backward compatibility - the fallback only activates when environment variables are unavailable and an error would otherwise occur. ## Test Plan Tested locally with: ```bash env -u ZSH_VERSION -u SHELL PATH="/usr/bin:/bin" $(which cargo) run -- tool update-shell --verbose ``` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/uv tool update-shell --verbose` DEBUG uv 0.8.11 DEBUG Ensuring that the executable directory is in PATH: /home/user/.local/bin DEBUG Detected parent process ID: 4147396 DEBUG Parent process executable: /usr/bin/zsh Updated configuration file: /home/user/.zshenv Restart your shell to apply changes ```
This commit is contained in:
parent
242214c546
commit
4b88b1379a
3 changed files with 53 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -6272,6 +6272,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"fs-err",
|
"fs-err",
|
||||||
"home",
|
"home",
|
||||||
|
"nix 0.30.1",
|
||||||
"same-file",
|
"same-file",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,14 @@ uv-fs = { workspace = true }
|
||||||
uv-static = { workspace = true }
|
uv-static = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
fs-err = { workspace = true }
|
||||||
home = { workspace = true }
|
home = { workspace = true }
|
||||||
same-file = { workspace = true }
|
same-file = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
nix = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows-registry = { workspace = true }
|
windows-registry = { workspace = true }
|
||||||
windows-result = { workspace = true }
|
windows-result = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ use std::path::{Path, PathBuf};
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
/// Shells for which virtualenv activation scripts are available.
|
/// Shells for which virtualenv activation scripts are available.
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
#[allow(clippy::doc_markdown)]
|
#[allow(clippy::doc_markdown)]
|
||||||
|
|
@ -64,6 +67,51 @@ impl Shell {
|
||||||
Some(Self::Powershell)
|
Some(Self::Powershell)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Fallback to detecting the shell from the parent process
|
||||||
|
Self::from_parent_process()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to determine the shell from the parent process.
|
||||||
|
///
|
||||||
|
/// This is a fallback method for when environment variables don't provide
|
||||||
|
/// enough information about the current shell. It looks at the parent process
|
||||||
|
/// to try to identify which shell is running.
|
||||||
|
///
|
||||||
|
/// This method currently only works on Unix-like systems. On other platforms,
|
||||||
|
/// it returns `None`.
|
||||||
|
fn from_parent_process() -> Option<Self> {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
// Get the parent process ID
|
||||||
|
let ppid = nix::unistd::getppid();
|
||||||
|
debug!("Detected parent process ID: {ppid}");
|
||||||
|
|
||||||
|
// Try to read the parent process executable path
|
||||||
|
let proc_exe_path = format!("/proc/{ppid}/exe");
|
||||||
|
if let Ok(exe_path) = fs_err::read_link(&proc_exe_path) {
|
||||||
|
debug!("Parent process executable: {}", exe_path.display());
|
||||||
|
if let Some(shell) = Self::from_shell_path(&exe_path) {
|
||||||
|
return Some(shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If reading exe fails, try reading the comm file
|
||||||
|
let proc_comm_path = format!("/proc/{ppid}/comm");
|
||||||
|
if let Ok(comm) = fs_err::read_to_string(&proc_comm_path) {
|
||||||
|
let comm = comm.trim();
|
||||||
|
debug!("Parent process comm: {comm}");
|
||||||
|
if let Some(shell) = parse_shell_from_path(Path::new(comm)) {
|
||||||
|
return Some(shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Could not determine shell from parent process");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue