Skip to content

Commit 0c5ae1f

Browse files
authored
Differentiate between implicit vs explicit architecture requests (#13723)
In #13721 (comment) I presumed that all the installation problems in #13722 were solved by #13709 but they were not because we don't differentiate between implicit and explicit architecture requests so a request for `aarch64` is considered satisfied by an existing `x86-64` installation even if the user explicitly requested that architecture. Now, we track if it was explicit or implicit, requiring an exact match in the former case, and a `supports` in the latter. We considered doing this for other items in the request, like the operating system but we don't have a `supports()` concept there. It might make sense for libc in the future.
1 parent 067b03c commit 0c5ae1f

File tree

4 files changed

+48
-30
lines changed

4 files changed

+48
-30
lines changed

crates/uv-python/src/discovery.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2723,7 +2723,7 @@ mod tests {
27232723

27242724
use crate::{
27252725
discovery::{PythonRequest, VersionRequest},
2726-
downloads::PythonDownloadRequest,
2726+
downloads::{ArchRequest, PythonDownloadRequest},
27272727
implementation::ImplementationName,
27282728
platform::{Arch, Libc, Os},
27292729
};
@@ -2813,10 +2813,10 @@ mod tests {
28132813
PythonVariant::Default
28142814
)),
28152815
implementation: Some(ImplementationName::CPython),
2816-
arch: Some(Arch {
2816+
arch: Some(ArchRequest::Explicit(Arch {
28172817
family: Architecture::Aarch64(Aarch64Architecture::Aarch64),
28182818
variant: None
2819-
}),
2819+
})),
28202820
os: Some(Os(target_lexicon::OperatingSystem::Darwin(None))),
28212821
libc: Some(Libc::None),
28222822
prereleases: None
@@ -2848,10 +2848,10 @@ mod tests {
28482848
PythonVariant::Default
28492849
)),
28502850
implementation: None,
2851-
arch: Some(Arch {
2851+
arch: Some(ArchRequest::Explicit(Arch {
28522852
family: Architecture::Aarch64(Aarch64Architecture::Aarch64),
28532853
variant: None
2854-
}),
2854+
})),
28552855
os: None,
28562856
libc: None,
28572857
prereleases: None

crates/uv-python/src/downloads.rs

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub struct ManagedPythonDownload {
116116
pub struct PythonDownloadRequest {
117117
pub(crate) version: Option<VersionRequest>,
118118
pub(crate) implementation: Option<ImplementationName>,
119-
pub(crate) arch: Option<Arch>,
119+
pub(crate) arch: Option<ArchRequest>,
120120
pub(crate) os: Option<Os>,
121121
pub(crate) libc: Option<Libc>,
122122

@@ -125,11 +125,40 @@ pub struct PythonDownloadRequest {
125125
pub(crate) prereleases: Option<bool>,
126126
}
127127

128+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129+
pub enum ArchRequest {
130+
Explicit(Arch),
131+
Environment(Arch),
132+
}
133+
134+
impl Display for ArchRequest {
135+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136+
match self {
137+
Self::Explicit(arch) | Self::Environment(arch) => write!(f, "{arch}"),
138+
}
139+
}
140+
}
141+
142+
impl ArchRequest {
143+
pub(crate) fn satisfied_by(self, arch: Arch) -> bool {
144+
match self {
145+
Self::Explicit(request) => request == arch,
146+
Self::Environment(env) => env.supports(arch),
147+
}
148+
}
149+
150+
pub fn inner(&self) -> Arch {
151+
match self {
152+
Self::Explicit(arch) | Self::Environment(arch) => *arch,
153+
}
154+
}
155+
}
156+
128157
impl PythonDownloadRequest {
129158
pub fn new(
130159
version: Option<VersionRequest>,
131160
implementation: Option<ImplementationName>,
132-
arch: Option<Arch>,
161+
arch: Option<ArchRequest>,
133162
os: Option<Os>,
134163
libc: Option<Libc>,
135164
prereleases: Option<bool>,
@@ -158,7 +187,7 @@ impl PythonDownloadRequest {
158187

159188
#[must_use]
160189
pub fn with_arch(mut self, arch: Arch) -> Self {
161-
self.arch = Some(arch);
190+
self.arch = Some(ArchRequest::Explicit(arch));
162191
self
163192
}
164193

@@ -219,7 +248,7 @@ impl PythonDownloadRequest {
219248
/// Platform information is pulled from the environment.
220249
pub fn fill_platform(mut self) -> Result<Self, Error> {
221250
if self.arch.is_none() {
222-
self.arch = Some(Arch::from_env());
251+
self.arch = Some(ArchRequest::Environment(Arch::from_env()));
223252
}
224253
if self.os.is_none() {
225254
self.os = Some(Os::from_env());
@@ -238,18 +267,6 @@ impl PythonDownloadRequest {
238267
Ok(self)
239268
}
240269

241-
/// Construct a new [`PythonDownloadRequest`] with platform information from the environment.
242-
pub fn from_env() -> Result<Self, Error> {
243-
Ok(Self::new(
244-
None,
245-
None,
246-
Some(Arch::from_env()),
247-
Some(Os::from_env()),
248-
Some(Libc::from_env()?),
249-
None,
250-
))
251-
}
252-
253270
pub fn implementation(&self) -> Option<&ImplementationName> {
254271
self.implementation.as_ref()
255272
}
@@ -258,7 +275,7 @@ impl PythonDownloadRequest {
258275
self.version.as_ref()
259276
}
260277

261-
pub fn arch(&self) -> Option<&Arch> {
278+
pub fn arch(&self) -> Option<&ArchRequest> {
262279
self.arch.as_ref()
263280
}
264281

@@ -288,7 +305,7 @@ impl PythonDownloadRequest {
288305
}
289306

290307
if let Some(arch) = &self.arch {
291-
if !arch.supports(key.arch) {
308+
if !arch.satisfied_by(key.arch) {
292309
return false;
293310
}
294311
}
@@ -366,7 +383,7 @@ impl PythonDownloadRequest {
366383
}
367384
if let Some(arch) = self.arch() {
368385
let interpreter_arch = Arch::from(&interpreter.platform().arch());
369-
if &interpreter_arch != arch {
386+
if !arch.satisfied_by(interpreter_arch) {
370387
debug!(
371388
"Skipping interpreter at `{executable}`: architecture `{interpreter_arch}` does not match request `{arch}`"
372389
);
@@ -408,7 +425,7 @@ impl From<&ManagedPythonInstallation> for PythonDownloadRequest {
408425
"Managed Python installations are expected to always have known implementation names, found {name}"
409426
),
410427
},
411-
Some(key.arch),
428+
Some(ArchRequest::Explicit(key.arch)),
412429
Some(key.os),
413430
Some(key.libc),
414431
Some(key.prerelease.is_some()),
@@ -478,7 +495,7 @@ impl FromStr for PythonDownloadRequest {
478495
);
479496
}
480497
3 => os = Some(Os::from_str(part)?),
481-
4 => arch = Some(Arch::from_str(part)?),
498+
4 => arch = Some(ArchRequest::Explicit(Arch::from_str(part)?)),
482499
5 => libc = Some(Libc::from_str(part)?),
483500
_ => return Err(Error::TooManyParts(s.to_string())),
484501
}

crates/uv/src/commands/python/install.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ impl InstallRequest {
6161
Ok(download) => download,
6262
Err(downloads::Error::NoDownloadFound(request))
6363
if request.libc().is_some_and(Libc::is_musl)
64-
&& request.arch().is_some_and(Arch::is_arm) =>
64+
&& request
65+
.arch()
66+
.is_some_and(|arch| Arch::is_arm(&arch.inner())) =>
6567
{
6668
return Err(anyhow::anyhow!(
6769
"uv does not yet provide musl Python distributions on aarch64."

crates/uv/tests/it/python_find.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,14 @@ fn python_find() {
9797
let arch = Arch::from_env();
9898

9999
uv_snapshot!(context.filters(), context.python_find()
100-
.arg(format!("cpython-3.12-{os}-{arch}"))
101-
, @r###"
100+
.arg(format!("cpython-3.12-{os}-{arch}")), @r"
102101
success: true
103102
exit_code: 0
104103
----- stdout -----
105104
[PYTHON-3.12]
106105
107106
----- stderr -----
108-
"###);
107+
");
109108

110109
// Request PyPy (which should be missing)
111110
uv_snapshot!(context.filters(), context.python_find().arg("pypy"), @r"

0 commit comments

Comments
 (0)