1
- use miette:: { miette, IntoDiagnostic } ;
2
- use runfiles:: Runfiles ; // Depended on out of rules_rust
3
- use std:: env:: { self , args, var} ;
1
+ use miette:: IntoDiagnostic ;
2
+ use runfiles:: Runfiles ;
3
+ // Depended on out of rules_rust
4
+ use std:: env;
5
+ use std:: ffi:: OsStr ;
4
6
use std:: fs;
5
- use std:: io:: { self , BufRead } ;
6
7
use std:: os:: unix:: process:: CommandExt ;
7
8
use std:: path:: { Path , PathBuf } ;
8
9
use std:: process:: Command ;
9
10
10
11
const PYVENV : & str = "pyvenv.cfg" ;
11
12
12
- fn find_venv_root ( ) -> miette:: Result < ( PathBuf , PathBuf ) > {
13
- if let Ok ( it) = var ( "VIRTUAL_ENV" ) {
13
+ fn find_venv_root ( exec_name : impl AsRef < Path > ) -> miette:: Result < ( PathBuf , PathBuf ) > {
14
+ if let Ok ( it) = env :: var ( "VIRTUAL_ENV" ) {
14
15
let root = PathBuf :: from ( it) ;
15
16
let cfg = root. join ( PYVENV ) ;
16
17
if cfg. exists ( ) {
@@ -20,17 +21,15 @@ fn find_venv_root() -> miette::Result<(PathBuf, PathBuf)> {
20
21
}
21
22
}
22
23
23
- if let Some ( this) = args ( ) . next ( ) {
24
- let this = PathBuf :: from ( this) ;
25
- if let Some ( parent) = this. parent ( ) . and_then ( |it| it. parent ( ) ) {
26
- let cfg = parent. join ( PYVENV ) ;
27
- if cfg. exists ( ) {
28
- return Ok ( ( parent. to_path_buf ( ) , cfg) ) ;
29
- }
24
+ let exec_name = exec_name. as_ref ( ) . to_owned ( ) ;
25
+ if let Some ( parent) = exec_name. parent ( ) . and_then ( |it| it. parent ( ) ) {
26
+ let cfg = parent. join ( PYVENV ) ;
27
+ if cfg. exists ( ) {
28
+ return Ok ( ( parent. to_path_buf ( ) , cfg) ) ;
30
29
}
31
30
}
32
31
33
- return Err ( miette ! ( "Unable to identify a virtualenv home!" ) ) ;
32
+ miette:: bail !( "Unable to identify a virtualenv home!" ) ;
34
33
}
35
34
36
35
#[ derive( Debug ) ]
@@ -40,7 +39,7 @@ enum InterpreterConfig {
40
39
}
41
40
42
41
#[ derive( Debug ) ]
43
- #[ allow ( unused_attributes ) ]
42
+ #[ expect ( dead_code ) ]
44
43
struct PyCfg {
45
44
root : PathBuf ,
46
45
cfg : PathBuf ,
@@ -50,37 +49,32 @@ struct PyCfg {
50
49
}
51
50
52
51
fn parse_venv_cfg ( venv_root : & Path , cfg_path : & Path ) -> miette:: Result < PyCfg > {
53
- let file = fs:: File :: open ( cfg_path) . into_diagnostic ( ) ?;
54
-
55
52
let mut version: Option < String > = None ;
56
53
let mut bazel_interpreter: Option < String > = None ;
57
54
let mut bazel_repo: Option < String > = None ;
58
55
let mut user_site: Option < bool > = None ;
59
56
60
57
// FIXME: Errors possible here?
61
- let reader = io:: BufReader :: new ( file) ;
62
-
63
- for line in reader. lines ( ) {
64
- let line = line. into_diagnostic ( ) ?;
65
- if let Some ( ( key, value) ) = line. split_once ( "=" ) {
66
- let key = key. trim ( ) ;
67
- let value = value. trim ( ) ;
68
- match key {
69
- "version_info" => {
70
- version = Some ( value. to_string ( ) ) ;
71
- }
72
- "aspect-runfiles-interpreter" => {
73
- bazel_interpreter = Some ( value. to_string ( ) ) ;
74
- }
75
- "aspect-runfiles-repo" => {
76
- bazel_repo = Some ( value. to_string ( ) ) ;
77
- }
78
- "aspect-include-user-site-packages" => {
79
- user_site = Some ( value == "true" ) ;
80
- }
81
- // We don't care about other keys
82
- & _ => { }
58
+ let cfg_file = fs:: read_to_string ( cfg_path) . into_diagnostic ( ) ?;
59
+
60
+ for ( key, value) in cfg_file. lines ( ) . flat_map ( |s| s. split_once ( "=" ) ) {
61
+ let key = key. trim ( ) ;
62
+ let value = value. trim ( ) ;
63
+ match key {
64
+ "version_info" => {
65
+ version = Some ( value. to_string ( ) ) ;
66
+ }
67
+ "aspect-runfiles-interpreter" => {
68
+ bazel_interpreter = Some ( value. to_string ( ) ) ;
69
+ }
70
+ "aspect-runfiles-repo" => {
71
+ bazel_repo = Some ( value. to_string ( ) ) ;
72
+ }
73
+ "aspect-include-user-site-packages" => {
74
+ user_site = value. parse ( ) . ok ( ) ;
83
75
}
76
+ // We don't care about other keys
77
+ _ => continue ,
84
78
}
85
79
}
86
80
@@ -104,16 +98,14 @@ fn parse_venv_cfg(venv_root: &Path, cfg_path: &Path) -> miette::Result<PyCfg> {
104
98
} ,
105
99
user_site : user_site. expect ( "User site flag not set!" ) ,
106
100
} ) ,
107
- ( None , _, _) => Err ( miette ! (
108
- "Invalid pyvenv.cfg file! no interpreter version specified!"
109
- ) ) ,
110
- _ => Err ( miette ! (
111
- "Invalid pyvenv.cfg file! runfiles interpreter incompletely configured!"
112
- ) ) ,
101
+ ( None , _, _) => miette:: bail!( "Invalid pyvenv.cfg file! no interpreter version specified!" ) ,
102
+ _ => {
103
+ miette:: bail!( "Invalid pyvenv.cfg file! runfiles interpreter incompletely configured!" )
104
+ }
113
105
}
114
106
}
115
107
116
- fn parse_version_info ( version_str : & String ) -> Option < String > {
108
+ fn parse_version_info ( version_str : & str ) -> Option < String > {
117
109
// To avoid pulling in the regex crate, we're gonna do this by hand.
118
110
let parts: Vec < _ > = version_str. split ( "." ) . collect ( ) ;
119
111
match parts[ ..] {
@@ -159,10 +151,9 @@ fn find_actual_interpreter(cfg: &PyCfg) -> miette::Result<PathBuf> {
159
151
InterpreterConfig :: External { version } => {
160
152
let Some ( python_executables) = find_python_executables ( & version, & cfg. root . join ( "bin" ) )
161
153
else {
162
- return Err ( miette ! (
163
- "No suitable Python interpreter found in PATH matching version '{}'." ,
164
- & version,
165
- ) ) ;
154
+ miette:: bail!(
155
+ "No suitable Python interpreter found in PATH matching version '{version}'."
156
+ ) ;
166
157
} ;
167
158
168
159
#[ cfg( feature = "debug" ) ]
@@ -177,27 +168,32 @@ fn find_actual_interpreter(cfg: &PyCfg) -> miette::Result<PathBuf> {
177
168
178
169
// FIXME: Do we still need to offset beyond one?
179
170
let Some ( actual_interpreter_path) = python_executables. get ( 0 ) else {
180
- return Err ( miette ! ( "Unable to find another interpreter!" , ) ) ;
171
+ miette:: bail !( "Unable to find another interpreter!" ) ;
181
172
} ;
182
173
183
174
Ok ( actual_interpreter_path. to_owned ( ) )
184
175
}
185
176
InterpreterConfig :: Runfiles { rpath, repo } => {
186
177
let r = Runfiles :: create ( ) . unwrap ( ) ;
187
178
if let Some ( interpreter) = r. rlocation_from ( rpath. as_str ( ) , & repo) {
188
- return Ok ( PathBuf :: from ( interpreter) ) ;
179
+ Ok ( PathBuf :: from ( interpreter) )
189
180
} else {
190
- return Err ( miette ! ( format!(
181
+ miette:: bail !( format!(
191
182
"Unable to identify an interpreter for venv {:?}" ,
192
183
cfg. interpreter,
193
- ) ) ) ;
184
+ ) ) ;
194
185
}
195
186
}
196
187
}
197
188
}
198
189
199
190
fn main ( ) -> miette:: Result < ( ) > {
200
- let ( venv_root, venv_cfg) = find_venv_root ( ) ?;
191
+ let all_args: Vec < _ > = env:: args ( ) . collect ( ) ;
192
+ let Some ( ( exec_name, exec_args) ) = all_args. split_first ( ) else {
193
+ miette:: bail!( "could not discover an execution command-line" ) ;
194
+ } ;
195
+
196
+ let ( venv_root, venv_cfg) = find_venv_root ( exec_name) ?;
201
197
202
198
let venv_config = parse_venv_cfg ( & venv_root, & venv_cfg) ?;
203
199
@@ -206,32 +202,41 @@ fn main() -> miette::Result<()> {
206
202
207
203
let actual_interpreter = find_actual_interpreter ( & venv_config) ?;
208
204
209
- let args: Vec < _ > = env:: args ( ) . collect ( ) ;
210
-
211
- let exec_args = & args[ 1 ..] ;
212
-
213
205
#[ cfg( feature = "debug" ) ]
214
206
eprintln ! (
215
207
"[aspect] Attempting to execute: {:?} with argv[0] as {:?} and args as {:?}" ,
216
208
& actual_interpreter_path, & venv_interpreter_path, exec_args,
217
209
) ;
218
210
219
211
let mut cmd = Command :: new ( & actual_interpreter) ;
220
-
221
- // Pass along our args
222
- cmd. args ( exec_args) ;
223
-
224
- // Lie about the value of argv0 to hoodwink the interpreter as to its
225
- // location on Linux-based platforms.
226
- cmd. arg0 ( & venv_interpreter) ;
227
-
228
- // Pseudo-`activate`
229
- cmd. env ( "VIRTUAL_ENV" , & venv_root. to_str ( ) . unwrap ( ) ) ;
230
- let venv_bin = ( & venv_root) . join ( "bin" ) . to_str ( ) . unwrap ( ) . to_owned ( ) ;
231
- if let Ok ( current_path) = var ( "PATH" ) {
232
- if current_path. find ( & venv_bin) . is_none ( ) {
233
- cmd. env ( "PATH" , format ! ( "{}:{}" , venv_bin, current_path) ) ;
234
- }
212
+ let cmd = cmd
213
+ // Pass along our args
214
+ . args ( exec_args)
215
+ // Lie about the value of argv0 to hoodwink the interpreter as to its
216
+ // location on Linux-based platforms.
217
+ . arg0 ( & venv_interpreter)
218
+ // Pseudo-`activate`
219
+ . env ( "VIRTUAL_ENV" , & venv_root) ;
220
+
221
+ let venv_bin = ( & venv_root) . join ( "bin" ) ;
222
+ // TODO(arrdem|myrrlyn): PATHSEP is : on Unix and ; on Windows
223
+ let mut path_segments = env:: var ( "PATH" )
224
+ . into_diagnostic ( ) ? // if the variable is unset or not-utf-8, quit
225
+ . split ( ":" ) // break into individual entries
226
+ . filter ( |& p| !p. is_empty ( ) ) // skip over `::`, which is possible
227
+ . map ( ToOwned :: to_owned) // we're dropping the big string, so own the fragments
228
+ . collect :: < Vec < _ > > ( ) ; // and save them.
229
+ let need_venv_in_path = path_segments
230
+ . iter ( )
231
+ . find ( |& p| OsStr :: new ( p) == & venv_bin)
232
+ . is_none ( ) ;
233
+ if need_venv_in_path {
234
+ // append to back
235
+ path_segments. push ( venv_bin. to_string_lossy ( ) . into_owned ( ) ) ;
236
+ // then move venv_bin to the front of PATH
237
+ path_segments. rotate_right ( 1 ) ;
238
+ // and write into the child environment. this avoids an empty PATH causing us to write `{venv_bin}:` with a trailing colon
239
+ cmd. env ( "PATH" , path_segments. join ( ":" ) ) ;
235
240
}
236
241
237
242
// Set the executable pointer for MacOS, but we do it consistently
@@ -254,9 +259,9 @@ fn main() -> miette::Result<()> {
254
259
255
260
// And punt
256
261
let err = cmd. exec ( ) ;
257
- Err ( miette ! ( format !(
262
+ miette:: bail !(
258
263
"Failed to exec target {}, {}" ,
259
264
actual_interpreter. display( ) ,
260
265
err,
261
- ) ) )
266
+ )
262
267
}
0 commit comments