diff --git a/crates/uv-virtualenv/src/lib.rs b/crates/uv-virtualenv/src/lib.rs index 1eac4eeca0c66..9c485c7ae6c64 100644 --- a/crates/uv-virtualenv/src/lib.rs +++ b/crates/uv-virtualenv/src/lib.rs @@ -32,6 +32,11 @@ pub enum Error { /// The non-virtual environment directory that would have been cleared. path: PathBuf, }, + #[error("Virtual environment path is not valid UTF-8: {}", path.user_display())] + NonUtf8Path { + /// The non-UTF-8 virtual environment path. + path: PathBuf, + }, } impl uv_errors::Hint for Error { diff --git a/crates/uv-virtualenv/src/virtualenv.rs b/crates/uv-virtualenv/src/virtualenv.rs index 33ffb4856ae6e..92cecb83a794a 100644 --- a/crates/uv-virtualenv/src/virtualenv.rs +++ b/crates/uv-virtualenv/src/virtualenv.rs @@ -480,6 +480,12 @@ pub(crate) fn create( .map(|path| path.simplified().to_str().unwrap().replace('\\', "\\\\")) .join(path_sep); + let location_string = location + .simplified() + .to_str() + .ok_or_else(|| Error::NonUtf8Path { + path: location.clone(), + })?; let virtual_env_dir = match (relocatable, name.to_owned()) { (true, "activate") => Cow::Borrowed( r#"'"$(dirname -- "$(dirname -- "$(realpath -- "$SCRIPT_PATH")")")"'"#, @@ -491,10 +497,10 @@ pub(crate) fn create( (true, "activate.nu") => Cow::Borrowed(r"(path self | path dirname | path dirname)"), (false, "activate.nu") => Cow::Owned(format!( "'{}'", - escape_posix_for_single_quotes(location.simplified().to_str().unwrap()) + escape_posix_for_single_quotes(location_string) )), // Note: `activate.ps1` is already relocatable by default. - _ => escape_posix_for_single_quotes(location.simplified().to_str().unwrap()), + _ => escape_posix_for_single_quotes(location_string), }; let activator = template diff --git a/crates/uv/tests/python/venv.rs b/crates/uv/tests/python/venv.rs index aeeb54423244f..0df85574351c8 100644 --- a/crates/uv/tests/python/venv.rs +++ b/crates/uv/tests/python/venv.rs @@ -10,6 +10,10 @@ use uv_static::EnvVars; #[cfg(unix)] use fs_err::os::unix::fs::symlink; +#[cfg(unix)] +use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; +#[cfg(windows)] +use std::{ffi::OsString, os::windows::ffi::OsStringExt}; use uv_test::{site_packages_path, uv_snapshot}; @@ -1489,6 +1493,31 @@ fn file_exists() -> Result<()> { Ok(()) } +#[test] +fn non_utf8_path() { + let context = uv_test::test_context_with_versions!(&["3.12"]); + let path = cfg_select! { + unix => OsStr::from_bytes(b".venv-\xff"), + windows => OsString::from_wide(&[0x002e, 0x0076, 0x0065, 0x006e, 0x0076, 0x002d, 0xd800]), + }; + + uv_snapshot!(context.filters(), context.venv() + .arg(path) + .arg("--python") + .arg("3.12"), @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Creating virtual environment at: .venv-� + error: Failed to create virtual environment + Caused by: Virtual environment path is not valid UTF-8: .venv-� + " + ); +} + #[test] fn empty_dir_exists() -> Result<()> { let context = uv_test::test_context_with_versions!(&["3.12"]);