@@ -16,7 +16,7 @@ use uv_state::{StateBucket, StateStore};
16
16
use uv_static:: EnvVars ;
17
17
use uv_trampoline_builder:: { windows_python_launcher, Launcher } ;
18
18
19
- use crate :: downloads:: Error as DownloadError ;
19
+ use crate :: downloads:: { Error as DownloadError , ManagedPythonDownload } ;
20
20
use crate :: implementation:: {
21
21
Error as ImplementationError , ImplementationName , LenientImplementationName ,
22
22
} ;
@@ -229,7 +229,7 @@ impl ManagedPythonInstallations {
229
229
. unwrap_or ( true )
230
230
} )
231
231
. filter_map ( |path| {
232
- ManagedPythonInstallation :: new ( path)
232
+ ManagedPythonInstallation :: from_path ( path)
233
233
. inspect_err ( |err| {
234
234
warn ! ( "Ignoring malformed managed Python entry:\n {err}" ) ;
235
235
} )
@@ -294,10 +294,27 @@ pub struct ManagedPythonInstallation {
294
294
path : PathBuf ,
295
295
/// An install key for the Python version.
296
296
key : PythonInstallationKey ,
297
+ /// The URL with the Python archive.
298
+ ///
299
+ /// Empty when self was constructed from a path.
300
+ url : Option < & ' static str > ,
301
+ /// The SHA256 of the Python archive at the URL.
302
+ ///
303
+ /// Empty when self was constructed from a path.
304
+ sha256 : Option < & ' static str > ,
297
305
}
298
306
299
307
impl ManagedPythonInstallation {
300
- pub fn new ( path : PathBuf ) -> Result < Self , Error > {
308
+ pub fn new ( path : PathBuf , download : & ManagedPythonDownload ) -> Self {
309
+ Self {
310
+ path,
311
+ key : download. key ( ) . clone ( ) ,
312
+ url : Some ( download. url ( ) ) ,
313
+ sha256 : download. sha256 ( ) ,
314
+ }
315
+ }
316
+
317
+ pub ( crate ) fn from_path ( path : PathBuf ) -> Result < Self , Error > {
301
318
let key = PythonInstallationKey :: from_str (
302
319
path. file_name ( )
303
320
. ok_or ( Error :: NameError ( "name is empty" . to_string ( ) ) ) ?
@@ -307,15 +324,23 @@ impl ManagedPythonInstallation {
307
324
308
325
let path = std:: path:: absolute ( & path) . map_err ( |err| Error :: AbsolutePath ( path, err) ) ?;
309
326
310
- Ok ( Self { path, key } )
327
+ Ok ( Self {
328
+ path,
329
+ key,
330
+ url : None ,
331
+ sha256 : None ,
332
+ } )
311
333
}
312
334
313
335
/// The path to this managed installation's Python executable.
314
336
///
315
337
/// If the installation has multiple execututables i.e., `python`, `python3`, etc., this will
316
338
/// return the _canonical_ executable name which the other names link to. On Unix, this is
317
339
/// `python{major}.{minor}{variant}` and on Windows, this is `python{exe}`.
318
- pub fn executable ( & self ) -> PathBuf {
340
+ ///
341
+ /// If windowed is true, `pythonw.exe` is selected over `python.exe` on windows, with no changes
342
+ /// on non-windows.
343
+ pub fn executable ( & self , windowed : bool ) -> PathBuf {
319
344
let implementation = match self . implementation ( ) {
320
345
ImplementationName :: CPython => "python" ,
321
346
ImplementationName :: PyPy => "pypy" ,
@@ -342,6 +367,9 @@ impl ManagedPythonInstallation {
342
367
// On Windows, the executable is just `python.exe` even for alternative variants
343
368
let variant = if cfg ! ( unix) {
344
369
self . key . variant . suffix ( )
370
+ } else if cfg ! ( windows) && windowed {
371
+ // Use windowed Python that doesn't open a terminal.
372
+ "w"
345
373
} else {
346
374
""
347
375
} ;
@@ -412,11 +440,11 @@ impl ManagedPythonInstallation {
412
440
413
441
pub fn satisfies ( & self , request : & PythonRequest ) -> bool {
414
442
match request {
415
- PythonRequest :: File ( path) => self . executable ( ) == * path,
443
+ PythonRequest :: File ( path) => self . executable ( false ) == * path,
416
444
PythonRequest :: Default | PythonRequest :: Any => true ,
417
445
PythonRequest :: Directory ( path) => self . path ( ) == * path,
418
446
PythonRequest :: ExecutableName ( name) => self
419
- . executable ( )
447
+ . executable ( false )
420
448
. file_name ( )
421
449
. is_some_and ( |filename| filename. to_string_lossy ( ) == * name) ,
422
450
PythonRequest :: Implementation ( implementation) => {
@@ -432,7 +460,7 @@ impl ManagedPythonInstallation {
432
460
433
461
/// Ensure the environment contains the canonical Python executable names.
434
462
pub fn ensure_canonical_executables ( & self ) -> Result < ( ) , Error > {
435
- let python = self . executable ( ) ;
463
+ let python = self . executable ( false ) ;
436
464
437
465
let canonical_names = & [ "python" ] ;
438
466
@@ -539,7 +567,7 @@ impl ManagedPythonInstallation {
539
567
///
540
568
/// If the file already exists at the target path, an error will be returned.
541
569
pub fn create_bin_link ( & self , target : & Path ) -> Result < ( ) , Error > {
542
- let python = self . executable ( ) ;
570
+ let python = self . executable ( false ) ;
543
571
544
572
let bin = target. parent ( ) . ok_or ( Error :: NoExecutableDirectory ) ?;
545
573
fs_err:: create_dir_all ( bin) . map_err ( |err| Error :: ExecutableDirectory {
@@ -585,15 +613,15 @@ impl ManagedPythonInstallation {
585
613
/// [`ManagedPythonInstallation::create_bin_link`].
586
614
pub fn is_bin_link ( & self , path : & Path ) -> bool {
587
615
if cfg ! ( unix) {
588
- is_same_file ( path, self . executable ( ) ) . unwrap_or_default ( )
616
+ is_same_file ( path, self . executable ( false ) ) . unwrap_or_default ( )
589
617
} else if cfg ! ( windows) {
590
618
let Some ( launcher) = Launcher :: try_from_path ( path) . unwrap_or_default ( ) else {
591
619
return false ;
592
620
} ;
593
621
if !matches ! ( launcher. kind, uv_trampoline_builder:: LauncherKind :: Python ) {
594
622
return false ;
595
623
}
596
- launcher. python_path == self . executable ( )
624
+ launcher. python_path == self . executable ( false )
597
625
} else {
598
626
unreachable ! ( "Only Windows and Unix are supported" )
599
627
}
@@ -627,6 +655,14 @@ impl ManagedPythonInstallation {
627
655
// Do not upgrade if the patch versions are the same
628
656
self . key . patch != other. key . patch
629
657
}
658
+
659
+ pub fn url ( & self ) -> Option < & ' static str > {
660
+ self . url
661
+ }
662
+
663
+ pub fn sha256 ( & self ) -> Option < & ' static str > {
664
+ self . sha256
665
+ }
630
666
}
631
667
632
668
/// Generate a platform portion of a key from the environment.
0 commit comments