@@ -7,7 +7,7 @@ use std::io::{self, Write};
77use std:: path:: { Path , PathBuf } ;
88use std:: str:: FromStr ;
99use thiserror:: Error ;
10- use tracing:: warn;
10+ use tracing:: { debug , warn} ;
1111
1212use uv_state:: { StateBucket , StateStore } ;
1313
@@ -44,6 +44,15 @@ pub enum Error {
4444 #[ source]
4545 err : io:: Error ,
4646 } ,
47+ #[ error( "Missing expected Python executable at {}" , _0. user_display( ) ) ]
48+ MissingExecutable ( PathBuf ) ,
49+ #[ error( "Failed to create canonical Python executable at {} from {}" , to. user_display( ) , from. user_display( ) ) ]
50+ CanonicalizeExecutable {
51+ from : PathBuf ,
52+ to : PathBuf ,
53+ #[ source]
54+ err : io:: Error ,
55+ } ,
4756 #[ error( "Failed to read Python installation directory: {0}" , dir. user_display( ) ) ]
4857 ReadError {
4958 dir : PathBuf ,
@@ -323,6 +332,48 @@ impl ManagedPythonInstallation {
323332 }
324333 }
325334
335+ /// Ensure the environment contains the canonical Python executable names.
336+ pub fn ensure_canonical_executables ( & self ) -> Result < ( ) , Error > {
337+ let python = self . executable ( ) ;
338+
339+ // Workaround for python-build-standalone v20241016 which is missing the standard
340+ // `python.exe` executable in free-threaded distributions on Windows.
341+ //
342+ // See https://github.com/astral-sh/uv/issues/8298
343+ if !python. try_exists ( ) ? {
344+ match self . key . variant {
345+ PythonVariant :: Default => return Err ( Error :: MissingExecutable ( python. clone ( ) ) ) ,
346+ PythonVariant :: Freethreaded => {
347+ // This is the alternative executable name for the freethreaded variant
348+ let python_in_dist = self . python_dir ( ) . join ( format ! (
349+ "python{}.{}t{}" ,
350+ self . key. major,
351+ self . key. minor,
352+ std:: env:: consts:: EXE_SUFFIX
353+ ) ) ;
354+ debug ! (
355+ "Creating link {} -> {}" ,
356+ python. user_display( ) ,
357+ python_in_dist. user_display( )
358+ ) ;
359+ uv_fs:: symlink_copy_fallback_file ( & python_in_dist, & python) . map_err ( |err| {
360+ if err. kind ( ) == io:: ErrorKind :: NotFound {
361+ Error :: MissingExecutable ( python_in_dist. clone ( ) )
362+ } else {
363+ Error :: CanonicalizeExecutable {
364+ from : python_in_dist,
365+ to : python,
366+ err,
367+ }
368+ }
369+ } ) ?;
370+ }
371+ }
372+ }
373+
374+ Ok ( ( ) )
375+ }
376+
326377 /// Ensure the environment is marked as externally managed with the
327378 /// standard `EXTERNALLY-MANAGED` file.
328379 pub fn ensure_externally_managed ( & self ) -> Result < ( ) , Error > {
0 commit comments