Rewrite the shebang in ephemeral env

This commit is contained in:
Charlie Marsh 2025-07-19 21:56:52 -04:00
parent b81cce9152
commit 66f483f38a
2 changed files with 67 additions and 7 deletions

View File

@ -78,6 +78,16 @@ impl EphemeralEnvironment {
)?; )?;
Ok(()) Ok(())
} }
/// Returns the path to the environment's scripts directory.
pub(crate) fn scripts(&self) -> &Path {
self.0.scripts()
}
/// Returns the path to the environment's Python executable.
pub(crate) fn sys_executable(&self) -> &Path {
self.0.interpreter().sys_executable()
}
} }
/// A [`PythonEnvironment`] stored in the cache. /// A [`PythonEnvironment`] stored in the cache.

View File

@ -1071,6 +1071,51 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
requirements_site_packages.escape_for_python(), requirements_site_packages.escape_for_python(),
))?; ))?;
for interpreter in [&base_interpreter, requirements_env.interpreter()] {
// Copy every binary from the base environment to the ephemeral environment.
for entry in fs_err::read_dir(interpreter.scripts())? {
let entry = entry?;
if !entry.file_type()?.is_file() {
continue;
}
// Read the whole file.
let contents = fs_err::read_to_string(&entry.path())?;
let expected = r#"#!/bin/sh
'''exec' "$(dirname -- "$(realpath -- "$0")")"/'python' "$0" "$@"
' '''
"#;
// let expected = format!("#!{}\n", interpreter.sys_executable().display());
// println!("Expected shebang: {expected}");
// Must start with a shebang.
if let Some(contents) = contents.strip_prefix(&expected) {
let contents = format!(
"#!{}\n{}",
ephemeral_env.sys_executable().display(),
contents
);
// Write the file to the ephemeral environment's scripts directory.
let target_path = ephemeral_env.scripts().join(entry.file_name());
fs_err::write(&target_path, &contents)?;
// Set the permissions to be executable.
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs_err::metadata(&target_path)?.permissions();
perms.set_mode(0o755);
fs_err::set_permissions(&target_path, perms)?;
}
println!("Writing to: {}", target_path.display());
// println!("{contents}");
}
}
}
// Write the `sys.prefix` of the parent environment to the `extends-environment` key of the `pyvenv.cfg` // Write the `sys.prefix` of the parent environment to the `extends-environment` key of the `pyvenv.cfg`
// file. This helps out static-analysis tools such as ty (see docs on // file. This helps out static-analysis tools such as ty (see docs on
// `CachedEnvironment::set_parent_environment`). // `CachedEnvironment::set_parent_environment`).
@ -1095,6 +1140,10 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
// Cast to `PythonEnvironment`. // Cast to `PythonEnvironment`.
let ephemeral_env = ephemeral_env.map(PythonEnvironment::from); let ephemeral_env = ephemeral_env.map(PythonEnvironment::from);
if let Some(e) = ephemeral_env.as_ref() {
println!("Using ephemeral environment at: {}", e.scripts().display());
}
// Determine the Python interpreter to use for the command, if necessary. // Determine the Python interpreter to use for the command, if necessary.
let interpreter = ephemeral_env let interpreter = ephemeral_env
.as_ref() .as_ref()
@ -1181,13 +1230,13 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.as_ref() .as_ref()
.map(PythonEnvironment::scripts) .map(PythonEnvironment::scripts)
.into_iter() .into_iter()
.chain( // .chain(
requirements_env // requirements_env
.as_ref() // .as_ref()
.map(PythonEnvironment::scripts) // .map(PythonEnvironment::scripts)
.into_iter(), // .into_iter(),
) // )
.chain(std::iter::once(base_interpreter.scripts())) // .chain(std::iter::once(base_interpreter.scripts()))
.chain( .chain(
// On Windows, non-virtual Python distributions put `python.exe` in the top-level // On Windows, non-virtual Python distributions put `python.exe` in the top-level
// directory, rather than in the `Scripts` subdirectory. // directory, rather than in the `Scripts` subdirectory.
@ -1205,6 +1254,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
.flat_map(std::env::split_paths), .flat_map(std::env::split_paths),
), ),
)?; )?;
println!("New PATH: {}", new_path.display());
process.env(EnvVars::PATH, new_path); process.env(EnvVars::PATH, new_path);
// Increment recursion depth counter. // Increment recursion depth counter.