22//!
33//! This module provides path calculation logic but implemented directly in Rust.
44//!
5- //! The main entry point is `init_path_config()` which should be called
6- //! before interpreter initialization to populate Settings with path info.
5+ //! The main entry point is `init_path_config()` which computes Paths from Settings.
76
87use std:: env;
98use std:: path:: { Path , PathBuf } ;
109
1110use crate :: version;
12- use crate :: vm:: Settings ;
11+ use crate :: vm:: { Paths , Settings } ;
1312
14- /// Initialize path configuration in Settings (like getpath.py)
13+ /// Compute path configuration from Settings
1514///
1615/// This function should be called before interpreter initialization.
17- /// It computes executable, base_executable, prefix, and module_search_paths.
18- pub fn init_path_config ( settings : & mut Settings ) {
19- // Skip if already configured
20- if settings. module_search_paths_set {
21- return ;
22- }
23-
16+ /// It returns a Paths struct with all computed path values.
17+ pub fn init_path_config ( settings : & Settings ) -> Paths {
2418 // 1. Compute executable path
25- if settings. executable . is_none ( ) {
26- settings. executable = get_executable_path ( ) . map ( |p| p. to_string_lossy ( ) . into_owned ( ) ) ;
27- }
28-
29- let exe_path = settings
30- . executable
31- . as_ref ( )
32- . map ( PathBuf :: from)
33- . or_else ( get_executable_path) ;
34-
35- // 2. Compute base_executable (for venv support)
36- if settings. base_executable . is_none ( ) {
37- settings. base_executable = compute_base_executable ( exe_path. as_deref ( ) ) ;
38- }
39-
40- // 3. Compute prefix paths (with fallbacks to ensure all values are set)
41- let ( prefix, base_prefix) = compute_prefixes ( exe_path. as_deref ( ) ) ;
42- let default_prefix = || {
43- std:: option_env!( "RUSTPYTHON_PREFIX" )
44- . map ( String :: from)
45- . unwrap_or_else ( || if cfg ! ( windows) { "C:" } else { "/usr/local" } . to_owned ( ) )
19+ let executable = get_executable_path ( )
20+ . map ( |p| p. to_string_lossy ( ) . into_owned ( ) )
21+ . unwrap_or_default ( ) ;
22+
23+ let exe_path = if executable. is_empty ( ) {
24+ None
25+ } else {
26+ Some ( PathBuf :: from ( & executable) )
4627 } ;
4728
48- if settings. prefix . is_none ( ) {
49- settings. prefix = Some ( prefix. clone ( ) . unwrap_or_else ( default_prefix) ) ;
50- }
51- if settings. base_prefix . is_none ( ) {
52- settings. base_prefix = Some (
53- base_prefix
54- . clone ( )
55- . or_else ( || prefix. clone ( ) )
56- . unwrap_or_else ( default_prefix) ,
57- ) ;
58- }
59- if settings. exec_prefix . is_none ( ) {
60- settings. exec_prefix = settings. prefix . clone ( ) ;
61- }
62- if settings. base_exec_prefix . is_none ( ) {
63- settings. base_exec_prefix = settings. base_prefix . clone ( ) ;
29+ // 2. Compute base_executable (for venv support)
30+ let base_executable =
31+ compute_base_executable ( exe_path. as_deref ( ) ) . unwrap_or_else ( || executable. clone ( ) ) ;
32+
33+ // 3. Compute prefix paths
34+ let ( prefix_opt, base_prefix_opt) = compute_prefixes ( exe_path. as_deref ( ) ) ;
35+ let prefix = prefix_opt. unwrap_or_else ( default_prefix) ;
36+ let base_prefix = base_prefix_opt. unwrap_or_else ( || prefix. clone ( ) ) ;
37+
38+ // 4. Build module_search_paths
39+ let module_search_paths = compute_module_search_paths ( settings, & base_prefix) ;
40+
41+ Paths {
42+ executable,
43+ base_executable,
44+ prefix : prefix. clone ( ) ,
45+ base_prefix : base_prefix. clone ( ) ,
46+ exec_prefix : prefix,
47+ base_exec_prefix : base_prefix,
48+ module_search_paths,
6449 }
50+ }
6551
66- // 4. Build module_search_paths (use settings.base_prefix which is now guaranteed to be set)
67- settings. module_search_paths =
68- compute_module_search_paths ( settings, settings. base_prefix . as_deref ( ) ) ;
69- settings. module_search_paths_set = true ;
52+ /// Get default prefix value
53+ fn default_prefix ( ) -> String {
54+ std:: option_env!( "RUSTPYTHON_PREFIX" )
55+ . map ( String :: from)
56+ . unwrap_or_else ( || {
57+ if cfg ! ( windows) {
58+ "C:" . to_owned ( )
59+ } else {
60+ "/usr/local" . to_owned ( )
61+ }
62+ } )
7063}
7164
7265/// Compute base_executable from executable path
@@ -84,10 +77,6 @@ fn compute_base_executable(exe_path: Option<&Path>) -> Option<String> {
8477 let home_path = PathBuf :: from ( & venv_home) ;
8578 let exe_name = exe_path. file_name ( ) ?;
8679 let base_exe = home_path. join ( exe_name) ;
87- if base_exe. exists ( ) {
88- return Some ( base_exe. to_string_lossy ( ) . into_owned ( ) ) ;
89- }
90- // Fallback: just return the home directory path with exe name
9180 return Some ( base_exe. to_string_lossy ( ) . into_owned ( ) ) ;
9281 }
9382
@@ -100,10 +89,8 @@ fn compute_prefixes(exe_path: Option<&Path>) -> (Option<String>, Option<String>)
10089 let Some ( exe_path) = exe_path else {
10190 return ( None , None ) ;
10291 } ;
103-
104- let exe_dir = match exe_path. parent ( ) {
105- Some ( d) => d,
106- None => return ( None , None ) ,
92+ let Some ( exe_dir) = exe_path. parent ( ) else {
93+ return ( None , None ) ;
10794 } ;
10895
10996 // Check if we're in a venv
@@ -124,60 +111,19 @@ fn compute_prefixes(exe_path: Option<&Path>) -> (Option<String>, Option<String>)
124111}
125112
126113/// Build the complete module_search_paths (sys.path)
127- fn compute_module_search_paths ( settings : & Settings , base_prefix : Option < & str > ) -> Vec < String > {
114+ fn compute_module_search_paths ( settings : & Settings , base_prefix : & str ) -> Vec < String > {
128115 let mut paths = Vec :: new ( ) ;
129116
130117 // 1. Add paths from path_list (PYTHONPATH/RUSTPYTHONPATH)
131118 paths. extend ( settings. path_list . iter ( ) . cloned ( ) ) ;
132119
133120 // 2. Add zip stdlib path
134- if let Some ( base_prefix) = base_prefix {
135- let platlibdir = "lib" ;
136- let zip_name = format ! ( "rustpython{}{}" , version:: MAJOR , version:: MINOR ) ;
137- let zip_path = PathBuf :: from ( base_prefix) . join ( platlibdir) . join ( & zip_name) ;
138- paths. push ( zip_path. to_string_lossy ( ) . into_owned ( ) ) ;
139- }
140-
141- paths
142- }
143-
144- /// Get the zip stdlib path to add to sys.path
145- ///
146- /// Returns a path like `/usr/local/lib/rustpython313` or
147- /// `/path/to/venv/lib/rustpython313` for virtual environments.
148- pub fn get_zip_stdlib_path ( ) -> Option < String > {
149- // ZIP_LANDMARK pattern: {platlibdir}/{impl_name}{VERSION_MAJOR}{VERSION_MINOR}
150121 let platlibdir = "lib" ;
151122 let zip_name = format ! ( "rustpython{}{}" , version:: MAJOR , version:: MINOR ) ;
123+ let zip_path = PathBuf :: from ( base_prefix) . join ( platlibdir) . join ( & zip_name) ;
124+ paths. push ( zip_path. to_string_lossy ( ) . into_owned ( ) ) ;
152125
153- let base_prefix = get_base_prefix ( ) ?;
154- let zip_path = base_prefix. join ( platlibdir) . join ( & zip_name) ;
155-
156- Some ( zip_path. to_string_lossy ( ) . into_owned ( ) )
157- }
158-
159- /// Get the base prefix directory
160- ///
161- /// For installed Python: parent of the bin directory
162- /// For venv: the 'home' value from pyvenv.cfg
163- fn get_base_prefix ( ) -> Option < PathBuf > {
164- let exe_path = get_executable_path ( ) ?;
165- let exe_dir = exe_path. parent ( ) ?;
166-
167- // Check if we're in a venv by looking for pyvenv.cfg
168- if let Some ( venv_home) = get_venv_home ( & exe_path) {
169- // venv_home is the directory containing the base Python
170- // Go up one level to get the prefix (e.g., /usr/local from /usr/local/bin)
171- let home_path = PathBuf :: from ( & venv_home) ;
172- if let Some ( parent) = home_path. parent ( ) {
173- return Some ( parent. to_path_buf ( ) ) ;
174- }
175- return Some ( home_path) ;
176- }
177-
178- // Not in venv: go up from bin/ to get prefix
179- // e.g., /usr/local/bin/rustpython -> /usr/local
180- exe_dir. parent ( ) . map ( |p| p. to_path_buf ( ) )
126+ paths
181127}
182128
183129/// Get the current executable path
@@ -230,8 +176,10 @@ mod tests {
230176 use super :: * ;
231177
232178 #[ test]
233- fn test_zip_stdlib_path_format ( ) {
234- // Just verify it returns something and doesn't panic
235- let _path = get_zip_stdlib_path ( ) ;
179+ fn test_init_path_config ( ) {
180+ let settings = Settings :: default ( ) ;
181+ let paths = init_path_config ( & settings) ;
182+ // Just verify it doesn't panic and returns valid paths
183+ assert ! ( !paths. prefix. is_empty( ) ) ;
236184 }
237185}
0 commit comments