Skip to content

Commit 60a811e

Browse files
authored
Allow Python requests to include +gil to require a GIL-enabled interpreter (#16537)
Addresses #16142 (comment) 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.
1 parent a87a3bb commit 60a811e

File tree

12 files changed

+97
-39
lines changed

12 files changed

+97
-39
lines changed

crates/uv-python/src/discovery.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ pub enum PythonVariant {
170170
Debug,
171171
Freethreaded,
172172
FreethreadedDebug,
173+
Gil,
174+
GilDebug,
173175
}
174176

175177
/// A Python discovery version request.
@@ -1685,41 +1687,57 @@ impl PythonVariant {
16851687
Self::Debug => interpreter.debug_enabled(),
16861688
Self::Freethreaded => interpreter.gil_disabled(),
16871689
Self::FreethreadedDebug => interpreter.gil_disabled() && interpreter.debug_enabled(),
1690+
Self::Gil => !interpreter.gil_disabled(),
1691+
Self::GilDebug => !interpreter.gil_disabled() && interpreter.debug_enabled(),
16881692
}
16891693
}
16901694

16911695
/// Return the executable suffix for the variant, e.g., `t` for `python3.13t`.
16921696
///
16931697
/// Returns an empty string for the default Python variant.
1694-
pub fn suffix(self) -> &'static str {
1698+
pub fn executable_suffix(self) -> &'static str {
16951699
match self {
16961700
Self::Default => "",
16971701
Self::Debug => "d",
16981702
Self::Freethreaded => "t",
16991703
Self::FreethreadedDebug => "td",
1704+
Self::Gil => "",
1705+
Self::GilDebug => "d",
1706+
}
1707+
}
1708+
1709+
/// Return the suffix for display purposes, e.g., `+gil`.
1710+
pub fn display_suffix(self) -> &'static str {
1711+
match self {
1712+
Self::Default => "",
1713+
Self::Debug => "+debug",
1714+
Self::Freethreaded => "+freethreaded",
1715+
Self::FreethreadedDebug => "+freethreaded+debug",
1716+
Self::Gil => "+gil",
1717+
Self::GilDebug => "+gil+debug",
17001718
}
17011719
}
17021720

17031721
/// Return the lib suffix for the variant, e.g., `t` for `python3.13t` but an empty string for
17041722
/// `python3.13d` or `python3.13`.
17051723
pub fn lib_suffix(self) -> &'static str {
17061724
match self {
1707-
Self::Default | Self::Debug => "",
1725+
Self::Default | Self::Debug | Self::Gil | Self::GilDebug => "",
17081726
Self::Freethreaded | Self::FreethreadedDebug => "t",
17091727
}
17101728
}
17111729

17121730
pub fn is_freethreaded(self) -> bool {
17131731
match self {
1714-
Self::Default | Self::Debug => false,
1732+
Self::Default | Self::Debug | Self::Gil | Self::GilDebug => false,
17151733
Self::Freethreaded | Self::FreethreadedDebug => true,
17161734
}
17171735
}
17181736

17191737
pub fn is_debug(self) -> bool {
17201738
match self {
1721-
Self::Default | Self::Freethreaded => false,
1722-
Self::Debug | Self::FreethreadedDebug => true,
1739+
Self::Default | Self::Freethreaded | Self::Gil => false,
1740+
Self::Debug | Self::FreethreadedDebug | Self::GilDebug => true,
17231741
}
17241742
}
17251743
}
@@ -2450,7 +2468,7 @@ impl fmt::Display for ExecutableName {
24502468
if let Some(prerelease) = &self.prerelease {
24512469
write!(f, "{prerelease}")?;
24522470
}
2453-
f.write_str(self.variant.suffix())?;
2471+
f.write_str(self.variant.executable_suffix())?;
24542472
f.write_str(EXE_SUFFIX)?;
24552473
Ok(())
24562474
}
@@ -3067,6 +3085,8 @@ impl FromStr for PythonVariant {
30673085
"t" | "freethreaded" => Ok(Self::Freethreaded),
30683086
"d" | "debug" => Ok(Self::Debug),
30693087
"td" | "freethreaded+debug" => Ok(Self::FreethreadedDebug),
3088+
"gil" => Ok(Self::Gil),
3089+
"gil+debug" => Ok(Self::GilDebug),
30703090
"" => Ok(Self::Default),
30713091
_ => Err(()),
30723092
}
@@ -3080,6 +3100,8 @@ impl fmt::Display for PythonVariant {
30803100
Self::Debug => f.write_str("debug"),
30813101
Self::Freethreaded => f.write_str("freethreaded"),
30823102
Self::FreethreadedDebug => f.write_str("freethreaded+debug"),
3103+
Self::Gil => f.write_str("gil"),
3104+
Self::GilDebug => f.write_str("gil+debug"),
30833105
}
30843106
}
30853107
}
@@ -3109,15 +3131,15 @@ impl fmt::Display for VersionRequest {
31093131
match self {
31103132
Self::Any => f.write_str("any"),
31113133
Self::Default => f.write_str("default"),
3112-
Self::Major(major, variant) => write!(f, "{major}{}", variant.suffix()),
3134+
Self::Major(major, variant) => write!(f, "{major}{}", variant.display_suffix()),
31133135
Self::MajorMinor(major, minor, variant) => {
3114-
write!(f, "{major}.{minor}{}", variant.suffix())
3136+
write!(f, "{major}.{minor}{}", variant.display_suffix())
31153137
}
31163138
Self::MajorMinorPatch(major, minor, patch, variant) => {
3117-
write!(f, "{major}.{minor}.{patch}{}", variant.suffix())
3139+
write!(f, "{major}.{minor}.{patch}{}", variant.display_suffix())
31183140
}
31193141
Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
3120-
write!(f, "{major}.{minor}{prerelease}{}", variant.suffix())
3142+
write!(f, "{major}.{minor}{prerelease}{}", variant.display_suffix())
31213143
}
31223144
Self::Range(specifiers, _) => write!(f, "{specifiers}"),
31233145
}

crates/uv-python/src/installation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ impl PythonInstallationKey {
460460
"python{maj}.{min}{var}{exe}",
461461
maj = self.major,
462462
min = self.minor,
463-
var = self.variant.suffix(),
463+
var = self.variant.executable_suffix(),
464464
exe = std::env::consts::EXE_SUFFIX
465465
)
466466
}
@@ -470,7 +470,7 @@ impl PythonInstallationKey {
470470
format!(
471471
"python{maj}{var}{exe}",
472472
maj = self.major,
473-
var = self.variant.suffix(),
473+
var = self.variant.executable_suffix(),
474474
exe = std::env::consts::EXE_SUFFIX
475475
)
476476
}
@@ -479,7 +479,7 @@ impl PythonInstallationKey {
479479
pub fn executable_name(&self) -> String {
480480
format!(
481481
"python{var}{exe}",
482-
var = self.variant.suffix(),
482+
var = self.variant.executable_suffix(),
483483
exe = std::env::consts::EXE_SUFFIX
484484
)
485485
}

crates/uv-python/src/interpreter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ impl Interpreter {
173173
base_executable,
174174
self.python_major(),
175175
self.python_minor(),
176-
self.variant().suffix(),
176+
self.variant().executable_suffix(),
177177
) {
178178
Ok(path) => path,
179179
Err(err) => {

crates/uv-python/src/managed.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ impl ManagedPythonInstallation {
392392
let variant = if self.implementation() == ImplementationName::GraalPy {
393393
""
394394
} else if cfg!(unix) {
395-
self.key.variant.suffix()
395+
self.key.variant.executable_suffix()
396396
} else if cfg!(windows) && windowed {
397397
// Use windowed Python that doesn't open a terminal.
398398
"w"
@@ -626,7 +626,7 @@ impl ManagedPythonInstallation {
626626
"{}python{}{}{}",
627627
std::env::consts::DLL_PREFIX,
628628
self.key.version().python_version(),
629-
self.key.variant().suffix(),
629+
self.key.variant().executable_suffix(),
630630
std::env::consts::DLL_SUFFIX
631631
));
632632
macos_dylib::patch_dylib_install_name(dylib_path)?;

crates/uv/src/commands/pip/operations.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ pub(crate) fn report_interpreter(
746746
"Using {} {}{}",
747747
implementation.pretty(),
748748
interpreter.python_version(),
749-
interpreter.variant().suffix(),
749+
interpreter.variant().display_suffix(),
750750
)
751751
.dimmed()
752752
)?;
@@ -758,7 +758,7 @@ pub(crate) fn report_interpreter(
758758
"Using {} {}{} interpreter at: {}",
759759
implementation.pretty(),
760760
interpreter.python_version(),
761-
interpreter.variant().suffix(),
761+
interpreter.variant().display_suffix(),
762762
interpreter.sys_executable().user_display()
763763
)
764764
.dimmed()
@@ -771,15 +771,15 @@ pub(crate) fn report_interpreter(
771771
"Using {} {}{}",
772772
implementation.pretty(),
773773
interpreter.python_version().cyan(),
774-
interpreter.variant().suffix().cyan()
774+
interpreter.variant().display_suffix().cyan()
775775
)?;
776776
} else {
777777
writeln!(
778778
printer.stderr(),
779779
"Using {} {}{} interpreter at: {}",
780780
implementation.pretty(),
781781
interpreter.python_version(),
782-
interpreter.variant().suffix(),
782+
interpreter.variant().display_suffix(),
783783
interpreter.sys_executable().user_display().cyan()
784784
)?;
785785
}

crates/uv/src/commands/project/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,15 +1011,15 @@ impl ProjectInterpreter {
10111011
"Using {} {}{}",
10121012
implementation.pretty(),
10131013
interpreter.python_version().cyan(),
1014-
interpreter.variant().suffix().cyan(),
1014+
interpreter.variant().display_suffix().cyan(),
10151015
)?;
10161016
} else {
10171017
writeln!(
10181018
printer.stderr(),
10191019
"Using {} {}{} interpreter at: {}",
10201020
implementation.pretty(),
10211021
interpreter.python_version(),
1022-
interpreter.variant().suffix(),
1022+
interpreter.variant().display_suffix(),
10231023
interpreter.sys_executable().user_display().cyan()
10241024
)?;
10251025
}

crates/uv/src/commands/python/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ fn create_bin_links(
859859
to.simplified_display(),
860860
installation.key().major(),
861861
installation.key().minor(),
862-
installation.key().variant().suffix()
862+
installation.key().variant().display_suffix()
863863
);
864864
} else {
865865
errors.push((

crates/uv/tests/it/init.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3968,7 +3968,7 @@ fn init_without_description() -> Result<()> {
39683968

39693969
/// Run `uv init --python 3.13t` to create a pin to a freethreaded Python.
39703970
#[test]
3971-
fn init_python_variant() -> Result<()> {
3971+
fn init_python_variant() {
39723972
let context = TestContext::new("3.13");
39733973
uv_snapshot!(context.filters(), context.init().arg("foo").arg("--python").arg("3.13t"), @r###"
39743974
success: true
@@ -3979,10 +3979,8 @@ fn init_python_variant() -> Result<()> {
39793979
Initialized project `foo` at `[TEMP_DIR]/foo`
39803980
"###);
39813981

3982-
let python_version = fs_err::read_to_string(context.temp_dir.join("foo/.python-version"))?;
3983-
assert_eq!(python_version, "3.13t\n");
3984-
3985-
Ok(())
3982+
let contents = context.read("foo/.python-version");
3983+
assert_snapshot!(contents, @"3.13+freethreaded");
39863984
}
39873985

39883986
/// Check how `uv init` reacts to working and broken git with different `--vcs` options.

crates/uv/tests/it/python_find.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -850,14 +850,14 @@ fn python_find_unsupported_version() {
850850
"###);
851851

852852
// Request free-threaded Python on unsupported version
853-
uv_snapshot!(context.filters(), context.python_find().arg("3.12t"), @r###"
853+
uv_snapshot!(context.filters(), context.python_find().arg("3.12t"), @r"
854854
success: false
855855
exit_code: 2
856856
----- stdout -----
857857
858858
----- stderr -----
859-
error: Invalid version request: Python <3.13 does not support free-threading but 3.12t was requested.
860-
"###);
859+
error: Invalid version request: Python <3.13 does not support free-threading but 3.12+freethreaded was requested.
860+
");
861861
}
862862

863863
#[test]
@@ -1334,6 +1334,44 @@ fn python_find_freethreaded_314() {
13341334
13351335
----- stderr -----
13361336
");
1337+
1338+
// Request Python 3.14+gil
1339+
uv_snapshot!(context.filters(), context.python_find().arg("3.14+gil"), @r"
1340+
success: false
1341+
exit_code: 2
1342+
----- stdout -----
1343+
1344+
----- stderr -----
1345+
error: No interpreter found for Python 3.14+gil in [PYTHON SOURCES]
1346+
");
1347+
1348+
// Install the non-freethreaded version
1349+
context
1350+
.python_install()
1351+
.arg("--preview")
1352+
.arg("3.14")
1353+
.assert()
1354+
.success();
1355+
1356+
// Request Python 3.14
1357+
uv_snapshot!(context.filters(), context.python_find().arg("3.14"), @r"
1358+
success: true
1359+
exit_code: 0
1360+
----- stdout -----
1361+
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
1362+
1363+
----- stderr -----
1364+
");
1365+
1366+
// Request Python 3.14+gil
1367+
uv_snapshot!(context.filters(), context.python_find().arg("3.14+gil"), @r"
1368+
success: true
1369+
exit_code: 0
1370+
----- stdout -----
1371+
[TEMP_DIR]/managed/cpython-3.14-[PLATFORM]/[INSTALL-BIN]/[PYTHON]
1372+
1373+
----- stderr -----
1374+
");
13371375
}
13381376

13391377
#[test]

crates/uv/tests/it/python_install.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ fn python_install_freethreaded() {
11301130
----- stdout -----
11311131
11321132
----- stderr -----
1133-
Using CPython 3.13.9t
1133+
Using CPython 3.13.9+freethreaded
11341134
Creating virtual environment at: .venv
11351135
Activate with: source .venv/[BIN]/activate
11361136
");
@@ -1200,7 +1200,7 @@ fn python_install_freethreaded() {
12001200
----- stdout -----
12011201
12021202
----- stderr -----
1203-
error: No download found for request: cpython-3.12t-[PLATFORM]
1203+
error: No download found for request: cpython-3.12+freethreaded-[PLATFORM]
12041204
");
12051205

12061206
uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r"

0 commit comments

Comments
 (0)