Skip to content

Commit c9774e9

Browse files
authored
allow manylinux compatibility override via _manylinux module. (#6039)
## Summary resolves #5915, not entirely sure if `manylinux_compatible` should be a separate field in the JSON returned by the interpreter or there's some way to use the existing `platform` for it. ## Test Plan ran the below ``` rm -rf .venv target/debug/uv venv # commenting out the line below triggers the change.. # target/debug/uv pip install no-manylinux target/debug/uv pip install cryptography --no-cache ``` is there an easy way to add this into the existing snapshot-based test suite? looking around to see if there's a way that doesn't involve something implementation-dependent like mocks. ~update: i think the output does differ between these two, so probably we can use that.~ i lied - that "building..." output seems to be discarded.
1 parent 2e02d57 commit c9774e9

File tree

7 files changed

+128
-4
lines changed

7 files changed

+128
-4
lines changed

crates/bench/benches/uv.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ mod resolver {
127127
Arch::Aarch64,
128128
);
129129

130-
static TAGS: LazyLock<Tags> =
131-
LazyLock::new(|| Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false).unwrap());
130+
static TAGS: LazyLock<Tags> = LazyLock::new(|| {
131+
Tags::from_env(&PLATFORM, (3, 11), "cpython", (3, 11), false, false).unwrap()
132+
});
132133

133134
pub(crate) async fn resolve(
134135
manifest: Manifest,

crates/platform-tags/src/tags.rs

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,19 @@ impl Tags {
9494
python_version: (u8, u8),
9595
implementation_name: &str,
9696
implementation_version: (u8, u8),
97+
manylinux_compatible: bool,
9798
gil_disabled: bool,
9899
) -> Result<Self, TagsError> {
99100
let implementation = Implementation::parse(implementation_name, gil_disabled)?;
100-
let platform_tags = compatible_tags(platform)?;
101+
102+
// Determine the compatible tags for the current platform.
103+
let platform_tags = {
104+
let mut platform_tags = compatible_tags(platform)?;
105+
if matches!(platform.os(), Os::Manylinux { .. }) && !manylinux_compatible {
106+
platform_tags.retain(|tag| !tag.starts_with("manylinux"));
107+
}
108+
platform_tags
109+
};
101110

102111
let mut tags = Vec::with_capacity(5 * platform_tags.len());
103112

@@ -931,6 +940,64 @@ mod tests {
931940
);
932941
}
933942

943+
/// Ensure the tags returned do not include the `manylinux` tags
944+
/// when `manylinux_incompatible` is set to `false`.
945+
#[test]
946+
fn test_manylinux_incompatible() {
947+
let tags = Tags::from_env(
948+
&Platform::new(
949+
Os::Manylinux {
950+
major: 2,
951+
minor: 28,
952+
},
953+
Arch::X86_64,
954+
),
955+
(3, 9),
956+
"cpython",
957+
(3, 9),
958+
false,
959+
false,
960+
)
961+
.unwrap();
962+
assert_snapshot!(
963+
tags,
964+
@r###"
965+
cp39-cp39-linux_x86_64
966+
cp39-abi3-linux_x86_64
967+
cp39-none-linux_x86_64
968+
cp38-abi3-linux_x86_64
969+
cp37-abi3-linux_x86_64
970+
cp36-abi3-linux_x86_64
971+
cp35-abi3-linux_x86_64
972+
cp34-abi3-linux_x86_64
973+
cp33-abi3-linux_x86_64
974+
cp32-abi3-linux_x86_64
975+
py39-none-linux_x86_64
976+
py3-none-linux_x86_64
977+
py38-none-linux_x86_64
978+
py37-none-linux_x86_64
979+
py36-none-linux_x86_64
980+
py35-none-linux_x86_64
981+
py34-none-linux_x86_64
982+
py33-none-linux_x86_64
983+
py32-none-linux_x86_64
984+
py31-none-linux_x86_64
985+
py30-none-linux_x86_64
986+
cp39-none-any
987+
py39-none-any
988+
py3-none-any
989+
py38-none-any
990+
py37-none-any
991+
py36-none-any
992+
py35-none-any
993+
py34-none-any
994+
py33-none-any
995+
py32-none-any
996+
py31-none-any
997+
py30-none-any
998+
"###);
999+
}
1000+
9341001
/// Check full tag ordering.
9351002
/// The list is displayed in decreasing priority.
9361003
///
@@ -951,6 +1018,7 @@ mod tests {
9511018
(3, 9),
9521019
"cpython",
9531020
(3, 9),
1021+
true,
9541022
false,
9551023
)
9561024
.unwrap();
@@ -1575,6 +1643,7 @@ mod tests {
15751643
"cpython",
15761644
(3, 9),
15771645
false,
1646+
false,
15781647
)
15791648
.unwrap();
15801649
assert_snapshot!(

crates/uv-python/python/get_interpreter_info.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,17 @@ def main() -> None:
542542
"python_version": ".".join(platform.python_version_tuple()[:2]),
543543
"sys_platform": sys.platform,
544544
}
545+
os_and_arch = get_operating_system_and_architecture()
546+
547+
manylinux_compatible = True
548+
if os_and_arch["os"]["name"] == "manylinux":
549+
# noinspection PyProtectedMember
550+
from .packaging._manylinux import _get_glibc_version, _is_compatible
551+
552+
manylinux_compatible = _is_compatible(
553+
arch=os_and_arch["arch"], version=_get_glibc_version()
554+
)
555+
545556
interpreter_info = {
546557
"result": "success",
547558
"markers": markers,
@@ -554,7 +565,8 @@ def main() -> None:
554565
"stdlib": sysconfig.get_path("stdlib"),
555566
"scheme": get_scheme(),
556567
"virtualenv": get_virtualenv(),
557-
"platform": get_operating_system_and_architecture(),
568+
"platform": os_and_arch,
569+
"manylinux_compatible": manylinux_compatible,
558570
# The `t` abiflag for freethreading Python.
559571
# https://peps.python.org/pep-0703/#build-configuration-changes
560572
"gil_disabled": bool(sysconfig.get_config_var("Py_GIL_DISABLED")),

crates/uv-python/src/interpreter.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub struct Interpreter {
3333
markers: Box<MarkerEnvironment>,
3434
scheme: Scheme,
3535
virtualenv: Scheme,
36+
manylinux_compatible: bool,
3637
sys_prefix: PathBuf,
3738
sys_base_exec_prefix: PathBuf,
3839
sys_base_prefix: PathBuf,
@@ -63,6 +64,7 @@ impl Interpreter {
6364
markers: Box::new(info.markers),
6465
scheme: info.scheme,
6566
virtualenv: info.virtualenv,
67+
manylinux_compatible: info.manylinux_compatible,
6668
sys_prefix: info.sys_prefix,
6769
sys_base_exec_prefix: info.sys_base_exec_prefix,
6870
pointer_size: info.pointer_size,
@@ -176,6 +178,7 @@ impl Interpreter {
176178
self.python_tuple(),
177179
self.implementation_name(),
178180
self.implementation_tuple(),
181+
self.manylinux_compatible,
179182
self.gil_disabled,
180183
)?;
181184
self.tags.set(tags).expect("tags should not be set");
@@ -373,6 +376,11 @@ impl Interpreter {
373376
&self.virtualenv
374377
}
375378

379+
/// Return whether this interpreter is `manylinux` compatible.
380+
pub fn manylinux_compatible(&self) -> bool {
381+
self.manylinux_compatible
382+
}
383+
376384
/// Return the [`PointerSize`] of the Python interpreter (i.e., 32- vs. 64-bit).
377385
pub fn pointer_size(&self) -> PointerSize {
378386
self.pointer_size
@@ -555,6 +563,7 @@ struct InterpreterInfo {
555563
markers: MarkerEnvironment,
556564
scheme: Scheme,
557565
virtualenv: Scheme,
566+
manylinux_compatible: bool,
558567
sys_prefix: PathBuf,
559568
sys_base_exec_prefix: PathBuf,
560569
sys_base_prefix: PathBuf,
@@ -785,6 +794,7 @@ mod tests {
785794
},
786795
"arch": "x86_64"
787796
},
797+
"manylinux_compatible": false,
788798
"markers": {
789799
"implementation_name": "cpython",
790800
"implementation_version": "3.12.0",

crates/uv-python/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ mod tests {
210210
},
211211
"arch": "x86_64"
212212
},
213+
"manylinux_compatible": true,
213214
"markers": {
214215
"implementation_name": "{IMPLEMENTATION}",
215216
"implementation_version": "{FULL_VERSION}",

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,23 @@ pub(crate) fn resolution_environment(
2929
(python_version.major(), python_version.minor()),
3030
interpreter.implementation_name(),
3131
interpreter.implementation_tuple(),
32+
interpreter.manylinux_compatible(),
3233
interpreter.gil_disabled(),
3334
)?),
3435
(Some(python_platform), None) => Cow::Owned(Tags::from_env(
3536
&python_platform.platform(),
3637
interpreter.python_tuple(),
3738
interpreter.implementation_name(),
3839
interpreter.implementation_tuple(),
40+
interpreter.manylinux_compatible(),
3941
interpreter.gil_disabled(),
4042
)?),
4143
(None, Some(python_version)) => Cow::Owned(Tags::from_env(
4244
interpreter.platform(),
4345
(python_version.major(), python_version.minor()),
4446
interpreter.implementation_name(),
4547
interpreter.implementation_tuple(),
48+
interpreter.manylinux_compatible(),
4649
interpreter.gil_disabled(),
4750
)?),
4851
(None, None) => Cow::Borrowed(interpreter.tags()?),

docs/pip/compatibility.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,34 @@ reuse any binary distributions that are already present in the local cache.
346346
Additionally, and in contrast to pip, uv's resolver will still read metadata from pre-built binary
347347
distributions when `--no-binary` is provided.
348348

349+
## `manylinux_compatible` enforcement
350+
351+
[PEP 600](https://peps.python.org/pep-0600/#package-installers) describes a mechanism through which
352+
Python distributors can opt out of `manylinux` compatibility by defining a `manylinux_compatible`
353+
function on the `_manylinux` standard library module.
354+
355+
uv respects `manylinux_compatible`, but only tests against the current glibc version, and applies
356+
the return value of `manylinux_compatible` globally.
357+
358+
In other words, if `manylinux_compatible` returns `True`, uv will treat the system as
359+
`manylinux`-compatible; if it returns `False`, uv will treat the system as `manylinux`-incompatible,
360+
without calling `manylinux_compatible` for every glibc version.
361+
362+
This approach is not a complete implementation of the spec, but is compatible with common blanket
363+
`manylinux_compatible` implementations like
364+
[`no-manylinux`](https://pypi.org/project/no-manylinux/):
365+
366+
```python
367+
from __future__ import annotations
368+
manylinux1_compatible = False
369+
manylinux2010_compatible = False
370+
manylinux2014_compatible = False
371+
372+
373+
def manylinux_compatible(*_, **__): # PEP 600
374+
return False
375+
```
376+
349377
## Bytecode compilation
350378

351379
Unlike pip, uv does not compile `.py` files to `.pyc` files during installation by default (i.e., uv

0 commit comments

Comments
 (0)