mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Separate platform tags (#18)
This commit is contained in:
parent
9ea6eaeb10
commit
47bbb7a78e
7 changed files with 300 additions and 281 deletions
|
@ -5,6 +5,7 @@ use anyhow::Result;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use puffin_interpreter::PythonExecutable;
|
use puffin_interpreter::PythonExecutable;
|
||||||
|
use puffin_platform::tags::Tags;
|
||||||
use puffin_platform::Platform;
|
use puffin_platform::Platform;
|
||||||
use puffin_resolve::resolve;
|
use puffin_resolve::resolve;
|
||||||
|
|
||||||
|
@ -25,15 +26,14 @@ pub(crate) async fn compile(src: &Path, cache: Option<&Path>) -> Result<ExitStat
|
||||||
python.executable().display()
|
python.executable().display()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Determine the current environment markers.
|
||||||
|
let markers = python.markers();
|
||||||
|
|
||||||
|
// Determine the compatible platform tags.
|
||||||
|
let tags = Tags::from_env(&platform, python.version())?;
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolution = resolve(
|
let resolution = resolve(&requirements, markers, &tags, cache).await?;
|
||||||
&requirements,
|
|
||||||
python.version(),
|
|
||||||
python.markers(),
|
|
||||||
&platform,
|
|
||||||
cache,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for (name, version) in resolution.iter() {
|
for (name, version) in resolution.iter() {
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
|
|
|
@ -5,6 +5,7 @@ use anyhow::Result;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use puffin_interpreter::PythonExecutable;
|
use puffin_interpreter::PythonExecutable;
|
||||||
|
use puffin_platform::tags::Tags;
|
||||||
use puffin_platform::Platform;
|
use puffin_platform::Platform;
|
||||||
use puffin_resolve::resolve;
|
use puffin_resolve::resolve;
|
||||||
|
|
||||||
|
@ -25,15 +26,14 @@ pub(crate) async fn install(src: &Path, cache: Option<&Path>) -> Result<ExitStat
|
||||||
python.executable().display()
|
python.executable().display()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Determine the current environment markers.
|
||||||
|
let markers = python.markers();
|
||||||
|
|
||||||
|
// Determine the compatible platform tags.
|
||||||
|
let tags = Tags::from_env(&platform, python.version())?;
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolution = resolve(
|
let resolution = resolve(&requirements, markers, &tags, cache).await?;
|
||||||
&requirements,
|
|
||||||
python.version(),
|
|
||||||
python.markers(),
|
|
||||||
&platform,
|
|
||||||
cache,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for (name, version) in resolution.iter() {
|
for (name, version) in resolution.iter() {
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
|
|
|
@ -4,6 +4,8 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
puffin-platform = { path = "../puffin-platform" }
|
||||||
|
|
||||||
anyhow = { version = "1.0.75" }
|
anyhow = { version = "1.0.75" }
|
||||||
mailparse = { version = "0.14.0" }
|
mailparse = { version = "0.14.0" }
|
||||||
memchr = { version = "2.6.4" }
|
memchr = { version = "2.6.4" }
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use puffin_platform::tags::Tags;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
@ -39,8 +40,8 @@ impl FromStr for WheelFilename {
|
||||||
|
|
||||||
impl WheelFilename {
|
impl WheelFilename {
|
||||||
/// Returns `true` if the wheel is compatible with the given tags.
|
/// Returns `true` if the wheel is compatible with the given tags.
|
||||||
pub fn is_compatible(&self, compatible_tags: &[(String, String, String)]) -> bool {
|
pub fn is_compatible(&self, compatible_tags: &Tags) -> bool {
|
||||||
for tag in compatible_tags {
|
for tag in compatible_tags.iter() {
|
||||||
if self.python_tag.contains(&tag.0)
|
if self.python_tag.contains(&tag.0)
|
||||||
&& self.abi_tag.contains(&tag.1)
|
&& self.abi_tag.contains(&tag.1)
|
||||||
&& self.platform_tag.contains(&tag.2)
|
&& self.platform_tag.contains(&tag.2)
|
||||||
|
|
|
@ -11,6 +11,8 @@ use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
|
pub mod tags;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum PlatformError {
|
pub enum PlatformError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
@ -35,13 +37,6 @@ impl Platform {
|
||||||
pub fn is_windows(&self) -> bool {
|
pub fn is_windows(&self) -> bool {
|
||||||
matches!(self.os, Os::Windows)
|
matches!(self.os, Os::Windows)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compatible_tags(
|
|
||||||
&self,
|
|
||||||
python_version: &pep440_rs::Version,
|
|
||||||
) -> Result<Vec<(String, String, String)>, PlatformError> {
|
|
||||||
compatible_tags(python_version, &self.os, self.arch)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All supported operating systems.
|
/// All supported operating systems.
|
||||||
|
@ -233,6 +228,7 @@ impl Arch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the macOS version from the SystemVersion.plist file.
|
||||||
fn get_mac_os_version() -> Result<(u16, u16), PlatformError> {
|
fn get_mac_os_version() -> Result<(u16, u16), PlatformError> {
|
||||||
// This is actually what python does
|
// This is actually what python does
|
||||||
// https://github.com/python/cpython/blob/cb2b3c8d3566ae46b3b8d0718019e1c98484589e/Lib/platform.py#L409-L428
|
// https://github.com/python/cpython/blob/cb2b3c8d3566ae46b3b8d0718019e1c98484589e/Lib/platform.py#L409-L428
|
||||||
|
@ -266,37 +262,7 @@ fn get_mac_os_version() -> Result<(u16, u16), PlatformError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the appropriate binary formats for a macOS version.
|
/// Find musl libc path from executable's ELF header.
|
||||||
/// Source: <https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L314>
|
|
||||||
fn get_mac_binary_formats(major: u16, minor: u16, arch: Arch) -> Vec<String> {
|
|
||||||
let mut formats = vec![match arch {
|
|
||||||
Arch::Aarch64 => "arm64".to_string(),
|
|
||||||
_ => arch.to_string(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
if matches!(arch, Arch::X86_64) {
|
|
||||||
if (major, minor) < (10, 4) {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
formats.extend([
|
|
||||||
"intel".to_string(),
|
|
||||||
"fat64".to_string(),
|
|
||||||
"fat32".to_string(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(arch, Arch::X86_64 | Arch::Aarch64) {
|
|
||||||
formats.push("universal2".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(arch, Arch::X86_64) {
|
|
||||||
formats.push("universal".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
formats
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find musl libc path from executable's ELF header
|
|
||||||
fn find_libc() -> Result<PathBuf, PlatformError> {
|
fn find_libc() -> Result<PathBuf, PlatformError> {
|
||||||
let buffer = fs::read("/bin/ls")?;
|
let buffer = fs::read("/bin/ls")?;
|
||||||
let error_str = "Couldn't parse /bin/ls for detecting the ld version";
|
let error_str = "Couldn't parse /bin/ls for detecting the ld version";
|
||||||
|
@ -311,13 +277,15 @@ fn find_libc() -> Result<PathBuf, PlatformError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the musl version from libc library's output. Taken from maturin
|
/// Read the musl version from libc library's output. Taken from maturin.
|
||||||
///
|
///
|
||||||
/// The libc library should output something like this to `stderr::`
|
/// The libc library should output something like this to `stderr`:
|
||||||
///
|
///
|
||||||
|
/// ```text
|
||||||
/// musl libc (`x86_64`)
|
/// musl libc (`x86_64`)
|
||||||
/// Version 1.2.2
|
/// Version 1.2.2
|
||||||
/// Dynamic Program Loader
|
/// Dynamic Program Loader
|
||||||
|
/// ```
|
||||||
fn get_musl_version(ld_path: impl AsRef<Path>) -> std::io::Result<Option<(u16, u16)>> {
|
fn get_musl_version(ld_path: impl AsRef<Path>) -> std::io::Result<Option<(u16, u16)>> {
|
||||||
let output = Command::new(ld_path.as_ref())
|
let output = Command::new(ld_path.as_ref())
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
|
@ -332,219 +300,3 @@ fn get_musl_version(ld_path: impl AsRef<Path>) -> std::io::Result<Option<(u16, u
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the compatible platform tags, e.g. `manylinux_2_17`, `macosx_11_0_arm64` or `win_amd64`
|
|
||||||
///
|
|
||||||
/// We have two cases: Actual platform specific tags (including "merged" tags such as universal2)
|
|
||||||
/// and "any".
|
|
||||||
///
|
|
||||||
/// Bit of a mess, needs to be cleaned up
|
|
||||||
pub fn compatible_platform_tags(os: &Os, arch: Arch) -> Result<Vec<String>, PlatformError> {
|
|
||||||
let platform_tags = match (&os, arch) {
|
|
||||||
(Os::Manylinux { major, minor }, _) => {
|
|
||||||
let mut platform_tags = vec![format!("linux_{}", arch)];
|
|
||||||
platform_tags.extend(
|
|
||||||
(arch.get_minimum_manylinux_minor()..=*minor)
|
|
||||||
.map(|minor| format!("manylinux_{major}_{minor}_{arch}")),
|
|
||||||
);
|
|
||||||
if (arch.get_minimum_manylinux_minor()..=*minor).contains(&12) {
|
|
||||||
platform_tags.push(format!("manylinux2010_{arch}"));
|
|
||||||
}
|
|
||||||
if (arch.get_minimum_manylinux_minor()..=*minor).contains(&17) {
|
|
||||||
platform_tags.push(format!("manylinux2014_{arch}"));
|
|
||||||
}
|
|
||||||
if (arch.get_minimum_manylinux_minor()..=*minor).contains(&5) {
|
|
||||||
platform_tags.push(format!("manylinux1_{arch}"));
|
|
||||||
}
|
|
||||||
platform_tags
|
|
||||||
}
|
|
||||||
(Os::Musllinux { major, minor }, _) => {
|
|
||||||
let mut platform_tags = vec![format!("linux_{}", arch)];
|
|
||||||
// musl 1.1 is the lowest supported version in musllinux
|
|
||||||
platform_tags
|
|
||||||
.extend((1..=*minor).map(|minor| format!("musllinux_{major}_{minor}_{arch}")));
|
|
||||||
platform_tags
|
|
||||||
}
|
|
||||||
(Os::Macos { major, minor }, Arch::X86_64) => {
|
|
||||||
// Source: https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L346
|
|
||||||
let mut platform_tags = vec![];
|
|
||||||
match major {
|
|
||||||
10 => {
|
|
||||||
// Prior to Mac OS 11, each yearly release of Mac OS bumped the "minor" version
|
|
||||||
// number. The major version was always 10.
|
|
||||||
for minor in (0..=*minor).rev() {
|
|
||||||
for binary_format in get_mac_binary_formats(*major, minor, arch) {
|
|
||||||
platform_tags.push(format!("macosx_{major}_{minor}_{binary_format}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value if *value >= 11 => {
|
|
||||||
// Starting with Mac OS 11, each yearly release bumps the major version number.
|
|
||||||
// The minor versions are now the midyear updates.
|
|
||||||
for major in (10..=*major).rev() {
|
|
||||||
for binary_format in get_mac_binary_formats(major, 0, arch) {
|
|
||||||
platform_tags.push(format!("macosx_{}_{}_{}", major, 0, binary_format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The "universal2" binary format can have a macOS version earlier than 11.0
|
|
||||||
// when the x86_64 part of the binary supports that version of macOS.
|
|
||||||
for minor in (4..=16).rev() {
|
|
||||||
for binary_format in get_mac_binary_formats(10, minor, arch) {
|
|
||||||
platform_tags
|
|
||||||
.push(format!("macosx_{}_{}_{}", 10, minor, binary_format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(PlatformError::OsVersionDetectionError(format!(
|
|
||||||
"Unsupported macOS version: {major}",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
platform_tags
|
|
||||||
}
|
|
||||||
(Os::Macos { major, .. }, Arch::Aarch64) => {
|
|
||||||
// Source: https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L346
|
|
||||||
let mut platform_tags = vec![];
|
|
||||||
// Starting with Mac OS 11, each yearly release bumps the major version number.
|
|
||||||
// The minor versions are now the midyear updates.
|
|
||||||
for major in (10..=*major).rev() {
|
|
||||||
for binary_format in get_mac_binary_formats(major, 0, arch) {
|
|
||||||
platform_tags.push(format!("macosx_{}_{}_{}", major, 0, binary_format));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The "universal2" binary format can have a macOS version earlier than 11.0
|
|
||||||
// when the x86_64 part of the binary supports that version of macOS.
|
|
||||||
platform_tags.extend(
|
|
||||||
(4..=16)
|
|
||||||
.rev()
|
|
||||||
.map(|minor| format!("macosx_{}_{}_universal2", 10, minor)),
|
|
||||||
);
|
|
||||||
platform_tags
|
|
||||||
}
|
|
||||||
(Os::Windows, Arch::X86) => {
|
|
||||||
vec!["win32".to_string()]
|
|
||||||
}
|
|
||||||
(Os::Windows, Arch::X86_64) => {
|
|
||||||
vec!["win_amd64".to_string()]
|
|
||||||
}
|
|
||||||
(Os::Windows, Arch::Aarch64) => vec!["win_arm64".to_string()],
|
|
||||||
(
|
|
||||||
Os::FreeBsd { release }
|
|
||||||
| Os::NetBsd { release }
|
|
||||||
| Os::OpenBsd { release }
|
|
||||||
| Os::Dragonfly { release }
|
|
||||||
| Os::Haiku { release },
|
|
||||||
_,
|
|
||||||
) => {
|
|
||||||
let release = release.replace(['.', '-'], "_");
|
|
||||||
vec![format!(
|
|
||||||
"{}_{}_{}",
|
|
||||||
os.to_string().to_lowercase(),
|
|
||||||
release,
|
|
||||||
arch
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
(Os::Illumos { release, arch }, _) => {
|
|
||||||
// See https://github.com/python/cpython/blob/46c8d915715aa2bd4d697482aa051fe974d440e1/Lib/sysconfig.py#L722-L730
|
|
||||||
if let Some((major, other)) = release.split_once('_') {
|
|
||||||
let major_ver: u64 = major.parse().map_err(|err| {
|
|
||||||
PlatformError::OsVersionDetectionError(format!(
|
|
||||||
"illumos major version is not a number: {err}"
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
if major_ver >= 5 {
|
|
||||||
// SunOS 5 == Solaris 2
|
|
||||||
let os = "solaris".to_string();
|
|
||||||
let release = format!("{}_{}", major_ver - 3, other);
|
|
||||||
let arch = format!("{arch}_64bit");
|
|
||||||
return Ok(vec![format!("{}_{}_{}", os, release, arch)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let os = os.to_string().to_lowercase();
|
|
||||||
vec![format!("{}_{}_{}", os, release, arch)]
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(PlatformError::OsVersionDetectionError(format!(
|
|
||||||
"Unsupported operating system and architecture combination: {os} {arch}"
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(platform_tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the compatible tags in a (`python_tag`, `abi_tag`, `platform_tag`) format
|
|
||||||
pub fn compatible_tags(
|
|
||||||
python_version: &pep440_rs::Version,
|
|
||||||
os: &Os,
|
|
||||||
arch: Arch,
|
|
||||||
) -> Result<Vec<(String, String, String)>, PlatformError> {
|
|
||||||
let python_version = (python_version.release[0], python_version.release[1]);
|
|
||||||
|
|
||||||
let mut tags = Vec::new();
|
|
||||||
let platform_tags = compatible_platform_tags(os, arch)?;
|
|
||||||
// 1. This exact c api version
|
|
||||||
for platform_tag in &platform_tags {
|
|
||||||
tags.push((
|
|
||||||
format!("cp{}{}", python_version.0, python_version.1),
|
|
||||||
format!(
|
|
||||||
"cp{}{}{}",
|
|
||||||
python_version.0,
|
|
||||||
python_version.1,
|
|
||||||
// hacky but that's legacy anyways
|
|
||||||
if python_version.1 <= 7 { "m" } else { "" }
|
|
||||||
),
|
|
||||||
platform_tag.clone(),
|
|
||||||
));
|
|
||||||
tags.push((
|
|
||||||
format!("cp{}{}", python_version.0, python_version.1),
|
|
||||||
"none".to_string(),
|
|
||||||
platform_tag.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// 2. abi3 and no abi (e.g. executable binary)
|
|
||||||
// For some reason 3.2 is the minimum python for the cp abi
|
|
||||||
for minor in 2..=python_version.1 {
|
|
||||||
for platform_tag in &platform_tags {
|
|
||||||
tags.push((
|
|
||||||
format!("cp{}{}", python_version.0, minor),
|
|
||||||
"abi3".to_string(),
|
|
||||||
platform_tag.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 3. no abi (e.g. executable binary)
|
|
||||||
for minor in 0..=python_version.1 {
|
|
||||||
for platform_tag in &platform_tags {
|
|
||||||
tags.push((
|
|
||||||
format!("py{}{}", python_version.0, minor),
|
|
||||||
"none".to_string(),
|
|
||||||
platform_tag.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 4. major only
|
|
||||||
for platform_tag in platform_tags {
|
|
||||||
tags.push((
|
|
||||||
format!("py{}", python_version.0),
|
|
||||||
"none".to_string(),
|
|
||||||
platform_tag,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
// 5. no binary
|
|
||||||
for minor in 0..=python_version.1 {
|
|
||||||
tags.push((
|
|
||||||
format!("py{}{}", python_version.0, minor),
|
|
||||||
"none".to_string(),
|
|
||||||
"any".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
tags.push((
|
|
||||||
format!("py{}", python_version.0),
|
|
||||||
"none".to_string(),
|
|
||||||
"any".to_string(),
|
|
||||||
));
|
|
||||||
tags.sort();
|
|
||||||
Ok(tags)
|
|
||||||
}
|
|
||||||
|
|
267
crates/puffin-platform/src/tags.rs
Normal file
267
crates/puffin-platform/src/tags.rs
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
use crate::{Arch, Os, Platform, PlatformError};
|
||||||
|
|
||||||
|
/// A set of compatible tags for a given Python version and platform, in
|
||||||
|
/// (`python_tag`, `abi_tag`, `platform_tag`) format.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Tags(Vec<(String, String, String)>);
|
||||||
|
|
||||||
|
impl Tags {
|
||||||
|
/// Returns the compatible tags for the given Python version and platform.
|
||||||
|
pub fn from_env(
|
||||||
|
platform: &Platform,
|
||||||
|
python_version: &pep440_rs::Version,
|
||||||
|
) -> Result<Self, PlatformError> {
|
||||||
|
let python_version = (python_version.release[0], python_version.release[1]);
|
||||||
|
|
||||||
|
let platform_tags = platform.compatible_tags()?;
|
||||||
|
|
||||||
|
let mut tags = Vec::with_capacity(5 * platform_tags.len());
|
||||||
|
|
||||||
|
// 1. This exact c api version
|
||||||
|
for platform_tag in &platform_tags {
|
||||||
|
tags.push((
|
||||||
|
format!("cp{}{}", python_version.0, python_version.1),
|
||||||
|
format!(
|
||||||
|
"cp{}{}{}",
|
||||||
|
python_version.0,
|
||||||
|
python_version.1,
|
||||||
|
// hacky but that's legacy anyways
|
||||||
|
if python_version.1 <= 7 { "m" } else { "" }
|
||||||
|
),
|
||||||
|
platform_tag.clone(),
|
||||||
|
));
|
||||||
|
tags.push((
|
||||||
|
format!("cp{}{}", python_version.0, python_version.1),
|
||||||
|
"none".to_string(),
|
||||||
|
platform_tag.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// 2. abi3 and no abi (e.g. executable binary)
|
||||||
|
// For some reason 3.2 is the minimum python for the cp abi
|
||||||
|
for minor in 2..=python_version.1 {
|
||||||
|
for platform_tag in &platform_tags {
|
||||||
|
tags.push((
|
||||||
|
format!("cp{}{}", python_version.0, minor),
|
||||||
|
"abi3".to_string(),
|
||||||
|
platform_tag.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. no abi (e.g. executable binary)
|
||||||
|
for minor in 0..=python_version.1 {
|
||||||
|
for platform_tag in &platform_tags {
|
||||||
|
tags.push((
|
||||||
|
format!("py{}{}", python_version.0, minor),
|
||||||
|
"none".to_string(),
|
||||||
|
platform_tag.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 4. major only
|
||||||
|
for platform_tag in platform_tags {
|
||||||
|
tags.push((
|
||||||
|
format!("py{}", python_version.0),
|
||||||
|
"none".to_string(),
|
||||||
|
platform_tag,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// 5. no binary
|
||||||
|
for minor in 0..=python_version.1 {
|
||||||
|
tags.push((
|
||||||
|
format!("py{}{}", python_version.0, minor),
|
||||||
|
"none".to_string(),
|
||||||
|
"any".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
tags.push((
|
||||||
|
format!("py{}", python_version.0),
|
||||||
|
"none".to_string(),
|
||||||
|
"any".to_string(),
|
||||||
|
));
|
||||||
|
tags.sort();
|
||||||
|
Ok(Self(tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &(String, String, String)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Platform {
|
||||||
|
/// Returns the compatible tags for the current [`Platform`] (e.g., `manylinux_2_17`,
|
||||||
|
/// `macosx_11_0_arm64`, or `win_amd64`).
|
||||||
|
///
|
||||||
|
/// We have two cases: Actual platform specific tags (including "merged" tags such as universal2)
|
||||||
|
/// and "any".
|
||||||
|
///
|
||||||
|
/// Bit of a mess, needs to be cleaned up.
|
||||||
|
fn compatible_tags(&self) -> Result<Vec<String>, PlatformError> {
|
||||||
|
let os = &self.os;
|
||||||
|
let arch = self.arch;
|
||||||
|
|
||||||
|
let platform_tags = match (&os, arch) {
|
||||||
|
(Os::Manylinux { major, minor }, _) => {
|
||||||
|
let mut platform_tags = vec![format!("linux_{}", arch)];
|
||||||
|
platform_tags.extend(
|
||||||
|
(arch.get_minimum_manylinux_minor()..=*minor)
|
||||||
|
.map(|minor| format!("manylinux_{major}_{minor}_{arch}")),
|
||||||
|
);
|
||||||
|
if (arch.get_minimum_manylinux_minor()..=*minor).contains(&12) {
|
||||||
|
platform_tags.push(format!("manylinux2010_{arch}"));
|
||||||
|
}
|
||||||
|
if (arch.get_minimum_manylinux_minor()..=*minor).contains(&17) {
|
||||||
|
platform_tags.push(format!("manylinux2014_{arch}"));
|
||||||
|
}
|
||||||
|
if (arch.get_minimum_manylinux_minor()..=*minor).contains(&5) {
|
||||||
|
platform_tags.push(format!("manylinux1_{arch}"));
|
||||||
|
}
|
||||||
|
platform_tags
|
||||||
|
}
|
||||||
|
(Os::Musllinux { major, minor }, _) => {
|
||||||
|
let mut platform_tags = vec![format!("linux_{}", arch)];
|
||||||
|
// musl 1.1 is the lowest supported version in musllinux
|
||||||
|
platform_tags
|
||||||
|
.extend((1..=*minor).map(|minor| format!("musllinux_{major}_{minor}_{arch}")));
|
||||||
|
platform_tags
|
||||||
|
}
|
||||||
|
(Os::Macos { major, minor }, Arch::X86_64) => {
|
||||||
|
// Source: https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L346
|
||||||
|
let mut platform_tags = vec![];
|
||||||
|
match major {
|
||||||
|
10 => {
|
||||||
|
// Prior to Mac OS 11, each yearly release of Mac OS bumped the "minor" version
|
||||||
|
// number. The major version was always 10.
|
||||||
|
for minor in (0..=*minor).rev() {
|
||||||
|
for binary_format in get_mac_binary_formats(*major, minor, arch) {
|
||||||
|
platform_tags
|
||||||
|
.push(format!("macosx_{major}_{minor}_{binary_format}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value if *value >= 11 => {
|
||||||
|
// Starting with Mac OS 11, each yearly release bumps the major version number.
|
||||||
|
// The minor versions are now the midyear updates.
|
||||||
|
for major in (10..=*major).rev() {
|
||||||
|
for binary_format in get_mac_binary_formats(major, 0, arch) {
|
||||||
|
platform_tags
|
||||||
|
.push(format!("macosx_{}_{}_{}", major, 0, binary_format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The "universal2" binary format can have a macOS version earlier than 11.0
|
||||||
|
// when the x86_64 part of the binary supports that version of macOS.
|
||||||
|
for minor in (4..=16).rev() {
|
||||||
|
for binary_format in get_mac_binary_formats(10, minor, arch) {
|
||||||
|
platform_tags
|
||||||
|
.push(format!("macosx_{}_{}_{}", 10, minor, binary_format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(PlatformError::OsVersionDetectionError(format!(
|
||||||
|
"Unsupported macOS version: {major}",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
platform_tags
|
||||||
|
}
|
||||||
|
(Os::Macos { major, .. }, Arch::Aarch64) => {
|
||||||
|
// Source: https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L346
|
||||||
|
let mut platform_tags = vec![];
|
||||||
|
// Starting with Mac OS 11, each yearly release bumps the major version number.
|
||||||
|
// The minor versions are now the midyear updates.
|
||||||
|
for major in (10..=*major).rev() {
|
||||||
|
for binary_format in get_mac_binary_formats(major, 0, arch) {
|
||||||
|
platform_tags.push(format!("macosx_{}_{}_{}", major, 0, binary_format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The "universal2" binary format can have a macOS version earlier than 11.0
|
||||||
|
// when the x86_64 part of the binary supports that version of macOS.
|
||||||
|
platform_tags.extend(
|
||||||
|
(4..=16)
|
||||||
|
.rev()
|
||||||
|
.map(|minor| format!("macosx_{}_{}_universal2", 10, minor)),
|
||||||
|
);
|
||||||
|
platform_tags
|
||||||
|
}
|
||||||
|
(Os::Windows, Arch::X86) => {
|
||||||
|
vec!["win32".to_string()]
|
||||||
|
}
|
||||||
|
(Os::Windows, Arch::X86_64) => {
|
||||||
|
vec!["win_amd64".to_string()]
|
||||||
|
}
|
||||||
|
(Os::Windows, Arch::Aarch64) => vec!["win_arm64".to_string()],
|
||||||
|
(
|
||||||
|
Os::FreeBsd { release }
|
||||||
|
| Os::NetBsd { release }
|
||||||
|
| Os::OpenBsd { release }
|
||||||
|
| Os::Dragonfly { release }
|
||||||
|
| Os::Haiku { release },
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
|
let release = release.replace(['.', '-'], "_");
|
||||||
|
vec![format!(
|
||||||
|
"{}_{}_{}",
|
||||||
|
os.to_string().to_lowercase(),
|
||||||
|
release,
|
||||||
|
arch
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
(Os::Illumos { release, arch }, _) => {
|
||||||
|
// See https://github.com/python/cpython/blob/46c8d915715aa2bd4d697482aa051fe974d440e1/Lib/sysconfig.py#L722-L730
|
||||||
|
if let Some((major, other)) = release.split_once('_') {
|
||||||
|
let major_ver: u64 = major.parse().map_err(|err| {
|
||||||
|
PlatformError::OsVersionDetectionError(format!(
|
||||||
|
"illumos major version is not a number: {err}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
if major_ver >= 5 {
|
||||||
|
// SunOS 5 == Solaris 2
|
||||||
|
let os = "solaris".to_string();
|
||||||
|
let release = format!("{}_{}", major_ver - 3, other);
|
||||||
|
let arch = format!("{arch}_64bit");
|
||||||
|
return Ok(vec![format!("{}_{}_{}", os, release, arch)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let os = os.to_string().to_lowercase();
|
||||||
|
vec![format!("{}_{}_{}", os, release, arch)]
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(PlatformError::OsVersionDetectionError(format!(
|
||||||
|
"Unsupported operating system and architecture combination: {os} {arch}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(platform_tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine the appropriate binary formats for a macOS version.
|
||||||
|
/// Source: <https://github.com/pypa/packaging/blob/fd4f11139d1c884a637be8aa26bb60a31fbc9411/packaging/tags.py#L314>
|
||||||
|
fn get_mac_binary_formats(major: u16, minor: u16, arch: Arch) -> Vec<String> {
|
||||||
|
let mut formats = vec![match arch {
|
||||||
|
Arch::Aarch64 => "arm64".to_string(),
|
||||||
|
_ => arch.to_string(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
if matches!(arch, Arch::X86_64) {
|
||||||
|
if (major, minor) < (10, 4) {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
formats.extend([
|
||||||
|
"intel".to_string(),
|
||||||
|
"fat64".to_string(),
|
||||||
|
"fat32".to_string(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(arch, Arch::X86_64 | Arch::Aarch64) {
|
||||||
|
formats.push("universal2".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(arch, Arch::X86_64) {
|
||||||
|
formats.push("universal".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
formats
|
||||||
|
}
|
|
@ -14,8 +14,9 @@ use puffin_package::metadata::Metadata21;
|
||||||
use puffin_package::package_name::PackageName;
|
use puffin_package::package_name::PackageName;
|
||||||
use puffin_package::requirements::Requirements;
|
use puffin_package::requirements::Requirements;
|
||||||
use puffin_package::wheel::WheelFilename;
|
use puffin_package::wheel::WheelFilename;
|
||||||
use puffin_platform::Platform;
|
use puffin_platform::tags::Tags;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Resolution(HashMap<PackageName, Version>);
|
pub struct Resolution(HashMap<PackageName, Version>);
|
||||||
|
|
||||||
impl Resolution {
|
impl Resolution {
|
||||||
|
@ -27,9 +28,8 @@ impl Resolution {
|
||||||
/// Resolve a set of requirements into a set of pinned versions.
|
/// Resolve a set of requirements into a set of pinned versions.
|
||||||
pub async fn resolve(
|
pub async fn resolve(
|
||||||
requirements: &Requirements,
|
requirements: &Requirements,
|
||||||
python_version: &Version,
|
|
||||||
markers: &MarkerEnvironment,
|
markers: &MarkerEnvironment,
|
||||||
platform: &Platform,
|
tags: &Tags,
|
||||||
cache: Option<&Path>,
|
cache: Option<&Path>,
|
||||||
) -> Result<Resolution> {
|
) -> Result<Resolution> {
|
||||||
// Instantiate a client.
|
// Instantiate a client.
|
||||||
|
@ -70,9 +70,6 @@ pub async fn resolve(
|
||||||
in_flight.insert(PackageName::normalize(&requirement.name));
|
in_flight.insert(PackageName::normalize(&requirement.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the compatible platform tags.
|
|
||||||
let tags = platform.compatible_tags(python_version)?;
|
|
||||||
|
|
||||||
// Resolve the requirements.
|
// Resolve the requirements.
|
||||||
let mut resolution: HashMap<PackageName, Version> = HashMap::with_capacity(requirements.len());
|
let mut resolution: HashMap<PackageName, Version> = HashMap::with_capacity(requirements.len());
|
||||||
|
|
||||||
|
@ -102,7 +99,7 @@ pub async fn resolve(
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !name.is_compatible(&tags) {
|
if !name.is_compatible(tags) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue