@@ -89,74 +89,45 @@ fn check_impl(
8989
9090 if python_lint {
9191 eprintln ! ( "linting python files" ) ;
92- let mut cfg_args_ruff = cfg_args. clone ( ) ;
93- let mut file_args_ruff = file_args. clone ( ) ;
94-
95- let mut cfg_path = root_path. to_owned ( ) ;
96- cfg_path. extend ( RUFF_CONFIG_PATH ) ;
97- let mut cache_dir = outdir. to_owned ( ) ;
98- cache_dir. extend ( RUFF_CACHE_PATH ) ;
99-
100- cfg_args_ruff. extend ( [
101- "--config" . as_ref ( ) ,
102- cfg_path. as_os_str ( ) ,
103- "--cache-dir" . as_ref ( ) ,
104- cache_dir. as_os_str ( ) ,
105- ] ) ;
106-
107- if file_args_ruff. is_empty ( ) {
108- file_args_ruff. push ( root_path. as_os_str ( ) ) ;
109- }
110-
111- let mut args = merge_args ( & cfg_args_ruff, & file_args_ruff) ;
112- args. insert ( 0 , "check" . as_ref ( ) ) ;
113- let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "ruff" , & args) ;
92+ let py_path = py_path. as_ref ( ) . unwrap ( ) ;
93+ let res = run_ruff ( root_path, outdir, py_path, & cfg_args, & file_args, & [ "check" . as_ref ( ) ] ) ;
11494
11595 if res. is_err ( ) && show_diff {
11696 eprintln ! ( "\n python linting failed! Printing diff suggestions:" ) ;
11797
118- args. insert ( 1 , "--diff" . as_ref ( ) ) ;
119- let _ = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "ruff" , & args) ;
98+ let _ = run_ruff ( root_path, outdir, py_path, & cfg_args, & file_args, & [
99+ "check" . as_ref ( ) ,
100+ "--diff" . as_ref ( ) ,
101+ ] ) ;
120102 }
121103 // Rethrow error
122104 let _ = res?;
123105 }
124106
125107 if python_fmt {
126- let mut cfg_args_ruff = cfg_args. clone ( ) ;
127- let mut file_args_ruff = file_args. clone ( ) ;
128-
108+ let mut args: Vec < & OsStr > = vec ! [ "format" . as_ref( ) ] ;
129109 if bless {
130110 eprintln ! ( "formatting python files" ) ;
131111 } else {
132112 eprintln ! ( "checking python file formatting" ) ;
133- cfg_args_ruff. push ( "--check" . as_ref ( ) ) ;
134- }
135-
136- let mut cfg_path = root_path. to_owned ( ) ;
137- cfg_path. extend ( RUFF_CONFIG_PATH ) ;
138- let mut cache_dir = outdir. to_owned ( ) ;
139- cache_dir. extend ( RUFF_CACHE_PATH ) ;
140-
141- cfg_args_ruff. extend ( [ "--config" . as_ref ( ) , cfg_path. as_os_str ( ) ] ) ;
142-
143- if file_args_ruff. is_empty ( ) {
144- file_args_ruff. push ( root_path. as_os_str ( ) ) ;
113+ args. push ( "--check" . as_ref ( ) ) ;
145114 }
146115
147- let mut args = merge_args ( & cfg_args_ruff, & file_args_ruff) ;
148- args. insert ( 0 , "format" . as_ref ( ) ) ;
149- let res = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "ruff" , & args) ;
116+ let py_path = py_path. as_ref ( ) . unwrap ( ) ;
117+ let res = run_ruff ( root_path, outdir, py_path, & cfg_args, & file_args, & args) ;
150118
151- if res. is_err ( ) && show_diff {
152- eprintln ! ( "\n python formatting does not match! Printing diff:" ) ;
153-
154- args. insert ( 0 , "--diff" . as_ref ( ) ) ;
155- let _ = py_runner ( py_path. as_ref ( ) . unwrap ( ) , true , None , "ruff" , & args) ;
156- }
157119 if res. is_err ( ) && !bless {
120+ if show_diff {
121+ eprintln ! ( "\n python formatting does not match! Printing diff:" ) ;
122+
123+ let _ = run_ruff ( root_path, outdir, py_path, & cfg_args, & file_args, & [
124+ "format" . as_ref ( ) ,
125+ "--diff" . as_ref ( ) ,
126+ ] ) ;
127+ }
158128 eprintln ! ( "rerun tidy with `--extra-checks=py:fmt --bless` to reformat Python code" ) ;
159129 }
130+
160131 // Rethrow error
161132 let _ = res?;
162133 }
@@ -247,6 +218,38 @@ fn check_impl(
247218 Ok ( ( ) )
248219}
249220
221+ fn run_ruff (
222+ root_path : & Path ,
223+ outdir : & Path ,
224+ py_path : & Path ,
225+ cfg_args : & [ & OsStr ] ,
226+ file_args : & [ & OsStr ] ,
227+ ruff_args : & [ & OsStr ] ,
228+ ) -> Result < ( ) , Error > {
229+ let mut cfg_args_ruff = cfg_args. into_iter ( ) . copied ( ) . collect :: < Vec < _ > > ( ) ;
230+ let mut file_args_ruff = file_args. into_iter ( ) . copied ( ) . collect :: < Vec < _ > > ( ) ;
231+
232+ let mut cfg_path = root_path. to_owned ( ) ;
233+ cfg_path. extend ( RUFF_CONFIG_PATH ) ;
234+ let mut cache_dir = outdir. to_owned ( ) ;
235+ cache_dir. extend ( RUFF_CACHE_PATH ) ;
236+
237+ cfg_args_ruff. extend ( [
238+ "--config" . as_ref ( ) ,
239+ cfg_path. as_os_str ( ) ,
240+ "--cache-dir" . as_ref ( ) ,
241+ cache_dir. as_os_str ( ) ,
242+ ] ) ;
243+
244+ if file_args_ruff. is_empty ( ) {
245+ file_args_ruff. push ( root_path. as_os_str ( ) ) ;
246+ }
247+
248+ let mut args: Vec < & OsStr > = ruff_args. into_iter ( ) . copied ( ) . collect ( ) ;
249+ args. extend ( merge_args ( & cfg_args_ruff, & file_args_ruff) ) ;
250+ py_runner ( py_path, true , None , "ruff" , & args)
251+ }
252+
250253/// Helper to create `cfg1 cfg2 -- file1 file2` output
251254fn merge_args < ' a > ( cfg_args : & [ & ' a OsStr ] , file_args : & [ & ' a OsStr ] ) -> Vec < & ' a OsStr > {
252255 let mut args = cfg_args. to_owned ( ) ;
@@ -321,8 +324,16 @@ fn get_or_create_venv(venv_path: &Path, src_reqs_path: &Path) -> Result<PathBuf,
321324fn create_venv_at_path ( path : & Path ) -> Result < ( ) , Error > {
322325 /// Preferred python versions in order. Newest to oldest then current
323326 /// development versions
324- const TRY_PY : & [ & str ] =
325- & [ "python3.11" , "python3.10" , "python3.9" , "python3" , "python" , "python3.12" , "python3.13" ] ;
327+ const TRY_PY : & [ & str ] = & [
328+ "python3.13" ,
329+ "python3.12" ,
330+ "python3.11" ,
331+ "python3.10" ,
332+ "python3.9" ,
333+ "python3" ,
334+ "python" ,
335+ "python3.14" ,
336+ ] ;
326337
327338 let mut sys_py = None ;
328339 let mut found = Vec :: new ( ) ;
@@ -357,22 +368,40 @@ fn create_venv_at_path(path: &Path) -> Result<(), Error> {
357368 return Err ( ret) ;
358369 } ;
359370
360- eprintln ! ( "creating virtual environment at '{}' using '{sys_py}'" , path. display( ) ) ;
361- let out = Command :: new ( sys_py) . args ( [ "-m" , "virtualenv" ] ) . arg ( path) . output ( ) . unwrap ( ) ;
371+ // First try venv, which should be packaged in the Python3 standard library.
372+ // If it is not available, try to create the virtual environment using the
373+ // virtualenv package.
374+ if try_create_venv ( sys_py, path, "venv" ) . is_ok ( ) {
375+ return Ok ( ( ) ) ;
376+ }
377+ try_create_venv ( sys_py, path, "virtualenv" )
378+ }
379+
380+ fn try_create_venv ( python : & str , path : & Path , module : & str ) -> Result < ( ) , Error > {
381+ eprintln ! (
382+ "creating virtual environment at '{}' using '{python}' and '{module}'" ,
383+ path. display( )
384+ ) ;
385+ let out = Command :: new ( python) . args ( [ "-m" , module] ) . arg ( path) . output ( ) . unwrap ( ) ;
362386
363387 if out. status . success ( ) {
364388 return Ok ( ( ) ) ;
365389 }
366390
367391 let stderr = String :: from_utf8_lossy ( & out. stderr ) ;
368- let err = if stderr. contains ( "No module named virtualenv" ) {
392+ let err = if stderr. contains ( & format ! ( "No module named {module}" ) ) {
369393 Error :: Generic ( format ! (
370- "virtualenv not found: you may need to install it \
371- (`{sys_py} -m pip install virtualenv`)"
394+ r#"{module} not found: you may need to install it:
395+ `{python} -m pip install {module}`
396+ If you see an error about "externally managed environment" when running the above command,
397+ either install `{module}` using your system package manager
398+ (e.g. `sudo apt-get install {python}-{module}`) or create a virtual environment manually, install
399+ `{module}` in it and then activate it before running tidy.
400+ "#
372401 ) )
373402 } else {
374403 Error :: Generic ( format ! (
375- "failed to create venv at '{}' using {sys_py }: {stderr}" ,
404+ "failed to create venv at '{}' using {python} -m {module }: {stderr}" ,
376405 path. display( )
377406 ) )
378407 } ;
0 commit comments