Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Lib/test/test_importlib/resources/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class OpenDiskTests(FilesTests, unittest.TestCase):
def setUp(self):
self.data = data01

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON, line ending issue")
def test_read_bytes(self):
super().test_read_bytes()

Expand All @@ -67,10 +67,11 @@ def setUp(self):

self.data = namespacedata01

@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON, line ending issue")
def test_read_bytes(self):
super().test_read_bytes()


class SiteDir:
def setUp(self):
self.fixtures = contextlib.ExitStack()
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,6 @@ def test_module_names(self):
for name in sys.stdlib_module_names:
self.assertIsInstance(name, str)

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'sys' has no attribute '_stdlib_dir'
def test_stdlib_dir(self):
os = import_helper.import_fresh_module('os')
marker = getattr(os, '__file__', None)
Expand Down
28 changes: 19 additions & 9 deletions crates/pylib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,25 @@ fn main() {
process_python_libs("./Lib/**/*");
}

if cfg!(windows)
&& let Ok(real_path) = std::fs::read_to_string("Lib")
{
let canonicalized_path = std::fs::canonicalize(real_path)
.expect("failed to resolve RUSTPYTHONPATH during build time");
// Strip the extended path prefix (\\?\) that canonicalize adds on Windows
let path_str = canonicalized_path.to_str().unwrap();
let path_str = path_str.strip_prefix(r"\\?\").unwrap_or(path_str);
println!("cargo:rustc-env=win_lib_path={path_str}");
if cfg!(windows) {
// On Windows, the Lib entry can be either:
// 1. A text file containing the relative path (git without symlink support)
// 2. A proper symlink (git with symlink support)
// We handle both cases to resolve to the actual Lib directory.
let lib_path = if let Ok(real_path) = std::fs::read_to_string("Lib") {
// Case 1: Text file containing relative path
std::path::PathBuf::from(real_path.trim())
} else {
// Case 2: Symlink or directory - canonicalize directly
std::path::PathBuf::from("Lib")
};

if let Ok(canonicalized_path) = std::fs::canonicalize(&lib_path) {
// Strip the extended path prefix (\\?\) that canonicalize adds on Windows
let path_str = canonicalized_path.to_str().unwrap();
let path_str = path_str.strip_prefix(r"\\?\").unwrap_or(path_str);
println!("cargo:rustc-env=win_lib_path={path_str}");
Comment on lines +14 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid panic on non‑UTF‑8 Windows paths.
to_str().unwrap() can panic on valid Windows paths containing non‑UTF‑8 sequences, breaking the build. Prefer a lossy conversion and consider warning when canonicalization fails so missing win_lib_path isn’t silent.

🔧 Proposed safer emission
-        if let Ok(canonicalized_path) = std::fs::canonicalize(&lib_path) {
-            // Strip the extended path prefix (\\?\) that canonicalize adds on Windows
-            let path_str = canonicalized_path.to_str().unwrap();
-            let path_str = path_str.strip_prefix(r"\\?\").unwrap_or(path_str);
-            println!("cargo:rustc-env=win_lib_path={path_str}");
-        }
+        if let Ok(canonicalized_path) = std::fs::canonicalize(&lib_path) {
+            // Strip the extended path prefix (\\?\) that canonicalize adds on Windows
+            let path_str = canonicalized_path.to_string_lossy();
+            let path_str = path_str.strip_prefix(r"\\?\").unwrap_or(&path_str);
+            println!("cargo:rustc-env=win_lib_path={path_str}");
+        } else {
+            println!("cargo:warning=failed to canonicalize Lib path: {:?}", lib_path);
+        }
🤖 Prompt for AI Agents
In `@crates/pylib/build.rs` around lines 14 - 31, The build script currently calls
canonicalize(...).to_str().unwrap(), which can panic on non‑UTF‑8 Windows paths;
change the emission to use a lossy conversion from the canonicalized Path (e.g.,
Path::to_string_lossy on the value returned by std::fs::canonicalize(&lib_path))
and print that into the cargo:rustc-env=win_lib_path value, and if
std::fs::canonicalize(&lib_path) returns an Err emit a visible build warning via
println!("cargo:warning=...") instead of silently skipping so missing
win_lib_path is obvious; update the code paths referencing canonicalized_path
and to_str().unwrap() accordingly.

}
}
}

Expand Down
19 changes: 19 additions & 0 deletions crates/vm/src/getpath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ pub fn init_path_config(settings: &Settings) -> Paths {
paths.module_search_paths =
build_module_search_paths(settings, &paths.prefix, &paths.exec_prefix);

// Step 9: Calculate stdlib_dir
paths.stdlib_dir = calculate_stdlib_dir(&paths.prefix);

paths
}

Expand Down Expand Up @@ -301,6 +304,22 @@ fn calculate_base_executable(executable: Option<&PathBuf>, home_dir: &Option<Pat
.unwrap_or_default()
}

/// Calculate stdlib_dir (sys._stdlib_dir)
/// Returns None if the stdlib directory doesn't exist
fn calculate_stdlib_dir(prefix: &str) -> Option<String> {
#[cfg(not(windows))]
let stdlib_dir = PathBuf::from(prefix).join(platform::stdlib_subdir());

#[cfg(windows)]
let stdlib_dir = PathBuf::from(prefix).join(platform::STDLIB_SUBDIR);

if stdlib_dir.is_dir() {
Some(stdlib_dir.to_string_lossy().into_owned())
} else {
None
}
}

/// Build the complete module_search_paths (sys.path)
fn build_module_search_paths(settings: &Settings, prefix: &str, exec_prefix: &str) -> Vec<String> {
let mut paths = Vec::new();
Expand Down
4 changes: 4 additions & 0 deletions crates/vm/src/stdlib/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ mod sys {
fn platlibdir(_vm: &VirtualMachine) -> &'static str {
option_env!("RUSTPYTHON_PLATLIBDIR").unwrap_or("lib")
}
#[pyattr]
fn _stdlib_dir(vm: &VirtualMachine) -> PyObjectRef {
vm.state.config.paths.stdlib_dir.clone().to_pyobject(vm)
}

// alphabetical order with segments of pyattr and others

Expand Down
2 changes: 2 additions & 0 deletions crates/vm/src/vm/setting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct Paths {
pub exec_prefix: String,
/// sys.base_exec_prefix
pub base_exec_prefix: String,
/// sys._stdlib_dir
pub stdlib_dir: Option<String>,
/// Computed module_search_paths (complete sys.path)
pub module_search_paths: Vec<String>,
}
Expand Down
20 changes: 10 additions & 10 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,13 @@ pub fn init_stdlib(vm: &mut VirtualMachine) {
/// Setup frozen standard library (compiled into the binary)
#[cfg(all(feature = "stdlib", feature = "freeze-stdlib"))]
fn setup_frozen_stdlib(vm: &mut VirtualMachine) {
use rustpython_vm::common::rc::PyRc;

vm.add_frozen(rustpython_pylib::FROZEN_STDLIB);

// FIXME: Remove this hack once sys._stdlib_dir is properly implemented
// or _frozen_importlib doesn't depend on it anymore.
assert!(vm.sys_module.get_attr("_stdlib_dir", vm).is_err());
vm.sys_module
.set_attr(
"_stdlib_dir",
vm.new_pyobj(rustpython_pylib::LIB_PATH.to_owned()),
vm,
)
.unwrap();
// Set stdlib_dir to the frozen stdlib path
let state = PyRc::get_mut(&mut vm.state).unwrap();
state.config.paths.stdlib_dir = Some(rustpython_pylib::LIB_PATH.to_owned());
}

/// Setup dynamic standard library loading from filesystem
Expand All @@ -135,6 +130,11 @@ fn setup_dynamic_stdlib(vm: &mut VirtualMachine) {
let state = PyRc::get_mut(&mut vm.state).unwrap();
let paths = collect_stdlib_paths();

// Set stdlib_dir to the first stdlib path if available
if let Some(first_path) = paths.first() {
state.config.paths.stdlib_dir = Some(first_path.clone());
}

// Insert at the beginning so stdlib comes before user paths
for path in paths.into_iter().rev() {
state.config.paths.module_search_paths.insert(0, path);
Expand Down
Loading