From 99f3e3e9919ebdaa48282e578a2d4cc0fb8e064a Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 19 Jan 2026 21:07:33 +0900 Subject: [PATCH] Add sys._stdlib_dir --- .../test_importlib/resources/test_files.py | 5 ++-- Lib/test/test_sys.py | 1 - crates/pylib/build.rs | 28 +++++++++++++------ crates/vm/src/getpath.rs | 19 +++++++++++++ crates/vm/src/stdlib/sys.rs | 4 +++ crates/vm/src/vm/setting.rs | 2 ++ src/interpreter.rs | 20 ++++++------- 7 files changed, 57 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_importlib/resources/test_files.py b/Lib/test/test_importlib/resources/test_files.py index 1d04cda1a8f..0e9c5c79a1a 100644 --- a/Lib/test/test_importlib/resources/test_files.py +++ b/Lib/test/test_importlib/resources/test_files.py @@ -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() @@ -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() diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 77300fbe0bb..47039aa5114 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -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) diff --git a/crates/pylib/build.rs b/crates/pylib/build.rs index 9b135690f82..f96ef9b477c 100644 --- a/crates/pylib/build.rs +++ b/crates/pylib/build.rs @@ -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}"); + } } } diff --git a/crates/vm/src/getpath.rs b/crates/vm/src/getpath.rs index 011d5336873..31fa0617b45 100644 --- a/crates/vm/src/getpath.rs +++ b/crates/vm/src/getpath.rs @@ -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 } @@ -301,6 +304,22 @@ fn calculate_base_executable(executable: Option<&PathBuf>, home_dir: &Option Option { + #[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 { let mut paths = Vec::new(); diff --git a/crates/vm/src/stdlib/sys.rs b/crates/vm/src/stdlib/sys.rs index 6c8a4e4062d..665b0f1f495 100644 --- a/crates/vm/src/stdlib/sys.rs +++ b/crates/vm/src/stdlib/sys.rs @@ -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 diff --git a/crates/vm/src/vm/setting.rs b/crates/vm/src/vm/setting.rs index 06cc35e933f..1a5ef9efa8f 100644 --- a/crates/vm/src/vm/setting.rs +++ b/crates/vm/src/vm/setting.rs @@ -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, /// Computed module_search_paths (complete sys.path) pub module_search_paths: Vec, } diff --git a/src/interpreter.rs b/src/interpreter.rs index 51667e724f1..08d23e78afe 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -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 @@ -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);