Introduce PyConfig to implement Paths correctly (#6461)

* getpath

* introduce PyConfig

* landmark in getpath
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2025-12-21 08:25:17 +09:00
1 parent 09fa97d1b9
commit 8b3bf558fc
16 files changed
+487 -162

No files matched your search

+1
View File
@@ -43,6 +43,7 @@ numer
orelse
pathconfig
patma
platstdlib
posonlyarg
posonlyargs
prec
+1 -1
View File
@@ -26,7 +26,7 @@ mod syslog {
use libc::{LOG_AUTHPRIV, LOG_CRON, LOG_PERROR};
fn get_argv(vm: &VirtualMachine) -> Option<PyStrRef> {
if let Some(argv) = vm.state.settings.argv.first()
if let Some(argv) = vm.state.config.settings.argv.first()
&& !argv.is_empty()
{
return Some(
+385
View File
@@ -0,0 +1,385 @@
//! Path configuration for RustPython (ref: Modules/getpath.py)
//!
//! This module implements Python path calculation logic following getpath.py.
//! It uses landmark-based search to locate prefix, exec_prefix, and stdlib directories.
//!
//! The main entry point is `init_path_config()` which computes Paths from Settings.
use crate::vm::{Paths, Settings};
use std::env;
use std::path::{Path, PathBuf};
// Platform-specific landmarks (ref: getpath.py PLATFORM CONSTANTS)
#[cfg(not(windows))]
mod platform {
use crate::version;
pub const BUILDDIR_TXT: &str = "pybuilddir.txt";
pub const BUILD_LANDMARK: &str = "Modules/Setup.local";
pub const VENV_LANDMARK: &str = "pyvenv.cfg";
pub const BUILDSTDLIB_LANDMARK: &str = "Lib/os.py";
pub fn stdlib_subdir() -> String {
format!("lib/python{}.{}", version::MAJOR, version::MINOR)
}
pub fn stdlib_landmarks() -> [String; 2] {
let subdir = stdlib_subdir();
[format!("{}/os.py", subdir), format!("{}/os.pyc", subdir)]
}
pub fn platstdlib_landmark() -> String {
format!(
"lib/python{}.{}/lib-dynload",
version::MAJOR,
version::MINOR
)
}
pub fn zip_landmark() -> String {
format!("lib/python{}{}.zip", version::MAJOR, version::MINOR)
}
}
#[cfg(windows)]
mod platform {
use crate::version;
pub const BUILDDIR_TXT: &str = "pybuilddir.txt";
pub const BUILD_LANDMARK: &str = "Modules\\Setup.local";
pub const VENV_LANDMARK: &str = "pyvenv.cfg";
pub const BUILDSTDLIB_LANDMARK: &str = "Lib\\os.py";
pub const STDLIB_SUBDIR: &str = "Lib";
pub fn stdlib_landmarks() -> [String; 2] {
["Lib\\os.py".into(), "Lib\\os.pyc".into()]
}
pub fn platstdlib_landmark() -> String {
"DLLs".into()
}
pub fn zip_landmark() -> String {
format!("python{}{}.zip", version::MAJOR, version::MINOR)
}
}
// Helper functions (ref: getpath.py HELPER FUNCTIONS)
/// Search upward from a directory for landmark files/directories
/// Returns the directory where a landmark was found
fn search_up<P, F>(start: P, landmarks: &[&str], test: F) -> Option<PathBuf>
where
P: AsRef<Path>,
F: Fn(&Path) -> bool,
{
let mut current = start.as_ref().to_path_buf();
loop {
for landmark in landmarks {
let path = current.join(landmark);
if test(&path) {
return Some(current);
}
}
if !current.pop() {
return None;
}
}
}
/// Search upward for a file landmark
fn search_up_file<P: AsRef<Path>>(start: P, landmarks: &[&str]) -> Option<PathBuf> {
search_up(start, landmarks, |p| p.is_file())
}
/// Search upward for a directory landmark
#[cfg(not(windows))]
fn search_up_dir<P: AsRef<Path>>(start: P, landmarks: &[&str]) -> Option<PathBuf> {
search_up(start, landmarks, |p| p.is_dir())
}
// Path computation functions
/// Compute path configuration from Settings
///
/// This function should be called before interpreter initialization.
/// It returns a Paths struct with all computed path values.
pub fn init_path_config(settings: &Settings) -> Paths {
let mut paths = Paths::default();
// Step 0: Get executable path
let executable = get_executable_path();
paths.executable = executable
.as_ref()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default();
let exe_dir = executable
.as_ref()
.and_then(|p| p.parent().map(PathBuf::from));
// Step 1: Check for __PYVENV_LAUNCHER__ environment variable
if let Ok(launcher) = env::var("__PYVENV_LAUNCHER__") {
paths.base_executable = launcher;
}
// Step 2: Check for venv (pyvenv.cfg) and get 'home'
let (venv_prefix, home_dir) = detect_venv(&exe_dir);
let search_dir = home_dir.clone().or(exe_dir.clone());
// Step 3: Check for build directory
let build_prefix = detect_build_directory(&search_dir);
// Step 4: Calculate prefix via landmark search
// When in venv, search_dir is home_dir, so this gives us the base Python's prefix
let calculated_prefix = calculate_prefix(&search_dir, &build_prefix);
// Step 5: Set prefix and base_prefix
if venv_prefix.is_some() {
// In venv: prefix = venv directory, base_prefix = original Python's prefix
paths.prefix = venv_prefix
.as_ref()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| calculated_prefix.clone());
paths.base_prefix = calculated_prefix;
} else {
// Not in venv: prefix == base_prefix
paths.prefix = calculated_prefix.clone();
paths.base_prefix = calculated_prefix;
}
// Step 6: Calculate exec_prefix
paths.exec_prefix = if venv_prefix.is_some() {
// In venv: exec_prefix = prefix (venv directory)
paths.prefix.clone()
} else {
calculate_exec_prefix(&search_dir, &paths.prefix)
};
paths.base_exec_prefix = paths.base_prefix.clone();
// Step 7: Calculate base_executable (if not already set by __PYVENV_LAUNCHER__)
if paths.base_executable.is_empty() {
paths.base_executable = calculate_base_executable(executable.as_ref(), &home_dir);
}
// Step 8: Build module_search_paths
paths.module_search_paths =
build_module_search_paths(settings, &paths.prefix, &paths.exec_prefix);
paths
}
/// Get default prefix value
fn default_prefix() -> String {
std::option_env!("RUSTPYTHON_PREFIX")
.map(String::from)
.unwrap_or_else(|| {
if cfg!(windows) {
"C:".to_owned()
} else {
"/usr/local".to_owned()
}
})
}
/// Detect virtual environment by looking for pyvenv.cfg
/// Returns (venv_prefix, home_dir from pyvenv.cfg)
fn detect_venv(exe_dir: &Option<PathBuf>) -> (Option<PathBuf>, Option<PathBuf>) {
// Try exe_dir/../pyvenv.cfg first (standard venv layout: venv/bin/python)
if let Some(dir) = exe_dir
&& let Some(venv_dir) = dir.parent()
{
let cfg = venv_dir.join(platform::VENV_LANDMARK);
if cfg.exists()
&& let Some(home) = parse_pyvenv_home(&cfg)
{
return (Some(venv_dir.to_path_buf()), Some(PathBuf::from(home)));
}
}
// Try exe_dir/pyvenv.cfg (alternative layout)
if let Some(dir) = exe_dir {
let cfg = dir.join(platform::VENV_LANDMARK);
if cfg.exists()
&& let Some(home) = parse_pyvenv_home(&cfg)
{
return (Some(dir.clone()), Some(PathBuf::from(home)));
}
}
(None, None)
}
/// Detect if running from a build directory
fn detect_build_directory(exe_dir: &Option<PathBuf>) -> Option<PathBuf> {
let dir = exe_dir.as_ref()?;
// Check for pybuilddir.txt (indicates build directory)
if dir.join(platform::BUILDDIR_TXT).exists() {
return Some(dir.clone());
}
// Check for Modules/Setup.local (build landmark)
if dir.join(platform::BUILD_LANDMARK).exists() {
return Some(dir.clone());
}
// Search up for Lib/os.py (build stdlib landmark)
search_up_file(dir, &[platform::BUILDSTDLIB_LANDMARK])
}
/// Calculate prefix by searching for landmarks
fn calculate_prefix(exe_dir: &Option<PathBuf>, build_prefix: &Option<PathBuf>) -> String {
// 1. If build directory detected, use it
if let Some(bp) = build_prefix {
return bp.to_string_lossy().into_owned();
}
if let Some(dir) = exe_dir {
// 2. Search for ZIP landmark
let zip = platform::zip_landmark();
if let Some(prefix) = search_up_file(dir, &[&zip]) {
return prefix.to_string_lossy().into_owned();
}
// 3. Search for stdlib landmarks (os.py)
let landmarks = platform::stdlib_landmarks();
let refs: Vec<&str> = landmarks.iter().map(|s| s.as_str()).collect();
if let Some(prefix) = search_up_file(dir, &refs) {
return prefix.to_string_lossy().into_owned();
}
}
// 4. Fallback to default
default_prefix()
}
/// Calculate exec_prefix
fn calculate_exec_prefix(exe_dir: &Option<PathBuf>, prefix: &str) -> String {
#[cfg(windows)]
{
// Windows: exec_prefix == prefix
let _ = exe_dir; // silence unused warning
prefix.to_owned()
}
#[cfg(not(windows))]
{
// POSIX: search for lib-dynload directory
if let Some(dir) = exe_dir {
let landmark = platform::platstdlib_landmark();
if let Some(exec_prefix) = search_up_dir(dir, &[&landmark]) {
return exec_prefix.to_string_lossy().into_owned();
}
}
// Fallback: same as prefix
prefix.to_owned()
}
}
/// Calculate base_executable
fn calculate_base_executable(executable: Option<&PathBuf>, home_dir: &Option<PathBuf>) -> String {
// If in venv and we have home, construct base_executable from home
if let (Some(exe), Some(home)) = (executable, home_dir)
&& let Some(exe_name) = exe.file_name()
{
let base = home.join(exe_name);
return base.to_string_lossy().into_owned();
}
// Otherwise, base_executable == executable
executable
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_default()
}
/// 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();
// 1. PYTHONPATH/RUSTPYTHONPATH from settings
paths.extend(settings.path_list.iter().cloned());
// 2. ZIP file path
let zip_path = PathBuf::from(prefix).join(platform::zip_landmark());
paths.push(zip_path.to_string_lossy().into_owned());
// 3. stdlib and platstdlib directories
#[cfg(not(windows))]
{
// POSIX: stdlib first, then lib-dynload
let stdlib_dir = PathBuf::from(prefix).join(platform::stdlib_subdir());
paths.push(stdlib_dir.to_string_lossy().into_owned());
let platstdlib = PathBuf::from(exec_prefix).join(platform::platstdlib_landmark());
paths.push(platstdlib.to_string_lossy().into_owned());
}
#[cfg(windows)]
{
// Windows: DLLs first, then Lib
let platstdlib = PathBuf::from(exec_prefix).join(platform::platstdlib_landmark());
paths.push(platstdlib.to_string_lossy().into_owned());
let stdlib_dir = PathBuf::from(prefix).join(platform::STDLIB_SUBDIR);
paths.push(stdlib_dir.to_string_lossy().into_owned());
}
paths
}
/// Get the current executable path
fn get_executable_path() -> Option<PathBuf> {
#[cfg(not(target_arch = "wasm32"))]
{
let exec_arg = env::args_os().next()?;
which::which(exec_arg).ok()
}
#[cfg(target_arch = "wasm32")]
{
let exec_arg = env::args().next()?;
Some(PathBuf::from(exec_arg))
}
}
/// Parse pyvenv.cfg and extract the 'home' key value
fn parse_pyvenv_home(pyvenv_cfg: &Path) -> Option<String> {
let content = std::fs::read_to_string(pyvenv_cfg).ok()?;
for line in content.lines() {
if let Some((key, value)) = line.split_once('=')
&& key.trim().to_lowercase() == "home"
{
return Some(value.trim().to_string());
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_init_path_config() {
let settings = Settings::default();
let paths = init_path_config(&settings);
// Just verify it doesn't panic and returns valid paths
assert!(!paths.prefix.is_empty());
}
#[test]
fn test_search_up() {
// Test with a path that doesn't have any landmarks
let result = search_up_file(std::env::temp_dir(), &["nonexistent_landmark_xyz"]);
assert!(result.is_none());
}
#[test]
fn test_default_prefix() {
let prefix = default_prefix();
assert!(!prefix.is_empty());
}
}
+1 -1
View File
@@ -206,7 +206,7 @@ fn remove_importlib_frames_inner(
// TODO: This function should do nothing on verbose mode.
// TODO: Fix this function after making PyTraceback.next mutable
pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &Py<PyBaseException>) {
if vm.state.settings.verbose != 0 {
if vm.state.config.settings.verbose != 0 {
return;
}
+1
View File
@@ -60,6 +60,7 @@ pub mod exceptions;
pub mod format;
pub mod frame;
pub mod function;
pub mod getpath;
pub mod import;
mod intern;
pub mod iter;
+2 -2
View File
@@ -132,7 +132,7 @@ mod builtins {
let optimize: i32 = args.optimize.map_or(Ok(-1), |v| v.try_to_primitive(vm))?;
let optimize: u8 = if optimize == -1 {
vm.state.settings.optimize
vm.state.config.settings.optimize
} else {
optimize
.try_into()
@@ -1080,7 +1080,7 @@ pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) {
builtins::extend_module(vm, module).unwrap();
let debug_mode: bool = vm.state.settings.optimize == 0;
let debug_mode: bool = vm.state.config.settings.optimize == 0;
// Create dynamic ExceptionGroup with multiple inheritance (BaseExceptionGroup + Exception)
let exception_group = crate::exception_group::exception_group();
+1 -1
View File
@@ -91,7 +91,7 @@ mod _imp {
#[pyattr]
fn check_hash_based_pycs(vm: &VirtualMachine) -> PyStrRef {
vm.ctx
.new_str(vm.state.settings.check_hash_pycs_mode.to_string())
.new_str(vm.state.config.settings.check_hash_pycs_mode.to_string())
}
#[pyfunction]
+3 -1
View File
@@ -2398,7 +2398,9 @@ mod _io {
*data = None;
let encoding = match args.encoding {
None if vm.state.settings.utf8_mode > 0 => identifier_utf8!(vm, utf_8).to_owned(),
None if vm.state.config.settings.utf8_mode > 0 => {
identifier_utf8!(vm, utf_8).to_owned()
}
Some(enc) if enc.as_str() != "locale" => enc,
_ => {
// None without utf8_mode or "locale" encoding
+1 -1
View File
@@ -114,7 +114,7 @@ pub(crate) mod _signal {
module: &Py<crate::builtins::PyModule>,
vm: &VirtualMachine,
) {
if vm.state.settings.install_signal_handlers {
if vm.state.config.settings.install_signal_handlers {
let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
let sig_ign = vm.new_pyobj(SIG_IGN as u8);
+20 -122
View File
@@ -25,7 +25,6 @@ mod sys {
use std::{
env::{self, VarError},
io::Read,
path,
sync::atomic::Ordering,
};
@@ -96,25 +95,20 @@ mod sys {
const DLLHANDLE: usize = 0;
#[pyattr]
const fn default_prefix(_vm: &VirtualMachine) -> &'static str {
// TODO: the windows one doesn't really make sense
if cfg!(windows) { "C:" } else { "/usr/local" }
fn prefix(vm: &VirtualMachine) -> String {
vm.state.config.paths.prefix.clone()
}
#[pyattr]
fn prefix(vm: &VirtualMachine) -> &'static str {
option_env!("RUSTPYTHON_PREFIX").unwrap_or_else(|| default_prefix(vm))
fn base_prefix(vm: &VirtualMachine) -> String {
vm.state.config.paths.base_prefix.clone()
}
#[pyattr]
fn base_prefix(vm: &VirtualMachine) -> &'static str {
option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| prefix(vm))
fn exec_prefix(vm: &VirtualMachine) -> String {
vm.state.config.paths.exec_prefix.clone()
}
#[pyattr]
fn exec_prefix(vm: &VirtualMachine) -> &'static str {
option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| prefix(vm))
}
#[pyattr]
fn base_exec_prefix(vm: &VirtualMachine) -> &'static str {
option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or_else(|| exec_prefix(vm))
fn base_exec_prefix(vm: &VirtualMachine) -> String {
vm.state.config.paths.base_exec_prefix.clone()
}
#[pyattr]
fn platlibdir(_vm: &VirtualMachine) -> &'static str {
@@ -126,6 +120,7 @@ mod sys {
#[pyattr]
fn argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
vm.state
.config
.settings
.argv
.iter()
@@ -162,117 +157,18 @@ mod sys {
}
#[pyattr]
fn _base_executable(vm: &VirtualMachine) -> PyObjectRef {
let ctx = &vm.ctx;
// First check __PYVENV_LAUNCHER__ environment variable
if let Ok(var) = env::var("__PYVENV_LAUNCHER__") {
return ctx.new_str(var).into();
}
// Try to detect if we're running from a venv by looking for pyvenv.cfg
if let Some(base_exe) = get_venv_base_executable() {
return ctx.new_str(base_exe).into();
}
executable(vm)
}
/// Try to find base executable from pyvenv.cfg (see getpath.py)
fn get_venv_base_executable() -> Option<String> {
// TODO: This is a minimal implementation of getpath.py
// To fully support all cases, `getpath.py` should be placed in @crates/vm/Lib/python_builtins/
// Get current executable path
#[cfg(not(target_arch = "wasm32"))]
let exe_path = {
let exec_arg = env::args_os().next()?;
which::which(exec_arg).ok()?
};
#[cfg(target_arch = "wasm32")]
let exe_path = {
let exec_arg = env::args().next()?;
path::PathBuf::from(exec_arg)
};
let exe_dir = exe_path.parent()?;
let exe_name = exe_path.file_name()?;
// Look for pyvenv.cfg in parent directory (typical venv layout: venv/bin/python)
let venv_dir = exe_dir.parent()?;
let pyvenv_cfg = venv_dir.join("pyvenv.cfg");
if !pyvenv_cfg.exists() {
return None;
}
// Parse pyvenv.cfg and extract home directory
let content = std::fs::read_to_string(&pyvenv_cfg).ok()?;
for line in content.lines() {
if let Some((key, value)) = line.split_once('=') {
let key = key.trim().to_lowercase();
let value = value.trim();
if key == "home" {
// First try to resolve symlinks (getpath.py line 373-377)
if let Ok(resolved) = std::fs::canonicalize(&exe_path)
&& resolved != exe_path
{
return Some(resolved.to_string_lossy().into_owned());
}
// Fallback: home_dir + executable_name (getpath.py line 381)
let base_exe = path::Path::new(value).join(exe_name);
return Some(base_exe.to_string_lossy().into_owned());
}
}
}
None
fn _base_executable(vm: &VirtualMachine) -> String {
vm.state.config.paths.base_executable.clone()
}
#[pyattr]
fn dont_write_bytecode(vm: &VirtualMachine) -> bool {
!vm.state.settings.write_bytecode
!vm.state.config.settings.write_bytecode
}
#[pyattr]
fn executable(vm: &VirtualMachine) -> PyObjectRef {
let ctx = &vm.ctx;
#[cfg(not(target_arch = "wasm32"))]
{
if let Some(exec_path) = env::args_os().next()
&& let Ok(path) = which::which(exec_path)
{
return ctx
.new_str(
path.into_os_string()
.into_string()
.unwrap_or_else(|p| p.to_string_lossy().into_owned()),
)
.into();
}
}
if let Some(exec_path) = env::args().next() {
let path = path::Path::new(&exec_path);
if !path.exists() {
return ctx.new_str(ascii!("")).into();
}
if path.is_absolute() {
return ctx.new_str(exec_path).into();
}
if let Ok(dir) = env::current_dir()
&& let Ok(dir) = dir.into_os_string().into_string()
{
return ctx
.new_str(format!(
"{}/{}",
dir,
exec_path.strip_prefix("./").unwrap_or(&exec_path)
))
.into();
}
}
ctx.none()
fn executable(vm: &VirtualMachine) -> String {
vm.state.config.paths.executable.clone()
}
#[pyattr]
@@ -312,8 +208,9 @@ mod sys {
#[pyattr]
fn path(vm: &VirtualMachine) -> Vec<PyObjectRef> {
vm.state
.settings
.path_list
.config
.paths
.module_search_paths
.iter()
.map(|path| vm.ctx.new_str(path.clone()).into())
.collect()
@@ -350,7 +247,7 @@ mod sys {
fn _xoptions(vm: &VirtualMachine) -> PyDictRef {
let ctx = &vm.ctx;
let xopts = ctx.new_dict();
for (key, value) in &vm.state.settings.xoptions {
for (key, value) in &vm.state.config.settings.xoptions {
let value = value.as_ref().map_or_else(
|| ctx.new_bool(true).into(),
|s| ctx.new_str(s.clone()).into(),
@@ -363,6 +260,7 @@ mod sys {
#[pyattr]
fn warnoptions(vm: &VirtualMachine) -> Vec<PyObjectRef> {
vm.state
.config
.settings
.warnoptions
.iter()
@@ -507,7 +405,7 @@ mod sys {
#[pyattr]
fn flags(vm: &VirtualMachine) -> PyTupleRef {
PyFlags::from_data(FlagsData::from_settings(&vm.state.settings), vm)
PyFlags::from_data(FlagsData::from_settings(&vm.state.config.settings), vm)
}
#[pyattr]
+1 -1
View File
@@ -38,7 +38,7 @@ impl VirtualMachine {
}
// TODO: check if this is proper place
if !self.state.settings.safe_path {
if !self.state.config.settings.safe_path {
let dir = std::path::Path::new(path)
.parent()
.unwrap()
+7 -3
View File
@@ -1,5 +1,5 @@
use super::{Context, VirtualMachine, setting::Settings, thread};
use crate::{PyResult, stdlib::atexit, vm::PyBaseExceptionRef};
use super::{Context, PyConfig, VirtualMachine, setting::Settings, thread};
use crate::{PyResult, getpath, stdlib::atexit, vm::PyBaseExceptionRef};
use std::sync::atomic::Ordering;
/// The general interface for the VM
@@ -47,10 +47,14 @@ impl Interpreter {
where
F: FnOnce(&mut VirtualMachine),
{
// Compute path configuration from settings
let paths = getpath::init_path_config(&settings);
let config = PyConfig::new(settings, paths);
let ctx = Context::genesis();
crate::types::TypeZoo::extend(ctx);
crate::exceptions::ExceptionZoo::extend(ctx);
let mut vm = VirtualMachine::new(settings, ctx.clone());
let mut vm = VirtualMachine::new(config, ctx.clone());
init(&mut vm);
vm.initialize();
Self { vm }
+14 -14
View File
@@ -50,7 +50,7 @@ use std::{
pub use context::Context;
pub use interpreter::Interpreter;
pub(crate) use method::PyMethod;
pub use setting::{CheckHashPycsMode, Settings};
pub use setting::{CheckHashPycsMode, Paths, PyConfig, Settings};
pub const MAX_MEMORY_SIZE: usize = isize::MAX as usize;
@@ -87,7 +87,7 @@ struct ExceptionStack {
}
pub struct PyGlobalState {
pub settings: Settings,
pub config: PyConfig,
pub module_inits: stdlib::StdlibMap,
pub frozen: HashMap<&'static str, FrozenModule, ahash::RandomState>,
pub stacksize: AtomicCell<usize>,
@@ -114,7 +114,7 @@ pub fn process_hash_secret_seed() -> u32 {
impl VirtualMachine {
/// Create a new `VirtualMachine` structure.
fn new(settings: Settings, ctx: PyRc<Context>) -> Self {
fn new(config: PyConfig, ctx: PyRc<Context>) -> Self {
flame_guard!("new VirtualMachine");
// make a new module without access to the vm; doesn't
@@ -141,7 +141,7 @@ impl VirtualMachine {
let module_inits = stdlib::get_module_inits();
let seed = match settings.hash_seed {
let seed = match config.settings.hash_seed {
Some(seed) => seed,
None => process_hash_secret_seed(),
};
@@ -151,7 +151,7 @@ impl VirtualMachine {
let warnings = WarningsState::init_state(&ctx);
let int_max_str_digits = AtomicCell::new(match settings.int_max_str_digits {
let int_max_str_digits = AtomicCell::new(match config.settings.int_max_str_digits {
-1 => 4300,
other => other,
} as usize);
@@ -171,7 +171,7 @@ impl VirtualMachine {
signal_rx: None,
repr_guards: RefCell::default(),
state: PyRc::new(PyGlobalState {
settings,
config,
module_inits,
frozen: HashMap::default(),
stacksize: AtomicCell::new(0),
@@ -227,7 +227,7 @@ impl VirtualMachine {
let rustpythonpath_env = std::env::var("RUSTPYTHONPATH").ok();
let pythonpath_env = std::env::var("PYTHONPATH").ok();
let env_set = rustpythonpath_env.as_ref().is_some() || pythonpath_env.as_ref().is_some();
let path_contains_env = self.state.settings.path_list.iter().any(|s| {
let path_contains_env = self.state.config.paths.module_search_paths.iter().any(|s| {
Some(s.as_str()) == rustpythonpath_env.as_deref() || Some(s.as_str()) == pythonpath_env.as_deref()
});
@@ -238,7 +238,7 @@ impl VirtualMachine {
} else if path_contains_env {
"RUSTPYTHONPATH or PYTHONPATH is set, but it doesn't contain the encodings library. If you are customizing the RustPython vm/interpreter, try adding the stdlib directory to the path. If you are developing the RustPython interpreter, it might be a bug during development."
} else {
"RUSTPYTHONPATH or PYTHONPATH is set, but it wasn't loaded to `Settings::path_list`. If you are going to customize the RustPython vm/interpreter, those environment variables are not loaded in the Settings struct by default. Please try creating a customized instance of the Settings struct. If you are developing the RustPython interpreter, it might be a bug during development."
"RUSTPYTHONPATH or PYTHONPATH is set, but it wasn't loaded to `PyConfig::paths::module_search_paths`. If you are going to customize the RustPython vm/interpreter, those environment variables are not loaded in the Settings struct by default. Please try creating a customized instance of the Settings struct. If you are developing the RustPython interpreter, it might be a bug during development."
};
let mut msg = format!(
@@ -303,7 +303,7 @@ impl VirtualMachine {
let io = import::import_builtin(self, "_io")?;
#[cfg(feature = "stdio")]
let make_stdio = |name, fd, write| {
let buffered_stdio = self.state.settings.buffered_stdio;
let buffered_stdio = self.state.config.settings.buffered_stdio;
let unbuffered = write && !buffered_stdio;
let buf = crate::stdlib::io::open(
self.ctx.new_int(fd).into(),
@@ -364,7 +364,7 @@ impl VirtualMachine {
let res = essential_init();
let importlib = self.expect_pyresult(res, "essential initialization failed");
if self.state.settings.allow_external_library
if self.state.config.settings.allow_external_library
&& cfg!(feature = "rustpython-compiler")
&& let Err(e) = import::init_importlib_package(self, importlib)
{
@@ -374,8 +374,8 @@ impl VirtualMachine {
self.print_exception(e);
}
let _expect_stdlib =
cfg!(feature = "freeze-stdlib") || !self.state.settings.path_list.is_empty();
let _expect_stdlib = cfg!(feature = "freeze-stdlib")
|| !self.state.config.paths.module_search_paths.is_empty();
#[cfg(feature = "encodings")]
if _expect_stdlib {
@@ -389,7 +389,7 @@ impl VirtualMachine {
// Here may not be the best place to give general `path_list` advice,
// but bare rustpython_vm::VirtualMachine users skipped proper settings must hit here while properly setup vm never enters here.
eprintln!(
"feature `encodings` is enabled but `settings.path_list` is empty. \
"feature `encodings` is enabled but `paths.module_search_paths` is empty. \
Please add the library path to `settings.path_list`. If you intended to disable the entire standard library (including the `encodings` feature), please also make sure to disable the `encodings` feature.\n\
Tip: You may also want to add `\"\"` to `settings.path_list` in order to enable importing from the current working directory."
);
@@ -505,7 +505,7 @@ impl VirtualMachine {
#[cfg(feature = "rustpython-codegen")]
pub fn compile_opts(&self) -> crate::compiler::CompileOpts {
crate::compiler::CompileOpts {
optimize: self.state.settings.optimize,
optimize: self.state.config.settings.optimize,
}
}
+34 -2
View File
@@ -1,8 +1,40 @@
#[cfg(feature = "flame-it")]
use std::ffi::OsString;
/// Struct containing all kind of settings for the python vm.
/// Mostly `PyConfig` in CPython.
/// Path configuration computed at runtime (like PyConfig path outputs)
#[derive(Debug, Clone, Default)]
pub struct Paths {
/// sys.executable
pub executable: String,
/// sys._base_executable (original interpreter in venv)
pub base_executable: String,
/// sys.prefix
pub prefix: String,
/// sys.base_prefix
pub base_prefix: String,
/// sys.exec_prefix
pub exec_prefix: String,
/// sys.base_exec_prefix
pub base_exec_prefix: String,
/// Computed module_search_paths (complete sys.path)
pub module_search_paths: Vec<String>,
}
/// Combined configuration: user settings + computed paths
/// CPython directly exposes every fields under both of them.
/// We separate them to maintain better ownership discipline.
pub struct PyConfig {
pub settings: Settings,
pub paths: Paths,
}
impl PyConfig {
pub fn new(settings: Settings, paths: Paths) -> Self {
Self { settings, paths }
}
}
/// User-configurable settings for the python vm.
#[non_exhaustive]
pub struct Settings {
/// -I
+9 -7
View File
@@ -104,23 +104,25 @@ pub fn init_stdlib(vm: &mut VirtualMachine) {
use rustpython_vm::common::rc::PyRc;
let state = PyRc::get_mut(&mut vm.state).unwrap();
let settings = &mut state.settings;
let path_list = std::mem::take(&mut settings.path_list);
// Collect additional paths to add
let mut additional_paths = Vec::new();
// BUILDTIME_RUSTPYTHONPATH should be set when distributing
if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") {
settings.path_list.extend(
additional_paths.extend(
crate::settings::split_paths(paths)
.map(|path| path.into_os_string().into_string().unwrap()),
)
} else {
#[cfg(feature = "rustpython-pylib")]
settings
.path_list
.push(rustpython_pylib::LIB_PATH.to_owned())
additional_paths.push(rustpython_pylib::LIB_PATH.to_owned())
}
settings.path_list.extend(path_list);
// Add to both path_list (for compatibility) and module_search_paths (for sys.path)
// Insert at the beginning so stdlib comes before user paths
for path in additional_paths.into_iter().rev() {
state.config.paths.module_search_paths.insert(0, path);
}
}
}
+6 -6
View File
@@ -169,7 +169,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
let scope = setup_main_module(vm)?;
if !vm.state.settings.safe_path {
if !vm.state.config.settings.safe_path {
// TODO: The prepending path depends on running mode
// See https://docs.python.org/3/using/cmdline.html#cmdoption-P
vm.run_code_string(
@@ -189,7 +189,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
// Enable faulthandler if -X faulthandler, PYTHONFAULTHANDLER or -X dev is set
// _PyFaulthandler_Init()
if vm.state.settings.faulthandler {
if vm.state.config.settings.faulthandler {
let _ = vm.run_code_string(
vm.new_scope_with_builtins(),
"import faulthandler; faulthandler.enable()",
@@ -198,8 +198,8 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
}
let is_repl = matches!(run_mode, RunMode::Repl);
if !vm.state.settings.quiet
&& (vm.state.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal()))
if !vm.state.config.settings.quiet
&& (vm.state.config.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal()))
{
eprintln!(
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
@@ -232,7 +232,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
}
RunMode::Repl => Ok(()),
};
if is_repl || vm.state.settings.inspect {
if is_repl || vm.state.config.settings.inspect {
shell::run_shell(vm, scope)?;
} else {
res?;
@@ -241,7 +241,7 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
#[cfg(feature = "flame-it")]
{
main_guard.end();
if let Err(e) = write_profile(&vm.state.as_ref().settings) {
if let Err(e) = write_profile(&vm.state.as_ref().config.settings) {
error!("Error writing profile information: {}", e);
}
}