diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 0def1da45..4fe77bab3 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1369,7 +1369,6 @@ class CmdLineTest(unittest.TestCase): proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass") self.assertEqual(proc.out.rstrip(), b"presite func") - @unittest.expectedFailure # TODO: RUSTPYTHON; explicit PYTHONHOME startup path configuration and fatal encodings failure reporting are not implemented def test_dump_path_config(self): # gh-151253: At the first import (import encodings) during Python # startup, if the import fails, dump the Python path configuration. diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 0ddf79cc0..ad748f63a 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -111,6 +111,7 @@ pub struct VirtualMachine { pub state: PyRc, pub initialized: bool, pub(crate) import_bootstrap_complete: bool, + startup_error: Option, recursion_depth: Cell, /// Depth of native recursion that pushes no Python frame, counted only /// where the stack pointer cannot be read. Everywhere else the native @@ -1372,6 +1373,7 @@ impl VirtualMachine { state, initialized: false, import_bootstrap_complete: false, + startup_error: None, recursion_depth: Cell::new(0), #[cfg(any(miri, target_env = "musl"))] native_recursion_depth: Cell::new(0), @@ -1409,6 +1411,9 @@ impl VirtualMachine { #[cfg(feature = "encodings")] fn import_encodings(&mut self) -> PyResult<()> { self.import("encodings", 0).map_err(|import_err| { + if self.state.config.settings.home.is_some() { + return import_err; + } let rustpythonpath_env = crate::host_env::os::var("RUSTPYTHONPATH").ok(); let pythonpath_env = crate::host_env::os::var("PYTHONPATH").ok(); let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some(); @@ -1668,10 +1673,14 @@ impl VirtualMachine { #[cfg(feature = "encodings")] if _expect_stdlib { if let Err(e) = self.import_encodings() { - eprintln!( - "encodings initialization failed. Only utf-8 encoding will be supported." - ); - self.print_exception(&e); + if self.state.config.settings.home.is_some() { + self.startup_error = Some(e); + } else { + eprintln!( + "encodings initialization failed. Only utf-8 encoding will be supported." + ); + self.print_exception(&e); + } } } else { // Here may not be the best place to give general `path_list` advice, @@ -1686,6 +1695,46 @@ impl VirtualMachine { self.initialized = true; } + /// Report an invalid explicit standard-library configuration to a command-line + /// host. Embedding applications may continue using the bootstrap codecs without calling this. + pub fn check_stdlib_initialization(&self) -> PyResult<()> { + let Some(error) = &self.startup_error else { + return Ok(()); + }; + let settings = &self.state.config.settings; + let paths = &self.state.config.paths; + let repr = |value: &str| { + self.ctx + .new_str(value) + .repr(self) + .unwrap_or_else(|_| "?".to_owned()) + }; + eprintln!("Python path configuration:"); + eprintln!( + " PYTHONHOME = {}", + settings + .home + .as_deref() + .map_or_else(|| "(not set)".to_owned(), repr) + ); + eprintln!(" isolated = {}", u8::from(settings.isolated)); + eprintln!(" environment = {}", u8::from(!settings.ignore_environment)); + eprintln!(" sys.executable = {}", repr(&paths.executable)); + eprintln!(" sys.prefix = {}", repr(&paths.prefix)); + eprintln!(" sys.exec_prefix = {}", repr(&paths.exec_prefix)); + eprintln!(" sys.path = ["); + for path in &paths.module_search_paths { + eprintln!(" {},", repr(path)); + } + eprintln!(" ]"); + // A startup import failure is fatal even if the imported module raised + // SystemExit(0) or SystemExit(None). Keep its exception as the cause, + // without passing its exit code to ordinary user-code finalization. + let startup_error = self.new_runtime_error("Failed to import encodings module"); + startup_error.set_cause(Some(error.clone())); + Err(startup_error) + } + /// Apply configured global lazy imports after startup imports, including site. /// Hosts that perform their own main-module setup can call this at that boundary. pub fn apply_startup_lazy_imports(&self) { diff --git a/crates/vm/src/vm/thread.rs b/crates/vm/src/vm/thread.rs index 48dca83e6..339218d79 100644 --- a/crates/vm/src/vm/thread.rs +++ b/crates/vm/src/vm/thread.rs @@ -1481,6 +1481,7 @@ impl VirtualMachine { state: self.state.clone(), initialized: self.initialized, import_bootstrap_complete: self.import_bootstrap_complete, + startup_error: self.startup_error.clone(), recursion_depth: Cell::new(0), #[cfg(any(miri, target_env = "musl"))] native_recursion_depth: Cell::new(0), diff --git a/src/lib.rs b/src/lib.rs index 7718d1969..ecb1aa055 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -314,6 +314,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> u32 { // pymain_run_python fn pymain_run_python(vm: &VirtualMachine, run_mode: RunMode) -> PyResult { + vm.check_stdlib_initialization()?; let scope = vm.new_scope_with_main()?; // The native warnings state already holds the default filters. Import the