Allow Python requests to include +gil to require a GIL-enabled interpreter (#16537)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | activate nushell venv (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | windows python install manager (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | pyenv on wsl x86-64 (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / check system | python on rocky linux 10 (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | x86-64 python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
zizmor / Run zizmor (push) Waiting to run

Addresses
https://github.com/astral-sh/uv/pull/16142#issuecomment-3390586957

Nobody seems to have a good idea about how to spell this. "not
free-threaded" would be the most technically correct, but I think "gil"
will be more intuitive.
This commit is contained in:
Zanie Blue 2025-11-03 13:35:52 -06:00 committed by GitHub
parent a87a3bbae4
commit 60a811e715
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 97 additions and 39 deletions

View file

@ -170,6 +170,8 @@ pub enum PythonVariant {
Debug,
Freethreaded,
FreethreadedDebug,
Gil,
GilDebug,
}
/// A Python discovery version request.
@ -1685,18 +1687,34 @@ impl PythonVariant {
Self::Debug => interpreter.debug_enabled(),
Self::Freethreaded => interpreter.gil_disabled(),
Self::FreethreadedDebug => interpreter.gil_disabled() && interpreter.debug_enabled(),
Self::Gil => !interpreter.gil_disabled(),
Self::GilDebug => !interpreter.gil_disabled() && interpreter.debug_enabled(),
}
}
/// Return the executable suffix for the variant, e.g., `t` for `python3.13t`.
///
/// Returns an empty string for the default Python variant.
pub fn suffix(self) -> &'static str {
pub fn executable_suffix(self) -> &'static str {
match self {
Self::Default => "",
Self::Debug => "d",
Self::Freethreaded => "t",
Self::FreethreadedDebug => "td",
Self::Gil => "",
Self::GilDebug => "d",
}
}
/// Return the suffix for display purposes, e.g., `+gil`.
pub fn display_suffix(self) -> &'static str {
match self {
Self::Default => "",
Self::Debug => "+debug",
Self::Freethreaded => "+freethreaded",
Self::FreethreadedDebug => "+freethreaded+debug",
Self::Gil => "+gil",
Self::GilDebug => "+gil+debug",
}
}
@ -1704,22 +1722,22 @@ impl PythonVariant {
/// `python3.13d` or `python3.13`.
pub fn lib_suffix(self) -> &'static str {
match self {
Self::Default | Self::Debug => "",
Self::Default | Self::Debug | Self::Gil | Self::GilDebug => "",
Self::Freethreaded | Self::FreethreadedDebug => "t",
}
}
pub fn is_freethreaded(self) -> bool {
match self {
Self::Default | Self::Debug => false,
Self::Default | Self::Debug | Self::Gil | Self::GilDebug => false,
Self::Freethreaded | Self::FreethreadedDebug => true,
}
}
pub fn is_debug(self) -> bool {
match self {
Self::Default | Self::Freethreaded => false,
Self::Debug | Self::FreethreadedDebug => true,
Self::Default | Self::Freethreaded | Self::Gil => false,
Self::Debug | Self::FreethreadedDebug | Self::GilDebug => true,
}
}
}
@ -2450,7 +2468,7 @@ impl fmt::Display for ExecutableName {
if let Some(prerelease) = &self.prerelease {
write!(f, "{prerelease}")?;
}
f.write_str(self.variant.suffix())?;
f.write_str(self.variant.executable_suffix())?;
f.write_str(EXE_SUFFIX)?;
Ok(())
}
@ -3067,6 +3085,8 @@ impl FromStr for PythonVariant {
"t" | "freethreaded" => Ok(Self::Freethreaded),
"d" | "debug" => Ok(Self::Debug),
"td" | "freethreaded+debug" => Ok(Self::FreethreadedDebug),
"gil" => Ok(Self::Gil),
"gil+debug" => Ok(Self::GilDebug),
"" => Ok(Self::Default),
_ => Err(()),
}
@ -3080,6 +3100,8 @@ impl fmt::Display for PythonVariant {
Self::Debug => f.write_str("debug"),
Self::Freethreaded => f.write_str("freethreaded"),
Self::FreethreadedDebug => f.write_str("freethreaded+debug"),
Self::Gil => f.write_str("gil"),
Self::GilDebug => f.write_str("gil+debug"),
}
}
}
@ -3109,15 +3131,15 @@ impl fmt::Display for VersionRequest {
match self {
Self::Any => f.write_str("any"),
Self::Default => f.write_str("default"),
Self::Major(major, variant) => write!(f, "{major}{}", variant.suffix()),
Self::Major(major, variant) => write!(f, "{major}{}", variant.display_suffix()),
Self::MajorMinor(major, minor, variant) => {
write!(f, "{major}.{minor}{}", variant.suffix())
write!(f, "{major}.{minor}{}", variant.display_suffix())
}
Self::MajorMinorPatch(major, minor, patch, variant) => {
write!(f, "{major}.{minor}.{patch}{}", variant.suffix())
write!(f, "{major}.{minor}.{patch}{}", variant.display_suffix())
}
Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
write!(f, "{major}.{minor}{prerelease}{}", variant.suffix())
write!(f, "{major}.{minor}{prerelease}{}", variant.display_suffix())
}
Self::Range(specifiers, _) => write!(f, "{specifiers}"),
}

View file

@ -460,7 +460,7 @@ impl PythonInstallationKey {
"python{maj}.{min}{var}{exe}",
maj = self.major,
min = self.minor,
var = self.variant.suffix(),
var = self.variant.executable_suffix(),
exe = std::env::consts::EXE_SUFFIX
)
}
@ -470,7 +470,7 @@ impl PythonInstallationKey {
format!(
"python{maj}{var}{exe}",
maj = self.major,
var = self.variant.suffix(),
var = self.variant.executable_suffix(),
exe = std::env::consts::EXE_SUFFIX
)
}
@ -479,7 +479,7 @@ impl PythonInstallationKey {
pub fn executable_name(&self) -> String {
format!(
"python{var}{exe}",
var = self.variant.suffix(),
var = self.variant.executable_suffix(),
exe = std::env::consts::EXE_SUFFIX
)
}

View file

@ -173,7 +173,7 @@ impl Interpreter {
base_executable,
self.python_major(),
self.python_minor(),
self.variant().suffix(),
self.variant().executable_suffix(),
) {
Ok(path) => path,
Err(err) => {

View file

@ -392,7 +392,7 @@ impl ManagedPythonInstallation {
let variant = if self.implementation() == ImplementationName::GraalPy {
""
} else if cfg!(unix) {
self.key.variant.suffix()
self.key.variant.executable_suffix()
} else if cfg!(windows) && windowed {
// Use windowed Python that doesn't open a terminal.
"w"
@ -626,7 +626,7 @@ impl ManagedPythonInstallation {
"{}python{}{}{}",
std::env::consts::DLL_PREFIX,
self.key.version().python_version(),
self.key.variant().suffix(),
self.key.variant().executable_suffix(),
std::env::consts::DLL_SUFFIX
));
macos_dylib::patch_dylib_install_name(dylib_path)?;

View file

@ -746,7 +746,7 @@ pub(crate) fn report_interpreter(
"Using {} {}{}",
implementation.pretty(),
interpreter.python_version(),
interpreter.variant().suffix(),
interpreter.variant().display_suffix(),
)
.dimmed()
)?;
@ -758,7 +758,7 @@ pub(crate) fn report_interpreter(
"Using {} {}{} interpreter at: {}",
implementation.pretty(),
interpreter.python_version(),
interpreter.variant().suffix(),
interpreter.variant().display_suffix(),
interpreter.sys_executable().user_display()
)
.dimmed()
@ -771,7 +771,7 @@ pub(crate) fn report_interpreter(
"Using {} {}{}",
implementation.pretty(),
interpreter.python_version().cyan(),
interpreter.variant().suffix().cyan()
interpreter.variant().display_suffix().cyan()
)?;
} else {
writeln!(
@ -779,7 +779,7 @@ pub(crate) fn report_interpreter(
"Using {} {}{} interpreter at: {}",
implementation.pretty(),
interpreter.python_version(),
interpreter.variant().suffix(),
interpreter.variant().display_suffix(),
interpreter.sys_executable().user_display().cyan()
)?;
}

View file

@ -1011,7 +1011,7 @@ impl ProjectInterpreter {
"Using {} {}{}",
implementation.pretty(),
interpreter.python_version().cyan(),
interpreter.variant().suffix().cyan(),
interpreter.variant().display_suffix().cyan(),
)?;
} else {
writeln!(
@ -1019,7 +1019,7 @@ impl ProjectInterpreter {
"Using {} {}{} interpreter at: {}",
implementation.pretty(),
interpreter.python_version(),
interpreter.variant().suffix(),
interpreter.variant().display_suffix(),
interpreter.sys_executable().user_display().cyan()
)?;
}

View file

@ -859,7 +859,7 @@ fn create_bin_links(
to.simplified_display(),
installation.key().major(),
installation.key().minor(),
installation.key().variant().suffix()
installation.key().variant().display_suffix()
);
} else {
errors.push((

View file

@ -3968,7 +3968,7 @@ fn init_without_description() -> Result<()> {
/// Run `uv init --python 3.13t` to create a pin to a freethreaded Python.
#[test]
fn init_python_variant() -> Result<()> {
fn init_python_variant() {
let context = TestContext::new("3.13");
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--python").arg("3.13t"), @r###"
success: true
@ -3979,10 +3979,8 @@ fn init_python_variant() -> Result<()> {
Initialized project `foo` at `[TEMP_DIR]/foo`
"###);
let python_version = fs_err::read_to_string(context.temp_dir.join("foo/.python-version"))?;
assert_eq!(python_version, "3.13t\n");
Ok(())
let contents = context.read("foo/.python-version");
assert_snapshot!(contents, @"3.13+freethreaded");
}
/// Check how `uv init` reacts to working and broken git with different `--vcs` options.

View file

@ -850,14 +850,14 @@ fn python_find_unsupported_version() {
"###);
// Request free-threaded Python on unsupported version
uv_snapshot!(context.filters(), context.python_find().arg("3.12t"), @r###"
uv_snapshot!(context.filters(), context.python_find().arg("3.12t"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.13 does not support free-threading but 3.12t was requested.
"###);
error: Invalid version request: Python <3.13 does not support free-threading but 3.12+freethreaded was requested.
");
}
#[test]
@ -1334,6 +1334,44 @@ fn python_find_freethreaded_314() {
----- stderr -----
");
// Request Python 3.14+gil
uv_snapshot!(context.filters(), context.python_find().arg("3.14+gil"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No interpreter found for Python 3.14+gil in [PYTHON SOURCES]
");
// Install the non-freethreaded version
context
.python_install()
.arg("--preview")
.arg("3.14")
.assert()
.success();
// Request Python 3.14
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
// Request Python 3.14+gil
uv_snapshot!(context.filters(), context.python_find().arg("3.14+gil"), @r"
success: true
exit_code: 0
----- stdout -----
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
----- stderr -----
");
}
#[test]

View file

@ -1130,7 +1130,7 @@ fn python_install_freethreaded() {
----- stdout -----
----- stderr -----
Using CPython 3.13.9t
Using CPython 3.13.9+freethreaded
Creating virtual environment at: .venv
Activate with: source .venv/[BIN]/activate
");
@ -1200,7 +1200,7 @@ fn python_install_freethreaded() {
----- stdout -----
----- stderr -----
error: No download found for request: cpython-3.12t-[PLATFORM]
error: No download found for request: cpython-3.12+freethreaded-[PLATFORM]
");
uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r"

View file

@ -280,7 +280,7 @@ fn python_list_unsupported_version() {
----- stdout -----
----- stderr -----
error: Invalid version request: Python <3.13 does not support free-threading but 3.12t was requested.
error: Invalid version request: Python <3.13 does not support free-threading but 3.12+freethreaded was requested.
");
}

View file

@ -451,21 +451,21 @@ fn python_pin_compatible_with_requires_python() -> Result<()> {
success: true
exit_code: 0
----- stdout -----
Updated `.python-version` from `3.11` -> `3.13t`
Updated `.python-version` from `3.11` -> `3.13+freethreaded`
----- stderr -----
warning: No interpreter found for Python 3.13t in [PYTHON SOURCES]
warning: No interpreter found for Python 3.13+freethreaded in [PYTHON SOURCES]
");
// Request a implementation version that is compatible
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.11"), @r###"
uv_snapshot!(context.filters(), context.python_pin().arg("cpython@3.11"), @r"
success: true
exit_code: 0
----- stdout -----
Updated `.python-version` from `3.13t` -> `cpython@3.11`
Updated `.python-version` from `3.13+freethreaded` -> `cpython@3.11`
----- stderr -----
"###);
");
let python_version = context.read(PYTHON_VERSION_FILENAME);
insta::with_settings!({