diff --git a/crates/uv-build/src/lib.rs b/crates/uv-build/src/lib.rs index 969938714..c61108054 100644 --- a/crates/uv-build/src/lib.rs +++ b/crates/uv-build/src/lib.rs @@ -28,7 +28,7 @@ use tracing::{debug, info_span, instrument, Instrument}; use distribution_types::Resolution; use pep440_rs::{Version, VersionSpecifiers}; use pep508_rs::Requirement; -use uv_fs::Simplified; +use uv_fs::{PythonExt, Simplified}; use uv_interpreter::{Interpreter, PythonEnvironment}; use uv_traits::{ BuildContext, BuildIsolation, BuildKind, ConfigSettings, SetupPyStrategy, SourceBuildTrait, @@ -637,7 +637,7 @@ impl SourceBuild { pep517_backend.backend_import(), escape_path_for_python(&metadata_directory), self.config_settings.escape_for_python(), - escape_path_for_python(&outfile), + outfile.escape_for_python(), }; let span = info_span!( "run_python_script", @@ -744,7 +744,7 @@ impl SourceBuild { .metadata_directory .as_deref() .map_or("None".to_string(), |path| { - format!(r#""{}""#, escape_path_for_python(path)) + format!(r#""{}""#, path.escape_for_python()) }); // Write the hook output to a file so that we can read it back reliably. @@ -767,10 +767,10 @@ impl SourceBuild { "#, pep517_backend.backend_import(), self.build_kind, - escape_path_for_python(wheel_dir), + wheel_dir.escape_for_python(), metadata_directory, self.config_settings.escape_for_python(), - escape_path_for_python(&outfile) + outfile.escape_for_python() }; let span = info_span!( "run_python_script", @@ -869,7 +869,7 @@ async fn create_pep517_build_environment( pep517_backend.backend_import(), build_kind, config_settings.escape_for_python(), - escape_path_for_python(&outfile) + outfile.escape_for_python() }; let span = info_span!( "run_python_script", diff --git a/crates/uv-fs/src/path.rs b/crates/uv-fs/src/path.rs index d722fb41e..521ae86db 100644 --- a/crates/uv-fs/src/path.rs +++ b/crates/uv-fs/src/path.rs @@ -24,6 +24,20 @@ impl> Simplified for T { } } +pub trait PythonExt { + /// Escape a [`Path`] for use in Python code. + fn escape_for_python(&self) -> String; +} + +impl> PythonExt for T { + fn escape_for_python(&self) -> String { + self.as_ref() + .to_string_lossy() + .replace('\\', "\\\\") + .replace('"', "\\\"") + } +} + /// Normalize the `path` component of a URL for use as a file path. /// /// For example, on Windows, transforms `C:\Users\ferris\wheel-0.42.0.tar.gz` to diff --git a/crates/uv-interpreter/python/get_interpreter_info.py b/crates/uv-interpreter/python/get_interpreter_info.py index 47a70ccc6..3a8917c9e 100644 --- a/crates/uv-interpreter/python/get_interpreter_info.py +++ b/crates/uv-interpreter/python/get_interpreter_info.py @@ -498,30 +498,35 @@ def get_operating_system_and_architecture(): return {"os": operating_system, "arch": architecture} -markers = { - "implementation_name": implementation_name, - "implementation_version": implementation_version, - "os_name": os.name, - "platform_machine": platform.machine(), - "platform_python_implementation": platform.python_implementation(), - "platform_release": platform.release(), - "platform_system": platform.system(), - "platform_version": platform.version(), - "python_full_version": python_full_version, - "python_version": ".".join(platform.python_version_tuple()[:2]), - "sys_platform": sys.platform, -} -interpreter_info = { - "result": "success", - "markers": markers, - "base_prefix": sys.base_prefix, - "base_exec_prefix": sys.base_exec_prefix, - "prefix": sys.prefix, - "base_executable": getattr(sys, "_base_executable", None), - "sys_executable": sys.executable, - "stdlib": sysconfig.get_path("stdlib"), - "scheme": get_scheme(), - "virtualenv": get_virtualenv(), - "platform": get_operating_system_and_architecture(), -} -print(json.dumps(interpreter_info)) +def main() -> None: + markers = { + "implementation_name": implementation_name, + "implementation_version": implementation_version, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_python_implementation": platform.python_implementation(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": python_full_version, + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + interpreter_info = { + "result": "success", + "markers": markers, + "base_prefix": sys.base_prefix, + "base_exec_prefix": sys.base_exec_prefix, + "prefix": sys.prefix, + "base_executable": getattr(sys, "_base_executable", None), + "sys_executable": sys.executable, + "stdlib": sysconfig.get_path("stdlib"), + "scheme": get_scheme(), + "virtualenv": get_virtualenv(), + "platform": get_operating_system_and_architecture(), + } + print(json.dumps(interpreter_info)) + + +if __name__ == "__main__": + main() diff --git a/crates/uv-interpreter/src/interpreter.rs b/crates/uv-interpreter/src/interpreter.rs index 660ec190e..aa167fc9a 100644 --- a/crates/uv-interpreter/src/interpreter.rs +++ b/crates/uv-interpreter/src/interpreter.rs @@ -15,7 +15,7 @@ use platform_tags::Platform; use platform_tags::{Tags, TagsError}; use pypi_types::Scheme; use uv_cache::{Cache, CacheBucket, CachedByTimestamp, Freshness, Timestamp}; -use uv_fs::{write_atomic_sync, Simplified}; +use uv_fs::{write_atomic_sync, PythonExt, Simplified}; use crate::Error; use crate::Virtualenv; @@ -369,11 +369,17 @@ impl InterpreterInfo { let tempdir = tempfile::tempdir_in(cache.root())?; Self::setup_python_query_files(tempdir.path())?; + // Sanitize the path by (1) running under isolated mode (`-I`) to ignore any site packages + // modifications, and then (2) adding the path containing our query script to the front of + // `sys.path` so that we can import it. + let script = format!( + r#"import sys; sys.path = ["{}"] + sys.path; from python.get_interpreter_info import main; main()"#, + tempdir.path().escape_for_python() + ); let output = Command::new(interpreter) - .arg("-m") - .arg("python.get_interpreter_info") - .env("PYTHONPATH", tempdir.path()) - .env("PYTHONSAFEPATH", "1") + .arg("-I") + .arg("-c") + .arg(script) .output() .map_err(|err| Error::PythonSubcommandLaunch { interpreter: interpreter.to_path_buf(),