mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Patch Python executable name for Windows free-threaded builds (#8310)
A temporary fix for https://github.com/astral-sh/uv/issues/8298 while we
wait for my slower upstream fix at
https://github.com/indygreg/python-build-standalone/pull/373
I think we'll want this machinery anyway to ensure that the various
executable names are available? Otherwise we need to special-case all
the `python` names in `uv run`?
We don't have unit test coverage of managed downloads, so I added an
[integration
test](3170395680
)
similar to what we have for Linux.
This commit is contained in:
parent
3fd69b448e
commit
c8cbd62a30
5 changed files with 114 additions and 1 deletions
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
|
@ -718,6 +718,46 @@ jobs:
|
|||
run: |
|
||||
./uv pip install -v anyio
|
||||
|
||||
integration-test-free-threaded-windows:
|
||||
timeout-minutes: 10
|
||||
needs: build-binary-windows
|
||||
name: "integration test | free-threaded on windows"
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
# Avoid debug build stack overflows.
|
||||
UV_STACK_SIZE: 2000000
|
||||
|
||||
steps:
|
||||
- name: "Download binary"
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: uv-windows-${{ github.sha }}
|
||||
|
||||
- name: "Install free-threaded Python via uv"
|
||||
run: |
|
||||
./uv python install -v 3.13t
|
||||
|
||||
- name: "Create a virtual environment"
|
||||
run: |
|
||||
./uv venv -p 3.13t --python-preference only-managed
|
||||
|
||||
- name: "Check version"
|
||||
run: |
|
||||
.venv/Scripts/python --version
|
||||
|
||||
- name: "Check is free-threaded"
|
||||
run: |
|
||||
.venv/Scripts/python -c "import sys; exit(1) if sys._is_gil_enabled() else exit(0)"
|
||||
|
||||
- name: "Check install"
|
||||
run: |
|
||||
./uv pip install -v anyio
|
||||
|
||||
- name: "Check uv run"
|
||||
run: |
|
||||
./uv run python -c ""
|
||||
./uv run -p 3.13t python -c ""
|
||||
|
||||
integration-test-pypy-linux:
|
||||
timeout-minutes: 10
|
||||
needs: build-binary-linux
|
||||
|
|
|
@ -104,6 +104,26 @@ pub fn remove_symlink(path: impl AsRef<Path>) -> std::io::Result<()> {
|
|||
fs_err::remove_file(path.as_ref())
|
||||
}
|
||||
|
||||
/// Create a symlink at `dst` pointing to `src` or, on Windows, copy `src` to `dst`.
|
||||
///
|
||||
/// This function should only be used for files. If targeting a directory, use [`replace_symlink`]
|
||||
/// instead; it will use a junction on Windows, which is more performant.
|
||||
pub fn symlink_copy_fallback_file(
|
||||
src: impl AsRef<Path>,
|
||||
dst: impl AsRef<Path>,
|
||||
) -> std::io::Result<()> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
fs_err::copy(src.as_ref(), dst.as_ref())?;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink(src.as_ref(), dst.as_ref())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn remove_symlink(path: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
match junction::delete(dunce::simplified(path.as_ref())) {
|
||||
|
|
|
@ -142,6 +142,7 @@ impl PythonInstallation {
|
|||
|
||||
let installed = ManagedPythonInstallation::new(path)?;
|
||||
installed.ensure_externally_managed()?;
|
||||
installed.ensure_canonical_executables()?;
|
||||
|
||||
Ok(Self {
|
||||
source: PythonSource::Managed,
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::io::{self, Write};
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use uv_state::{StateBucket, StateStore};
|
||||
|
||||
|
@ -44,6 +44,15 @@ pub enum Error {
|
|||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
#[error("Missing expected Python executable at {}", _0.user_display())]
|
||||
MissingExecutable(PathBuf),
|
||||
#[error("Failed to create canonical Python executable at {} from {}", to.user_display(), from.user_display())]
|
||||
CanonicalizeExecutable {
|
||||
from: PathBuf,
|
||||
to: PathBuf,
|
||||
#[source]
|
||||
err: io::Error,
|
||||
},
|
||||
#[error("Failed to read Python installation directory: {0}", dir.user_display())]
|
||||
ReadError {
|
||||
dir: PathBuf,
|
||||
|
@ -323,6 +332,48 @@ impl ManagedPythonInstallation {
|
|||
}
|
||||
}
|
||||
|
||||
/// Ensure the environment contains the canonical Python executable names.
|
||||
pub fn ensure_canonical_executables(&self) -> Result<(), Error> {
|
||||
let python = self.executable();
|
||||
|
||||
// Workaround for python-build-standalone v20241016 which is missing the standard
|
||||
// `python.exe` executable in free-threaded distributions on Windows.
|
||||
//
|
||||
// See https://github.com/astral-sh/uv/issues/8298
|
||||
if !python.try_exists()? {
|
||||
match self.key.variant {
|
||||
PythonVariant::Default => return Err(Error::MissingExecutable(python.clone())),
|
||||
PythonVariant::Freethreaded => {
|
||||
// This is the alternative executable name for the freethreaded variant
|
||||
let python_in_dist = self.python_dir().join(format!(
|
||||
"python{}.{}t{}",
|
||||
self.key.major,
|
||||
self.key.minor,
|
||||
std::env::consts::EXE_SUFFIX
|
||||
));
|
||||
debug!(
|
||||
"Creating link {} -> {}",
|
||||
python.user_display(),
|
||||
python_in_dist.user_display()
|
||||
);
|
||||
uv_fs::symlink_copy_fallback_file(&python_in_dist, &python).map_err(|err| {
|
||||
if err.kind() == io::ErrorKind::NotFound {
|
||||
Error::MissingExecutable(python_in_dist.clone())
|
||||
} else {
|
||||
Error::CanonicalizeExecutable {
|
||||
from: python_in_dist,
|
||||
to: python,
|
||||
err,
|
||||
}
|
||||
}
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure the environment is marked as externally managed with the
|
||||
/// standard `EXTERNALLY-MANAGED` file.
|
||||
pub fn ensure_externally_managed(&self) -> Result<(), Error> {
|
||||
|
|
|
@ -167,6 +167,7 @@ pub(crate) async fn install(
|
|||
// Ensure the installations have externally managed markers
|
||||
let managed = ManagedPythonInstallation::new(path.clone())?;
|
||||
managed.ensure_externally_managed()?;
|
||||
managed.ensure_canonical_executables()?;
|
||||
}
|
||||
Err(err) => {
|
||||
errors.push((key, err));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue