Skip to content

Commit

Permalink
allow manylinux compatibility override via _manylinux module. (#6039)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
ChannyClaus committed Aug 21, 2024
1 parent 2e02d57 commit c9774e9
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 4 deletions.
5 changes: 3 additions & 2 deletions crates/bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ mod resolver {
Arch::Aarch64,
);

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

pub(crate) async fn resolve(
manifest: Manifest,
Expand Down
71 changes: 70 additions & 1 deletion crates/platform-tags/src/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,19 @@ impl Tags {
python_version: (u8, u8),
implementation_name: &str,
implementation_version: (u8, u8),
manylinux_compatible: bool,
gil_disabled: bool,
) -> Result<Self, TagsError> {
let implementation = Implementation::parse(implementation_name, gil_disabled)?;
let platform_tags = compatible_tags(platform)?;

// Determine the compatible tags for the current platform.
let platform_tags = {
let mut platform_tags = compatible_tags(platform)?;
if matches!(platform.os(), Os::Manylinux { .. }) && !manylinux_compatible {
platform_tags.retain(|tag| !tag.starts_with("manylinux"));
}
platform_tags
};

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

Expand Down Expand Up @@ -931,6 +940,64 @@ mod tests {
);
}

/// Ensure the tags returned do not include the `manylinux` tags
/// when `manylinux_incompatible` is set to `false`.
#[test]
fn test_manylinux_incompatible() {
let tags = Tags::from_env(
&Platform::new(
Os::Manylinux {
major: 2,
minor: 28,
},
Arch::X86_64,
),
(3, 9),
"cpython",
(3, 9),
false,
false,
)
.unwrap();
assert_snapshot!(
tags,
@r###"
cp39-cp39-linux_x86_64
cp39-abi3-linux_x86_64
cp39-none-linux_x86_64
cp38-abi3-linux_x86_64
cp37-abi3-linux_x86_64
cp36-abi3-linux_x86_64
cp35-abi3-linux_x86_64
cp34-abi3-linux_x86_64
cp33-abi3-linux_x86_64
cp32-abi3-linux_x86_64
py39-none-linux_x86_64
py3-none-linux_x86_64
py38-none-linux_x86_64
py37-none-linux_x86_64
py36-none-linux_x86_64
py35-none-linux_x86_64
py34-none-linux_x86_64
py33-none-linux_x86_64
py32-none-linux_x86_64
py31-none-linux_x86_64
py30-none-linux_x86_64
cp39-none-any
py39-none-any
py3-none-any
py38-none-any
py37-none-any
py36-none-any
py35-none-any
py34-none-any
py33-none-any
py32-none-any
py31-none-any
py30-none-any
"###);
}

/// Check full tag ordering.
/// The list is displayed in decreasing priority.
///
Expand All @@ -951,6 +1018,7 @@ mod tests {
(3, 9),
"cpython",
(3, 9),
true,
false,
)
.unwrap();
Expand Down Expand Up @@ -1575,6 +1643,7 @@ mod tests {
"cpython",
(3, 9),
false,
false,
)
.unwrap();
assert_snapshot!(
Expand Down
14 changes: 13 additions & 1 deletion crates/uv-python/python/get_interpreter_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,17 @@ def main() -> None:
"python_version": ".".join(platform.python_version_tuple()[:2]),
"sys_platform": sys.platform,
}
os_and_arch = get_operating_system_and_architecture()

manylinux_compatible = True
if os_and_arch["os"]["name"] == "manylinux":
# noinspection PyProtectedMember
from .packaging._manylinux import _get_glibc_version, _is_compatible

manylinux_compatible = _is_compatible(
arch=os_and_arch["arch"], version=_get_glibc_version()
)

interpreter_info = {
"result": "success",
"markers": markers,
Expand All @@ -554,7 +565,8 @@ def main() -> None:
"stdlib": sysconfig.get_path("stdlib"),
"scheme": get_scheme(),
"virtualenv": get_virtualenv(),
"platform": get_operating_system_and_architecture(),
"platform": os_and_arch,
"manylinux_compatible": manylinux_compatible,
# 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")),
Expand Down
10 changes: 10 additions & 0 deletions crates/uv-python/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct Interpreter {
markers: Box<MarkerEnvironment>,
scheme: Scheme,
virtualenv: Scheme,
manylinux_compatible: bool,
sys_prefix: PathBuf,
sys_base_exec_prefix: PathBuf,
sys_base_prefix: PathBuf,
Expand Down Expand Up @@ -63,6 +64,7 @@ impl Interpreter {
markers: Box::new(info.markers),
scheme: info.scheme,
virtualenv: info.virtualenv,
manylinux_compatible: info.manylinux_compatible,
sys_prefix: info.sys_prefix,
sys_base_exec_prefix: info.sys_base_exec_prefix,
pointer_size: info.pointer_size,
Expand Down Expand Up @@ -176,6 +178,7 @@ impl Interpreter {
self.python_tuple(),
self.implementation_name(),
self.implementation_tuple(),
self.manylinux_compatible,
self.gil_disabled,
)?;
self.tags.set(tags).expect("tags should not be set");
Expand Down Expand Up @@ -373,6 +376,11 @@ impl Interpreter {
&self.virtualenv
}

/// Return whether this interpreter is `manylinux` compatible.
pub fn manylinux_compatible(&self) -> bool {
self.manylinux_compatible
}

/// Return the [`PointerSize`] of the Python interpreter (i.e., 32- vs. 64-bit).
pub fn pointer_size(&self) -> PointerSize {
self.pointer_size
Expand Down Expand Up @@ -555,6 +563,7 @@ struct InterpreterInfo {
markers: MarkerEnvironment,
scheme: Scheme,
virtualenv: Scheme,
manylinux_compatible: bool,
sys_prefix: PathBuf,
sys_base_exec_prefix: PathBuf,
sys_base_prefix: PathBuf,
Expand Down Expand Up @@ -785,6 +794,7 @@ mod tests {
},
"arch": "x86_64"
},
"manylinux_compatible": false,
"markers": {
"implementation_name": "cpython",
"implementation_version": "3.12.0",
Expand Down
1 change: 1 addition & 0 deletions crates/uv-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ mod tests {
},
"arch": "x86_64"
},
"manylinux_compatible": true,
"markers": {
"implementation_name": "{IMPLEMENTATION}",
"implementation_version": "{FULL_VERSION}",
Expand Down
3 changes: 3 additions & 0 deletions crates/uv/src/commands/pip/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,23 @@ pub(crate) fn resolution_environment(
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
interpreter.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(Some(python_platform), None) => Cow::Owned(Tags::from_env(
&python_platform.platform(),
interpreter.python_tuple(),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
interpreter.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(None, Some(python_version)) => Cow::Owned(Tags::from_env(
interpreter.platform(),
(python_version.major(), python_version.minor()),
interpreter.implementation_name(),
interpreter.implementation_tuple(),
interpreter.manylinux_compatible(),
interpreter.gil_disabled(),
)?),
(None, None) => Cow::Borrowed(interpreter.tags()?),
Expand Down
28 changes: 28 additions & 0 deletions docs/pip/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,34 @@ reuse any binary distributions that are already present in the local cache.
Additionally, and in contrast to pip, uv's resolver will still read metadata from pre-built binary
distributions when `--no-binary` is provided.

## `manylinux_compatible` enforcement

[PEP 600](https://peps.python.org/pep-0600/#package-installers) describes a mechanism through which
Python distributors can opt out of `manylinux` compatibility by defining a `manylinux_compatible`
function on the `_manylinux` standard library module.

uv respects `manylinux_compatible`, but only tests against the current glibc version, and applies
the return value of `manylinux_compatible` globally.

In other words, if `manylinux_compatible` returns `True`, uv will treat the system as
`manylinux`-compatible; if it returns `False`, uv will treat the system as `manylinux`-incompatible,
without calling `manylinux_compatible` for every glibc version.

This approach is not a complete implementation of the spec, but is compatible with common blanket
`manylinux_compatible` implementations like
[`no-manylinux`](https://pypi.org/project/no-manylinux/):

```python
from __future__ import annotations
manylinux1_compatible = False
manylinux2010_compatible = False
manylinux2014_compatible = False


def manylinux_compatible(*_, **__): # PEP 600
return False
```

## Bytecode compilation

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

0 comments on commit c9774e9

Please sign in to comment.