mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Refactor uv-toolchain::platform
to use target-lexicon
(#4236)
Closes https://github.com/astral-sh/uv/issues/3857 Instead of using custom `Arch`, `Os`, and `Libc` types I just use `target-lexicon`'s which enumerate way more variants and implement display and parsing. We use a wrapper type to represent a couple special cases to support the "x86" alias for "i686" and "macos" for "darwin". Alternatively we could try to use our `platform-tags` types but those capture more information (like operating system versions) that we don't have for downloads. As discussed in https://github.com/astral-sh/uv/pull/4160, this is not sufficient for proper libc detection but that work is larger and will be handled separately.
This commit is contained in:
parent
5a09c26e77
commit
f7f55ede2f
12 changed files with 4367 additions and 4691 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4921,6 +4921,7 @@ dependencies = [
|
|||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"target-lexicon",
|
||||
"temp-env",
|
||||
"tempfile",
|
||||
"test-log",
|
||||
|
|
|
@ -120,6 +120,7 @@ serde = { version = "1.0.197", features = ["derive"] }
|
|||
serde_json = { version = "1.0.114" }
|
||||
sha2 = { version = "0.10.8" }
|
||||
sys-info = { version = "0.9.1" }
|
||||
target-lexicon = {version = "0.12.14" }
|
||||
tempfile = { version = "3.9.0" }
|
||||
textwrap = { version = "0.16.1" }
|
||||
thiserror = { version = "1.0.56" }
|
||||
|
|
|
@ -41,6 +41,7 @@ same-file = { workspace = true }
|
|||
schemars = { workspace = true, optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
target-lexicon = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio-util = { workspace = true, features = ["compat"] }
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -84,11 +84,7 @@ _suffix_re = re.compile(
|
|||
ARCH_MAP = {
|
||||
"ppc64": "powerpc64",
|
||||
"ppc64le": "powerpc64le",
|
||||
"i686": "x86",
|
||||
"i386": "x86",
|
||||
"armv7": "armv7l",
|
||||
}
|
||||
OS_MAP = {"darwin": "macos"}
|
||||
|
||||
|
||||
def parse_filename(filename):
|
||||
|
@ -107,8 +103,8 @@ def parse_filename(filename):
|
|||
|
||||
|
||||
def normalize_triple(triple):
|
||||
if "-static" in triple or "-gnueabihf" in triple or "-gnueabi" in triple:
|
||||
logging.debug("Skipping %r: unknown triple", triple)
|
||||
if "-static" in triple:
|
||||
logging.debug("Skipping %r: static unsupported", triple)
|
||||
return
|
||||
triple = SPECIAL_TRIPLES.get(triple, triple)
|
||||
pieces = triple.split("-")
|
||||
|
@ -134,7 +130,7 @@ def normalize_arch(arch):
|
|||
|
||||
|
||||
def normalize_os(os):
|
||||
return OS_MAP.get(os, os)
|
||||
return os
|
||||
|
||||
|
||||
def read_sha256(url):
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,14 +6,18 @@
|
|||
pub(crate) const PYTHON_DOWNLOADS: &[PythonDownload] = &[
|
||||
{{#versions}}
|
||||
PythonDownload {
|
||||
key: "{{key}}",
|
||||
major: {{value.major}},
|
||||
minor: {{value.minor}},
|
||||
patch: {{value.patch}},
|
||||
implementation: ImplementationName::{{value.name}},
|
||||
arch: Arch::{{value.arch}},
|
||||
os: Os::{{value.os}},
|
||||
libc: Libc::{{value.libc}},
|
||||
arch: Arch(target_lexicon::Architecture::{{value.arch}}),
|
||||
os: Os(target_lexicon::OperatingSystem::{{value.os}}),
|
||||
{{#value.libc}}
|
||||
libc: Libc::Some(target_lexicon::Environment::{{.}}),
|
||||
{{/value.libc}}
|
||||
{{^value.libc}}
|
||||
libc: Libc::None,
|
||||
{{/value.libc}}
|
||||
url: "{{value.url}}",
|
||||
{{#value.sha256}}
|
||||
sha256: Some("{{.}}")
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
|||
use std::str::FromStr;
|
||||
|
||||
use crate::implementation::{Error as ImplementationError, ImplementationName};
|
||||
use crate::platform::{Arch, Error as PlatformError, Libc, Os};
|
||||
use crate::platform::{Arch, Libc, Os};
|
||||
use crate::{PythonVersion, ToolchainRequest, VersionRequest};
|
||||
use thiserror::Error;
|
||||
use uv_client::BetterReqwestError;
|
||||
|
@ -21,8 +21,6 @@ pub enum Error {
|
|||
#[error(transparent)]
|
||||
IO(#[from] io::Error),
|
||||
#[error(transparent)]
|
||||
PlatformError(#[from] PlatformError),
|
||||
#[error(transparent)]
|
||||
ImplementationError(#[from] ImplementationError),
|
||||
#[error("Invalid python version: {0}")]
|
||||
InvalidPythonVersion(String),
|
||||
|
@ -59,7 +57,6 @@ pub enum Error {
|
|||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PythonDownload {
|
||||
key: &'static str,
|
||||
implementation: ImplementationName,
|
||||
arch: Arch,
|
||||
os: Os,
|
||||
|
@ -157,10 +154,10 @@ impl PythonDownloadRequest {
|
|||
self.implementation = Some(ImplementationName::CPython);
|
||||
}
|
||||
if self.arch.is_none() {
|
||||
self.arch = Some(Arch::from_env()?);
|
||||
self.arch = Some(Arch::from_env());
|
||||
}
|
||||
if self.os.is_none() {
|
||||
self.os = Some(Os::from_env()?);
|
||||
self.os = Some(Os::from_env());
|
||||
}
|
||||
if self.libc.is_none() {
|
||||
self.libc = Some(Libc::from_env());
|
||||
|
@ -173,8 +170,8 @@ impl PythonDownloadRequest {
|
|||
Ok(Self::new(
|
||||
None,
|
||||
None,
|
||||
Some(Arch::from_env()?),
|
||||
Some(Os::from_env()?),
|
||||
Some(Arch::from_env()),
|
||||
Some(Os::from_env()),
|
||||
Some(Libc::from_env()),
|
||||
))
|
||||
}
|
||||
|
@ -252,11 +249,6 @@ pub enum DownloadResult {
|
|||
}
|
||||
|
||||
impl PythonDownload {
|
||||
/// Return the [`PythonDownload`] corresponding to the key, if it exists.
|
||||
pub fn from_key(key: &str) -> Option<&PythonDownload> {
|
||||
PYTHON_DOWNLOADS.iter().find(|&value| value.key == key)
|
||||
}
|
||||
|
||||
/// Return the first [`PythonDownload`] matching a request, if any.
|
||||
pub fn from_request(request: &PythonDownloadRequest) -> Result<&'static PythonDownload, Error> {
|
||||
request
|
||||
|
@ -274,8 +266,17 @@ impl PythonDownload {
|
|||
self.url
|
||||
}
|
||||
|
||||
pub fn key(&self) -> &str {
|
||||
self.key
|
||||
pub fn key(&self) -> String {
|
||||
format!(
|
||||
"{}-{}.{}.{}-{}-{}-{}",
|
||||
self.implementation.as_str().to_ascii_lowercase(),
|
||||
self.major,
|
||||
self.minor,
|
||||
self.patch,
|
||||
self.os,
|
||||
self.arch,
|
||||
self.libc
|
||||
)
|
||||
}
|
||||
|
||||
pub fn sha256(&self) -> Option<&str> {
|
||||
|
@ -289,7 +290,7 @@ impl PythonDownload {
|
|||
parent_path: &Path,
|
||||
) -> Result<DownloadResult, Error> {
|
||||
let url = Url::parse(self.url)?;
|
||||
let path = parent_path.join(self.key).clone();
|
||||
let path = parent_path.join(self.key()).clone();
|
||||
|
||||
// If it already exists, return it
|
||||
if path.is_dir() {
|
||||
|
@ -363,6 +364,6 @@ impl From<reqwest_middleware::Error> for Error {
|
|||
|
||||
impl Display for PythonDownload {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.key)
|
||||
f.write_str(&self.key())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ impl InstalledToolchains {
|
|||
pub fn find_matching_current_platform(
|
||||
&self,
|
||||
) -> Result<impl DoubleEndedIterator<Item = InstalledToolchain>, Error> {
|
||||
let platform_key = platform_key_from_env()?;
|
||||
let platform_key = platform_key_from_env();
|
||||
|
||||
let iter = InstalledToolchains::from_settings()?
|
||||
.find_all()?
|
||||
|
@ -286,11 +286,11 @@ impl InstalledToolchain {
|
|||
}
|
||||
|
||||
/// Generate a platform portion of a key from the environment.
|
||||
fn platform_key_from_env() -> Result<String, Error> {
|
||||
let os = Os::from_env()?;
|
||||
let arch = Arch::from_env()?;
|
||||
fn platform_key_from_env() -> String {
|
||||
let os = Os::from_env();
|
||||
let arch = Arch::from_env();
|
||||
let libc = Libc::from_env();
|
||||
Ok(format!("{os}-{arch}-{libc}").to_lowercase())
|
||||
format!("{os}-{arch}-{libc}").to_lowercase()
|
||||
}
|
||||
|
||||
impl fmt::Display for InstalledToolchain {
|
||||
|
|
|
@ -1,72 +1,80 @@
|
|||
use std::{
|
||||
fmt::{self},
|
||||
str::FromStr,
|
||||
};
|
||||
use std::fmt::Display;
|
||||
use std::ops::Deref;
|
||||
use std::{fmt, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
||||
/// All supported operating systems.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Os {
|
||||
Windows,
|
||||
Linux,
|
||||
Macos,
|
||||
FreeBsd,
|
||||
NetBsd,
|
||||
OpenBsd,
|
||||
Dragonfly,
|
||||
Illumos,
|
||||
Haiku,
|
||||
}
|
||||
|
||||
/// All supported CPU architectures
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Arch {
|
||||
Aarch64,
|
||||
Armv6L,
|
||||
Armv7L,
|
||||
Powerpc64Le,
|
||||
Powerpc64,
|
||||
X86,
|
||||
X86_64,
|
||||
S390X,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum Libc {
|
||||
Gnu,
|
||||
Musl,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Operating system not supported: {0}")]
|
||||
OsNotSupported(String),
|
||||
#[error("Architecture not supported: {0}")]
|
||||
ArchNotSupported(String),
|
||||
#[error("Libc type could not be detected")]
|
||||
LibcNotDetected,
|
||||
#[error("Unknown operating system: {0}")]
|
||||
UnknownOs(String),
|
||||
#[error("Unknown architecture: {0}")]
|
||||
UnknownArch(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Os {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Windows => write!(f, "Windows"),
|
||||
Self::Macos => write!(f, "MacOS"),
|
||||
Self::FreeBsd => write!(f, "FreeBSD"),
|
||||
Self::NetBsd => write!(f, "NetBSD"),
|
||||
Self::Linux => write!(f, "Linux"),
|
||||
Self::OpenBsd => write!(f, "OpenBSD"),
|
||||
Self::Dragonfly => write!(f, "DragonFly"),
|
||||
Self::Illumos => write!(f, "Illumos"),
|
||||
Self::Haiku => write!(f, "Haiku"),
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct Arch(pub(crate) target_lexicon::Architecture);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub struct Os(pub(crate) target_lexicon::OperatingSystem);
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum Libc {
|
||||
Some(target_lexicon::Environment),
|
||||
None,
|
||||
}
|
||||
|
||||
impl Libc {
|
||||
pub(crate) fn from_env() -> Self {
|
||||
match std::env::consts::OS {
|
||||
// TODO(zanieb): On Linux, we use the uv target host to determine the libc variant
|
||||
// but we should only use this as a fallback and should instead inspect the
|
||||
// machine's `/bin/sh` (or similar).
|
||||
"linux" => Self::Some(target_lexicon::Environment::Gnu),
|
||||
"windows" | "macos" => Self::None,
|
||||
// Use `None` on platforms without explicit support.
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Os {
|
||||
pub(crate) fn from_env() -> Result<Self, Error> {
|
||||
Self::from_str(std::env::consts::OS)
|
||||
pub(crate) fn from_env() -> Self {
|
||||
Self(target_lexicon::HOST.operating_system)
|
||||
}
|
||||
}
|
||||
|
||||
impl Arch {
|
||||
pub(crate) fn from_env() -> Self {
|
||||
Self(target_lexicon::HOST.architecture)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Libc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Some(env) => write!(f, "{env}"),
|
||||
Self::None => write!(f, "none"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Os {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &**self {
|
||||
target_lexicon::OperatingSystem::Darwin => write!(f, "macos"),
|
||||
inner => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Arch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &**self {
|
||||
target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686) => {
|
||||
write!(f, "x86")
|
||||
}
|
||||
inner => write!(f, "{inner}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,33 +82,15 @@ impl FromStr for Os {
|
|||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"windows" => Ok(Self::Windows),
|
||||
"linux" => Ok(Self::Linux),
|
||||
"macos" => Ok(Self::Macos),
|
||||
"freebsd" => Ok(Self::FreeBsd),
|
||||
"netbsd" => Ok(Self::NetBsd),
|
||||
"openbsd" => Ok(Self::OpenBsd),
|
||||
"dragonfly" => Ok(Self::Dragonfly),
|
||||
"illumos" => Ok(Self::Illumos),
|
||||
"haiku" => Ok(Self::Haiku),
|
||||
_ => Err(Error::OsNotSupported(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Arch {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Aarch64 => write!(f, "aarch64"),
|
||||
Self::Armv6L => write!(f, "armv6l"),
|
||||
Self::Armv7L => write!(f, "armv7l"),
|
||||
Self::Powerpc64Le => write!(f, "ppc64le"),
|
||||
Self::Powerpc64 => write!(f, "ppc64"),
|
||||
Self::X86 => write!(f, "i686"),
|
||||
Self::X86_64 => write!(f, "x86_64"),
|
||||
Self::S390X => write!(f, "s390x"),
|
||||
let inner = match s {
|
||||
"macos" => target_lexicon::OperatingSystem::Darwin,
|
||||
_ => target_lexicon::OperatingSystem::from_str(s)
|
||||
.map_err(|()| Error::UnknownOs(s.to_string()))?,
|
||||
};
|
||||
if matches!(inner, target_lexicon::OperatingSystem::Unknown) {
|
||||
return Err(Error::UnknownOs(s.to_string()));
|
||||
}
|
||||
Ok(Self(inner))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,45 +98,32 @@ impl FromStr for Arch {
|
|||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"aarch64" | "arm64" => Ok(Self::Aarch64),
|
||||
"armv6l" => Ok(Self::Armv6L),
|
||||
"armv7l" | "arm" => Ok(Self::Armv7L),
|
||||
"powerpc64le" | "ppc64le" => Ok(Self::Powerpc64Le),
|
||||
"powerpc64" | "ppc64" => Ok(Self::Powerpc64),
|
||||
"x86" | "i686" | "i386" => Ok(Self::X86),
|
||||
"x86_64" | "amd64" => Ok(Self::X86_64),
|
||||
"s390x" => Ok(Self::S390X),
|
||||
_ => Err(Error::ArchNotSupported(s.to_string())),
|
||||
let inner = match s {
|
||||
// Allow users to specify "x86" as a shorthand for the "i686" variant, they should not need
|
||||
// to specify the exact architecture and this variant is what we have downloads for.
|
||||
"x86" => target_lexicon::Architecture::X86_32(target_lexicon::X86_32Architecture::I686),
|
||||
_ => target_lexicon::Architecture::from_str(s)
|
||||
.map_err(|()| Error::UnknownArch(s.to_string()))?,
|
||||
};
|
||||
if matches!(inner, target_lexicon::Architecture::Unknown) {
|
||||
return Err(Error::UnknownArch(s.to_string()));
|
||||
}
|
||||
Ok(Self(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl Arch {
|
||||
pub(crate) fn from_env() -> Result<Self, Error> {
|
||||
Self::from_str(std::env::consts::ARCH)
|
||||
impl Deref for Arch {
|
||||
type Target = target_lexicon::Architecture;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Libc {
|
||||
pub(crate) fn from_env() -> Self {
|
||||
// TODO(zanieb): Perform this lookup
|
||||
match std::env::consts::OS {
|
||||
// Supported platforms.
|
||||
"linux" => Libc::Gnu,
|
||||
"windows" | "macos" => Libc::None,
|
||||
// Platforms without explicit support.
|
||||
_ => Libc::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Deref for Os {
|
||||
type Target = target_lexicon::OperatingSystem;
|
||||
|
||||
impl fmt::Display for Libc {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Libc::Gnu => f.write_str("gnu"),
|
||||
Libc::None => f.write_str("none"),
|
||||
Libc::Musl => f.write_str("musl"),
|
||||
}
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
26
crates/uv-toolchain/template-download-metadata.py
Normal file → Executable file
26
crates/uv-toolchain/template-download-metadata.py
Normal file → Executable file
|
@ -41,11 +41,31 @@ def prepare_name(name: str) -> str:
|
|||
raise ValueError(f"Unknown implementation name: {name}")
|
||||
|
||||
|
||||
def prepare_libc(libc: str) -> str | None:
|
||||
if libc == "none":
|
||||
return None
|
||||
else:
|
||||
return libc.title()
|
||||
|
||||
|
||||
def prepare_arch(arch: str) -> str:
|
||||
match arch:
|
||||
# Special constructors
|
||||
case "i686":
|
||||
return "X86_32(target_lexicon::X86_32Architecture::I686)"
|
||||
case "aarch64":
|
||||
return "Aarch64(target_lexicon::Aarch64Architecture::Aarch64)"
|
||||
case "armv7":
|
||||
return "Arm(target_lexicon::ArmArchitecture::Armv7)"
|
||||
case _:
|
||||
return arch.capitalize()
|
||||
|
||||
|
||||
def prepare_value(value: dict) -> dict:
|
||||
# Convert fields from snake case to camel case for enums
|
||||
for key in ["arch", "os", "libc"]:
|
||||
value[key] = value[key].title()
|
||||
value["os"] = value["os"].title()
|
||||
value["arch"] = prepare_arch(value["arch"])
|
||||
value["name"] = prepare_name(value["name"])
|
||||
value["libc"] = prepare_libc(value["libc"])
|
||||
return value
|
||||
|
||||
|
||||
|
|
|
@ -54,10 +54,7 @@ pub(crate) async fn list(
|
|||
));
|
||||
}
|
||||
for download in downloads {
|
||||
output.insert((
|
||||
download.python_version().version().clone(),
|
||||
download.key().to_owned(),
|
||||
));
|
||||
output.insert((download.python_version().version().clone(), download.key()));
|
||||
}
|
||||
|
||||
for (version, key) in output {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue