Honor Python 3.15 frozen-module startup configuration (#9202)

* Honor frozen module startup selection

Assisted-by: OpenAI Codex:model version unavailable

* Match frozen option configuration diagnostics

Assisted-by: OpenAI Codex:model version unavailable

* Preserve frozen option initialization precedence

Validate the first occurrence, then apply the final value as CPython does after getpath configuration normalization.

Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-19 20:26:02 +09:00
1 parent dfa3a1ae66
commit c4681299c2
6 files changed
+57 -6

No files matched your search

+6 -2
View File
@@ -180,10 +180,14 @@ fn find_frozen(name: &str, vm: &VirtualMachine) -> Result<FrozenModule, FrozenEr
return Ok(frozen);
}
// use_frozen(): override > 0 → true, override < 0 → false, 0 → default (true)
// use_frozen(): override > 0 → true, override < 0 → false, 0 → startup setting
// When disabled, non-bootstrap modules are simply not found (same as look_up_frozen)
let override_val = vm.state.override_frozen_modules.load();
if override_val < 0 {
if override_val < 0
|| (override_val == 0
&& vm.import_bootstrap_complete
&& !vm.state.config.settings.use_frozen_modules)
{
return Err(FrozenError::NotFound);
}
+4 -1
View File
@@ -110,6 +110,7 @@ pub struct VirtualMachine {
pub(crate) lazy_imports_resolving: RefCell<HashSet<usize>>,
pub state: PyRc<PyGlobalState>,
pub initialized: bool,
pub(crate) import_bootstrap_complete: bool,
recursion_depth: Cell<usize>,
/// Depth of native recursion that pushes no Python frame, counted only
/// where the stack pointer cannot be read. Everywhere else the native
@@ -1370,6 +1371,7 @@ impl VirtualMachine {
lazy_imports_resolving: RefCell::default(),
state,
initialized: false,
import_bootstrap_complete: false,
recursion_depth: Cell::new(0),
#[cfg(any(miri, target_env = "musl"))]
native_recursion_depth: Cell::new(0),
@@ -1450,7 +1452,7 @@ impl VirtualMachine {
// Use dotted names when freeze-stdlib is enabled (modules come from Lib/encodings/),
// otherwise use underscored names (modules come from core_modules/).
let (ascii_module_name, utf8_module_name, latin1_module_name) =
if cfg!(feature = "freeze-stdlib") {
if cfg!(feature = "freeze-stdlib") && self.state.config.settings.use_frozen_modules {
("encodings.ascii", "encodings.utf_8", "encodings.latin_1")
} else {
("encodings_ascii", "encodings_utf_8", "encodings_latin_1")
@@ -1644,6 +1646,7 @@ impl VirtualMachine {
let res = essential_init();
let importlib = self.expect_pyresult(res, "essential initialization failed");
self.import_bootstrap_complete = true;
#[cfg(feature = "host_env")]
if self.state.config.settings.allow_external_library
+3 -1
View File
@@ -139,7 +139,8 @@ pub struct Settings {
/// --check-hash-based-pycs
pub check_hash_pycs_mode: CheckHashPycsMode,
// int use_frozen_modules;
/// -X frozen_modules=on|off (bootstrap modules remain available).
pub use_frozen_modules: bool,
/// -P
pub safe_path: bool,
@@ -204,6 +205,7 @@ impl Default for Settings {
install_signal_handlers: true,
user_site_directory: true,
import_site: true,
use_frozen_modules: true,
ignore_environment: false,
verbose: 0,
quiet: false,
+1
View File
@@ -1480,6 +1480,7 @@ impl VirtualMachine {
lazy_imports_resolving: RefCell::default(),
state: self.state.clone(),
initialized: self.initialized,
import_bootstrap_complete: self.import_bootstrap_complete,
recursion_depth: Cell::new(0),
#[cfg(any(miri, target_env = "musl"))]
native_recursion_depth: Cell::new(0),
+7 -2
View File
@@ -55,6 +55,11 @@ fn install_default_tls_provider(_vm: &mut crate::VirtualMachine) {
fn set_frozen_stdlib_dir(vm: &mut crate::VirtualMachine) {
use rustpython_vm::common::rc::PyRc;
if !vm.state.config.settings.use_frozen_modules {
setup_dynamic_stdlib(vm);
return;
}
if use_build_stdlib(vm) {
let state = PyRc::get_mut(&mut vm.state).unwrap();
state.config.paths.stdlib_dir =
@@ -63,7 +68,7 @@ fn set_frozen_stdlib_dir(vm: &mut crate::VirtualMachine) {
}
/// Setup dynamic standard library loading from filesystem
#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))]
#[cfg(feature = "stdlib")]
fn setup_dynamic_stdlib(vm: &mut crate::VirtualMachine) {
use rustpython_vm::common::rc::PyRc;
@@ -117,7 +122,7 @@ fn canonical_stdlib_path(path: String) -> String {
}
/// Collect standard library paths from build-time configuration
#[cfg(all(feature = "stdlib", not(feature = "freeze-stdlib")))]
#[cfg(feature = "stdlib")]
fn collect_stdlib_paths() -> Vec<String> {
// BUILDTIME_RUSTPYTHONPATH should be set when distributing
if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") {
+36
View File
@@ -313,9 +313,15 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
settings.import_time = parse_import_time(value.to_str(), "PYTHONPROFILEIMPORTTIME");
}
if let Some(value) = get_env("PYTHON_FROZEN_MODULES").filter(|value| !value.is_empty()) {
settings.use_frozen_modules =
parse_frozen_modules(Some(&value.to_string_lossy()), "PYTHON_FROZEN_MODULES");
}
// CPython uses the first occurrence of these -X options.
let mut lazy_imports_option_seen = false;
let mut import_time_option_seen = false;
let mut frozen_modules_option_seen = false;
let xopts = args.implementation_option.into_iter().map(|s| {
let (name, value) = match s.split_once('=') {
Some((name, value)) => (name.to_owned(), Some(value)),
@@ -327,6 +333,11 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
settings.import_time = parse_import_time(value, "-X importtime");
import_time_option_seen = true;
}
"frozen_modules" if !frozen_modules_option_seen => {
settings.use_frozen_modules =
parse_frozen_modules(value, "option -X frozen_modules");
frozen_modules_option_seen = true;
}
"faulthandler" => settings.faulthandler = true,
"warn_default_encoding" => settings.warn_default_encoding = true,
"utf8" => {
@@ -398,6 +409,18 @@ pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> {
});
settings.xoptions.extend(xopts);
// CPython validates the first frozen_modules option before initialization,
// then getpath's config dictionary round-trip keeps the last occurrence.
if let Some((_, value)) = settings
.xoptions
.iter()
.rev()
.find(|(name, _)| name == "frozen_modules")
{
settings.use_frozen_modules =
parse_frozen_modules(value.as_deref(), "option -X frozen_modules");
}
// Resolve utf8_mode if not explicitly set by PYTHONUTF8 or -X utf8.
// Default to UTF-8 mode since RustPython's locale encoding detection
// is incomplete. Users can set PYTHONUTF8=0 or -X utf8=0 to disable.
@@ -513,6 +536,19 @@ fn parse_cpu_count(value: Option<&str>) -> Result<Option<NonZeroI32>, ()> {
}
}
fn parse_frozen_modules(value: Option<&str>, source: &str) -> bool {
match value {
None | Some("" | "on") => true,
Some("off") => false,
_ => {
eprintln!(
"Fatal Python error: config_init_import: bad value for {source} (expected \"on\" or \"off\")"
);
std::process::exit(1);
}
}
}
/// = config_init_import_time
fn parse_import_time(value: Option<&str>, source: &str) -> u8 {
let value = value