Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7,389 changes: 4,926 additions & 2,463 deletions crates/uv-python/download-metadata.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions crates/uv-python/fetch-download-metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class PythonDownload:
implementation: ImplementationName
filename: str
url: str
build: str
sha256: str | None = None
build_options: list[str] = field(default_factory=list)
variant: Variant | None = None
Expand Down Expand Up @@ -397,6 +398,7 @@ def _parse_download_asset(self, asset: dict[str, Any]) -> PythonDownload | None:
implementation=self.implementation,
filename=filename,
url=url,
build=str(release),
build_options=build_options,
variant=variant,
sha256=sha256,
Expand Down Expand Up @@ -507,6 +509,7 @@ async def _fetch_downloads(self) -> list[PythonDownload]:
python_version = Version.from_str(version["python_version"])
if python_version < (3, 7, 0):
continue
pypy_version = version["pypy_version"]
for file in version["files"]:
arch = self._normalize_arch(file["arch"])
platform = self._normalize_os(file["platform"])
Expand All @@ -523,6 +526,7 @@ async def _fetch_downloads(self) -> list[PythonDownload]:
implementation=self.implementation,
filename=file["filename"],
url=file["download_url"],
build=pypy_version,
)
# Only keep the latest pypy version of each arch/platform
if (python_version, arch, platform) not in results:
Expand Down Expand Up @@ -612,6 +616,7 @@ async def _fetch_downloads(self) -> list[PythonDownload]:
implementation=self.implementation,
filename=asset["name"],
url=url,
build=pyodide_version,
)
)

Expand Down Expand Up @@ -708,6 +713,7 @@ async def _fetch_downloads(self) -> list[PythonDownload]:
implementation=self.implementation,
filename=asset["name"],
url=url,
build=graalpy_version,
sha256=sha256,
)
# Only keep the latest GraalPy version of each arch/platform
Expand Down Expand Up @@ -811,6 +817,7 @@ def sort_key(download: PythonDownload) -> tuple:
"url": download.url,
"sha256": download.sha256,
"variant": download.variant if download.variant else None,
"build": download.build,
}

VERSIONS_FILE.parent.mkdir(parents=True, exist_ok=True)
Expand Down
32 changes: 31 additions & 1 deletion crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::interpreter::{StatusCodeError, UnexpectedResponseError};
use crate::managed::{ManagedPythonInstallations, PythonMinorVersionLink};
#[cfg(windows)]
use crate::microsoft_store::find_microsoft_store_pythons;
use crate::python_version::python_build_versions_from_env;
use crate::virtualenv::Error as VirtualEnvError;
use crate::virtualenv::{
CondaEnvironmentKind, conda_environment_from_env, virtualenv_from_env,
Expand Down Expand Up @@ -263,6 +264,9 @@ pub enum Error {
// TODO(zanieb): Is this error case necessary still? We should probably drop it.
#[error("Interpreter discovery for `{0}` requires `{1}` but only `{2}` is allowed")]
SourceNotAllowed(PythonRequest, PythonSource, PythonPreference),

#[error(transparent)]
BuildVersion(#[from] crate::python_version::BuildVersionError),
}

/// Lazily iterate over Python executables in mutable virtual environments.
Expand Down Expand Up @@ -342,6 +346,9 @@ fn python_executables_from_installed<'a>(
installed_installations.root().user_display()
);
let installations = installed_installations.find_matching_current_platform()?;

let build_versions = python_build_versions_from_env()?;

// Check that the Python version and platform satisfy the request to avoid
// unnecessary interpreter queries later
Ok(installations
Expand All @@ -355,6 +362,22 @@ fn python_executables_from_installed<'a>(
debug!("Skipping managed installation `{installation}`: does not satisfy requested platform `{platform}`");
return false;
}

if let Some(requested_build) = build_versions.get(&installation.implementation()) {
let Some(installation_build) = installation.build() else {
debug!(
"Skipping managed installation `{installation}`: a build version was requested but is not recorded for this installation"
);
return false;
};
if installation_build != requested_build {
debug!(
"Skipping managed installation `{installation}`: requested build version `{requested_build}` does not match installation build version `{installation_build}`"
);
return false;
}
}

true
})
.inspect(|installation| debug!("Found managed installation `{installation}`"))
Expand Down Expand Up @@ -1218,6 +1241,7 @@ pub fn find_python_installations<'a>(
return Box::new(iter::once(Err(Error::InvalidVersionRequest(err))));
}
}

Box::new({
debug!("Searching for {request} in {sources}");
python_interpreters(
Expand All @@ -1229,7 +1253,9 @@ pub fn find_python_installations<'a>(
cache,
preview,
)
.filter_ok(|(_source, interpreter)| request.satisfied_by_interpreter(interpreter))
.filter_ok(move |(_source, interpreter)| {
request.satisfied_by_interpreter(interpreter)
})
.map_ok(|tuple| Ok(PythonInstallation::from_tuple(tuple)))
})
}
Expand Down Expand Up @@ -3186,6 +3212,7 @@ mod tests {
arch: None,
os: None,
libc: None,
build: None,
prereleases: None
})
);
Expand All @@ -3205,6 +3232,7 @@ mod tests {
))),
os: Some(Os::new(target_lexicon::OperatingSystem::Darwin(None))),
libc: Some(Libc::None),
build: None,
prereleases: None
})
);
Expand All @@ -3221,6 +3249,7 @@ mod tests {
arch: None,
os: None,
libc: None,
build: None,
prereleases: None
})
);
Expand All @@ -3240,6 +3269,7 @@ mod tests {
))),
os: None,
libc: None,
build: None,
prereleases: None
})
);
Expand Down
Loading
Loading