mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
Allow selection of debug build interpreters
This commit is contained in:
parent
9cdfad191c
commit
9991174276
8 changed files with 13586 additions and 31 deletions
|
@ -618,6 +618,8 @@ def main() -> None:
|
|||
# The `t` abiflag for freethreading Python.
|
||||
# https://peps.python.org/pep-0703/#build-configuration-changes
|
||||
"gil_disabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),
|
||||
# https://docs.python.org/3/using/configure.html#debug-build
|
||||
"debug_enabled": bool(sysconfig.get_config_var("Py_DEBUG")),
|
||||
# Determine if the interpreter is 32-bit or 64-bit.
|
||||
# https://github.com/python/cpython/blob/b228655c227b2ca298a8ffac44d14ce3d22f6faa/Lib/venv/__init__.py#L136
|
||||
"pointer_size": "64" if sys.maxsize > 2**32 else "32",
|
||||
|
|
|
@ -145,7 +145,9 @@ pub(crate) struct DiscoveryPreferences {
|
|||
pub enum PythonVariant {
|
||||
#[default]
|
||||
Default,
|
||||
Debug,
|
||||
Freethreaded,
|
||||
FreethreadedDebug,
|
||||
}
|
||||
|
||||
/// A Python discovery version request.
|
||||
|
@ -1344,17 +1346,32 @@ impl PythonVariant {
|
|||
fn matches_interpreter(self, interpreter: &Interpreter) -> bool {
|
||||
match self {
|
||||
PythonVariant::Default => !interpreter.gil_disabled(),
|
||||
PythonVariant::Debug => interpreter.debug_enabled(),
|
||||
PythonVariant::Freethreaded => interpreter.gil_disabled(),
|
||||
PythonVariant::FreethreadedDebug => {
|
||||
interpreter.gil_disabled() && interpreter.debug_enabled()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the lib or executable suffix for the variant, e.g., `t` for `python3.13t`.
|
||||
/// 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 {
|
||||
match self {
|
||||
Self::Default => "",
|
||||
Self::Debug => "d",
|
||||
Self::Freethreaded => "t",
|
||||
Self::FreethreadedDebug => "td",
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the lib suffix for the variant, e.g., `t` for `python3.13t` but an empty string for
|
||||
/// `python3.13d` or `python3.13`.
|
||||
pub fn lib_suffix(self) -> &'static str {
|
||||
match self {
|
||||
Self::Default | Self::Debug => "",
|
||||
Self::Freethreaded | Self::FreethreadedDebug => "t",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1978,12 +1995,14 @@ impl VersionRequest {
|
|||
}
|
||||
|
||||
// Include free-threaded variants
|
||||
if self.is_freethreaded() {
|
||||
if let Some(variant) = self.variant() {
|
||||
if variant != PythonVariant::Default {
|
||||
for i in 0..names.len() {
|
||||
let name = names[i].with_variant(PythonVariant::Freethreaded);
|
||||
let name = names[i].with_variant(variant);
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
names.sort_unstable_by_key(|name| name.into_comparator(self, implementation));
|
||||
names.reverse();
|
||||
|
@ -2272,7 +2291,9 @@ impl VersionRequest {
|
|||
| Self::MajorMinor(_, _, variant)
|
||||
| Self::MajorMinorPatch(_, _, _, variant)
|
||||
| Self::MajorMinorPrerelease(_, _, _, variant)
|
||||
| Self::Range(_, variant) => variant == &PythonVariant::Freethreaded,
|
||||
| Self::Range(_, variant) => {
|
||||
variant == &PythonVariant::Freethreaded || variant == &PythonVariant::Default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2317,11 +2338,23 @@ impl FromStr for VersionRequest {
|
|||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// Check if the version request is for a free-threaded Python version
|
||||
// Check if the version request is for a debug Python version
|
||||
let (s, variant) = s
|
||||
.strip_suffix('t')
|
||||
.map_or((s, PythonVariant::Default), |s| {
|
||||
(s, PythonVariant::Freethreaded)
|
||||
.strip_suffix('d')
|
||||
.map_or((s, PythonVariant::Default), |s| (s, PythonVariant::Debug));
|
||||
|
||||
// Check if the version request is for a free-threaded Python version
|
||||
let (s, variant) = s.strip_suffix('t').map_or((s, variant), |s| {
|
||||
(
|
||||
s,
|
||||
if variant == PythonVariant::Default {
|
||||
PythonVariant::Freethreaded
|
||||
} else if variant == PythonVariant::Debug {
|
||||
PythonVariant::FreethreadedDebug
|
||||
} else {
|
||||
unreachable!()
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
if variant == PythonVariant::Freethreaded && s.ends_with('t') {
|
||||
|
@ -2329,6 +2362,13 @@ impl FromStr for VersionRequest {
|
|||
return Err(Error::InvalidVersionRequest(format!("{s}t")));
|
||||
}
|
||||
|
||||
if variant == PythonVariant::Debug && s.ends_with('d') {
|
||||
// More than one trailing "d" is not allowed
|
||||
return Err(Error::InvalidVersionRequest(format!("{s}d")));
|
||||
}
|
||||
|
||||
// TODO(zanieb): Special-case error for use of `dt` instead of `td`
|
||||
|
||||
let Ok(version) = Version::from_str(s) else {
|
||||
return parse_version_specifiers_request(s, variant);
|
||||
};
|
||||
|
@ -2422,7 +2462,9 @@ impl fmt::Display for PythonVariant {
|
|||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => f.write_str("default"),
|
||||
Self::Debug => f.write_str("debug"),
|
||||
Self::Freethreaded => f.write_str("freethreaded"),
|
||||
Self::FreethreadedDebug => f.write_str("freethreaded+debug"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2452,23 +2494,15 @@ impl fmt::Display for VersionRequest {
|
|||
match self {
|
||||
Self::Any => f.write_str("any"),
|
||||
Self::Default => f.write_str("default"),
|
||||
Self::Major(major, PythonVariant::Default) => write!(f, "{major}"),
|
||||
Self::Major(major, PythonVariant::Freethreaded) => write!(f, "{major}t"),
|
||||
Self::MajorMinor(major, minor, PythonVariant::Default) => write!(f, "{major}.{minor}"),
|
||||
Self::MajorMinor(major, minor, PythonVariant::Freethreaded) => {
|
||||
write!(f, "{major}.{minor}t")
|
||||
Self::Major(major, variant) => write!(f, "{major}{}", variant.suffix()),
|
||||
Self::MajorMinor(major, minor, variant) => {
|
||||
write!(f, "{major}.{minor}{}", variant.suffix())
|
||||
}
|
||||
Self::MajorMinorPatch(major, minor, patch, PythonVariant::Default) => {
|
||||
write!(f, "{major}.{minor}.{patch}")
|
||||
Self::MajorMinorPatch(major, minor, patch, variant) => {
|
||||
write!(f, "{major}.{minor}.{patch}{}", variant.suffix())
|
||||
}
|
||||
Self::MajorMinorPatch(major, minor, patch, PythonVariant::Freethreaded) => {
|
||||
write!(f, "{major}.{minor}.{patch}t")
|
||||
}
|
||||
Self::MajorMinorPrerelease(major, minor, prerelease, PythonVariant::Default) => {
|
||||
write!(f, "{major}.{minor}{prerelease}")
|
||||
}
|
||||
Self::MajorMinorPrerelease(major, minor, prerelease, PythonVariant::Freethreaded) => {
|
||||
write!(f, "{major}.{minor}{prerelease}t")
|
||||
Self::MajorMinorPrerelease(major, minor, prerelease, variant) => {
|
||||
write!(f, "{major}.{minor}{prerelease}{}", variant.suffix())
|
||||
}
|
||||
Self::Range(specifiers, _) => write!(f, "{specifiers}"),
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -388,7 +388,7 @@ impl fmt::Display for PythonInstallationKey {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let variant = match self.variant {
|
||||
PythonVariant::Default => String::new(),
|
||||
PythonVariant::Freethreaded => format!("+{}", self.variant),
|
||||
_ => format!("+{}", self.variant),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
|
|
|
@ -34,6 +34,7 @@ use crate::{
|
|||
};
|
||||
|
||||
/// A Python executable and its associated platform markers.
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Interpreter {
|
||||
platform: Platform,
|
||||
|
@ -54,6 +55,7 @@ pub struct Interpreter {
|
|||
prefix: Option<Prefix>,
|
||||
pointer_size: PointerSize,
|
||||
gil_disabled: bool,
|
||||
debug_enabled: bool,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
|
@ -77,6 +79,7 @@ impl Interpreter {
|
|||
sys_base_exec_prefix: info.sys_base_exec_prefix,
|
||||
pointer_size: info.pointer_size,
|
||||
gil_disabled: info.gil_disabled,
|
||||
debug_enabled: info.debug_enabled,
|
||||
sys_base_prefix: info.sys_base_prefix,
|
||||
sys_base_executable: info.sys_base_executable,
|
||||
sys_executable: info.sys_executable,
|
||||
|
@ -206,7 +209,13 @@ impl Interpreter {
|
|||
|
||||
pub fn variant(&self) -> PythonVariant {
|
||||
if self.gil_disabled() {
|
||||
if self.debug_enabled() {
|
||||
PythonVariant::FreethreadedDebug
|
||||
} else {
|
||||
PythonVariant::Freethreaded
|
||||
}
|
||||
} else if self.debug_enabled() {
|
||||
PythonVariant::Debug
|
||||
} else {
|
||||
PythonVariant::default()
|
||||
}
|
||||
|
@ -452,6 +461,12 @@ impl Interpreter {
|
|||
self.gil_disabled
|
||||
}
|
||||
|
||||
/// Return whether this is a debug build of Python, as specified by the sysconfig var
|
||||
/// `Py_DEBUG`.
|
||||
pub fn debug_enabled(&self) -> bool {
|
||||
self.debug_enabled
|
||||
}
|
||||
|
||||
/// Return the `--target` directory for this interpreter, if any.
|
||||
pub fn target(&self) -> Option<&Target> {
|
||||
self.target.as_ref()
|
||||
|
@ -713,6 +728,7 @@ pub enum InterpreterInfoError {
|
|||
},
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
struct InterpreterInfo {
|
||||
platform: Platform,
|
||||
|
@ -730,6 +746,7 @@ struct InterpreterInfo {
|
|||
standalone: bool,
|
||||
pointer_size: PointerSize,
|
||||
gil_disabled: bool,
|
||||
debug_enabled: bool,
|
||||
}
|
||||
|
||||
impl InterpreterInfo {
|
||||
|
@ -1084,6 +1101,7 @@ mod tests {
|
|||
},
|
||||
"pointer_size": "64",
|
||||
"gil_disabled": true
|
||||
"debug_enabled": false
|
||||
}
|
||||
"##};
|
||||
|
||||
|
|
|
@ -506,7 +506,7 @@ impl ManagedPythonInstallation {
|
|||
let stdlib = if matches!(self.key.os, Os(target_lexicon::OperatingSystem::Windows)) {
|
||||
self.python_dir().join("Lib")
|
||||
} else {
|
||||
let lib_suffix = self.key.variant.suffix();
|
||||
let lib_suffix = self.key.variant.lib_suffix();
|
||||
let python = if matches!(
|
||||
self.key.implementation,
|
||||
LenientImplementationName::Known(ImplementationName::PyPy)
|
||||
|
|
|
@ -56,6 +56,8 @@ def prepare_variant(variant: str | None) -> str | None:
|
|||
return "PythonVariant::Default"
|
||||
case "freethreaded":
|
||||
return "PythonVariant::Freethreaded"
|
||||
case "freethreaded+debug":
|
||||
return "PythonVariant::FreethreadedDebug"
|
||||
case "debug":
|
||||
return "PythonVariant::Debug"
|
||||
case _:
|
||||
|
@ -126,8 +128,6 @@ def main() -> None:
|
|||
data["versions"] = [
|
||||
{"key": key, "value": prepare_value(value)}
|
||||
for key, value in json.loads(VERSION_METADATA.read_text()).items()
|
||||
# Exclude debug variants for now, we don't support them in the Rust side
|
||||
if value["variant"] != "debug"
|
||||
]
|
||||
|
||||
# Render the template
|
||||
|
|
|
@ -660,6 +660,79 @@ fn python_install_freethreaded() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_install_debug() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
.with_filtered_python_keys()
|
||||
.with_filtered_exe_suffix()
|
||||
.with_managed_python_dirs();
|
||||
|
||||
// Install the latest version
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13d"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No download found for request: cpython-3.13d-[PLATFORM]
|
||||
");
|
||||
|
||||
let bin_python = context
|
||||
.bin_dir
|
||||
.child(format!("python3.13d{}", std::env::consts::EXE_SUFFIX));
|
||||
|
||||
// The executable should be installed in the bin directory
|
||||
bin_python.assert(predicate::path::exists());
|
||||
|
||||
// On Unix, it should be a link
|
||||
#[cfg(unix)]
|
||||
bin_python.assert(predicate::path::is_symlink());
|
||||
|
||||
// The executable should "work"
|
||||
uv_snapshot!(context.filters(), Command::new(bin_python.as_os_str())
|
||||
.arg("-c").arg("import subprocess; print('hello world')"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
hello world
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
// Should be distinct from 3.13
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.13"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Installed Python 3.13.2 in [TIME]
|
||||
+ cpython-3.13.2-[PLATFORM]
|
||||
"###);
|
||||
|
||||
// Should work with older Python versions too
|
||||
uv_snapshot!(context.filters(), context.python_install().arg("3.12d"), @r###"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: No download found for request: cpython-3.12d-[PLATFORM]
|
||||
"###);
|
||||
|
||||
uv_snapshot!(context.filters(), context.python_uninstall().arg("--all"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Searching for Python installations
|
||||
Uninstalled 2 versions in [TIME]
|
||||
- cpython-3.13.2-[PLATFORM]
|
||||
- cpython-3.13.2+freethreaded-[PLATFORM] (python3.13t)
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_install_invalid_request() {
|
||||
let context: TestContext = TestContext::new_with_versions(&[])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue