mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-03 13:14:41 +00:00
Incorporate build tag into wheel prioritization (#3781)
## Summary It turns out that in the [spec](https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-name-convention), if a wheel filename includes a build tag, then we need to use it to break ties. This PR implements that behavior. (Previously, we dropped the build tag entirely.) Closes #3779. ## Test Plan Run: `cargo run pip install -i https://pypi.anaconda.org/intel/simple mkl_fft==1.3.8 --python-platform linux --python-version 3.10`. This now resolves without error. Previously, we selected build tag 63 of `mkl_fft==1.3.8`, which led to an incompatibility with NumPy. Now, we select build tag 70.
This commit is contained in:
parent
5bebaddc24
commit
a9d9a6c13f
11 changed files with 132 additions and 29 deletions
63
crates/distribution-filename/src/build_tag.rs
Normal file
63
crates/distribution-filename/src/build_tag.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum BuildTagError {
|
||||||
|
#[error("must not be empty")]
|
||||||
|
Empty,
|
||||||
|
#[error("must start with a digit")]
|
||||||
|
NoLeadingDigit,
|
||||||
|
#[error(transparent)]
|
||||||
|
ParseInt(#[from] ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The optional build tag for a wheel:
|
||||||
|
///
|
||||||
|
/// > Must start with a digit. Acts as a tie-breaker if two wheel file names are the same in all
|
||||||
|
/// > other respects (i.e. name, version, and other tags). Sort as an empty tuple if unspecified,
|
||||||
|
/// > else sort as a two-item tuple with the first item being the initial digits as an int, and the
|
||||||
|
/// > second item being the remainder of the tag as a str.
|
||||||
|
///
|
||||||
|
/// See: <https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-name-convention>
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Hash,
|
||||||
|
Ord,
|
||||||
|
PartialOrd,
|
||||||
|
rkyv::Archive,
|
||||||
|
rkyv::Deserialize,
|
||||||
|
rkyv::Serialize,
|
||||||
|
)]
|
||||||
|
#[archive(check_bytes)]
|
||||||
|
#[archive_attr(derive(Debug))]
|
||||||
|
pub struct BuildTag(u32, Option<Arc<str>>);
|
||||||
|
|
||||||
|
impl FromStr for BuildTag {
|
||||||
|
type Err = BuildTagError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
// A build tag must not be empty.
|
||||||
|
if s.is_empty() {
|
||||||
|
return Err(BuildTagError::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A build tag must start with a digit.
|
||||||
|
let (prefix, suffix) = match s.find(|c: char| !c.is_ascii_digit()) {
|
||||||
|
// Ex) `abc`
|
||||||
|
Some(0) => return Err(BuildTagError::NoLeadingDigit),
|
||||||
|
// Ex) `123abc`
|
||||||
|
Some(split) => {
|
||||||
|
let (prefix, suffix) = s.split_at(split);
|
||||||
|
(prefix, Some(suffix))
|
||||||
|
}
|
||||||
|
// Ex) `123`
|
||||||
|
None => (s, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(BuildTag(prefix.parse::<u32>()?, suffix.map(Arc::from)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,11 @@ use std::fmt::{Display, Formatter};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
|
|
||||||
|
pub use build_tag::{BuildTag, BuildTagError};
|
||||||
pub use source_dist::{SourceDistExtension, SourceDistFilename, SourceDistFilenameError};
|
pub use source_dist::{SourceDistExtension, SourceDistFilename, SourceDistFilenameError};
|
||||||
pub use wheel::{WheelFilename, WheelFilenameError};
|
pub use wheel::{WheelFilename, WheelFilenameError};
|
||||||
|
|
||||||
|
mod build_tag;
|
||||||
mod source_dist;
|
mod source_dist;
|
||||||
mod wheel;
|
mod wheel;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
source: crates/distribution-filename/src/wheel.rs
|
source: crates/distribution-filename/src/wheel.rs
|
||||||
expression: "WheelFilename::from_str(\"foo-1.2.3-build-python-abi-platform.whl\")"
|
expression: "WheelFilename::from_str(\"foo-1.2.3-12-python-abi-platform.whl\")"
|
||||||
---
|
---
|
||||||
Ok(
|
Ok(
|
||||||
WheelFilename {
|
WheelFilename {
|
||||||
|
|
@ -8,6 +8,12 @@ Ok(
|
||||||
"foo",
|
"foo",
|
||||||
),
|
),
|
||||||
version: "1.2.3",
|
version: "1.2.3",
|
||||||
|
build_tag: Some(
|
||||||
|
BuildTag(
|
||||||
|
12,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
),
|
||||||
python_tag: [
|
python_tag: [
|
||||||
"python",
|
"python",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Ok(
|
||||||
"foo",
|
"foo",
|
||||||
),
|
),
|
||||||
version: "1.2.3",
|
version: "1.2.3",
|
||||||
|
build_tag: None,
|
||||||
python_tag: [
|
python_tag: [
|
||||||
"ab",
|
"ab",
|
||||||
"cd",
|
"cd",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ Ok(
|
||||||
"foo",
|
"foo",
|
||||||
),
|
),
|
||||||
version: "1.2.3",
|
version: "1.2.3",
|
||||||
|
build_tag: None,
|
||||||
python_tag: [
|
python_tag: [
|
||||||
"foo",
|
"foo",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,15 @@ use pep440_rs::{Version, VersionParseError};
|
||||||
use platform_tags::{TagCompatibility, Tags};
|
use platform_tags::{TagCompatibility, Tags};
|
||||||
use uv_normalize::{InvalidNameError, PackageName};
|
use uv_normalize::{InvalidNameError, PackageName};
|
||||||
|
|
||||||
|
use crate::{BuildTag, BuildTagError};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||||
#[archive(check_bytes)]
|
#[archive(check_bytes)]
|
||||||
#[archive_attr(derive(Debug))]
|
#[archive_attr(derive(Debug))]
|
||||||
pub struct WheelFilename {
|
pub struct WheelFilename {
|
||||||
pub name: PackageName,
|
pub name: PackageName,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
|
pub build_tag: Option<BuildTag>,
|
||||||
pub python_tag: Vec<String>,
|
pub python_tag: Vec<String>,
|
||||||
pub abi_tag: Vec<String>,
|
pub abi_tag: Vec<String>,
|
||||||
pub platform_tag: Vec<String>,
|
pub platform_tag: Vec<String>,
|
||||||
|
|
@ -57,16 +60,6 @@ impl WheelFilename {
|
||||||
compatible_tags.compatibility(&self.python_tag, &self.abi_tag, &self.platform_tag)
|
compatible_tags.compatibility(&self.python_tag, &self.abi_tag, &self.platform_tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the tag for this wheel.
|
|
||||||
pub fn get_tag(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{}-{}-{}",
|
|
||||||
self.python_tag.join("."),
|
|
||||||
self.abi_tag.join("."),
|
|
||||||
self.platform_tag.join(".")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The wheel filename without the extension.
|
/// The wheel filename without the extension.
|
||||||
pub fn stem(&self) -> String {
|
pub fn stem(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -82,6 +75,16 @@ impl WheelFilename {
|
||||||
Self::parse(stem, stem)
|
Self::parse(stem, stem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the tag for this wheel.
|
||||||
|
fn get_tag(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"{}-{}-{}",
|
||||||
|
self.python_tag.join("."),
|
||||||
|
self.abi_tag.join("."),
|
||||||
|
self.platform_tag.join(".")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a wheel filename from the stem (e.g., `foo-1.2.3-py3-none-any`).
|
/// Parse a wheel filename from the stem (e.g., `foo-1.2.3-py3-none-any`).
|
||||||
///
|
///
|
||||||
/// The originating `filename` is used for high-fidelity error messages.
|
/// The originating `filename` is used for high-fidelity error messages.
|
||||||
|
|
@ -129,7 +132,7 @@ impl WheelFilename {
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let (name, version, python_tag, abi_tag, platform_tag) =
|
let (name, version, build_tag, python_tag, abi_tag, platform_tag) =
|
||||||
if let Some(platform_tag) = parts.next() {
|
if let Some(platform_tag) = parts.next() {
|
||||||
if parts.next().is_some() {
|
if parts.next().is_some() {
|
||||||
return Err(WheelFilenameError::InvalidWheelFileName(
|
return Err(WheelFilenameError::InvalidWheelFileName(
|
||||||
|
|
@ -140,6 +143,7 @@ impl WheelFilename {
|
||||||
(
|
(
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
|
Some(build_tag_or_python_tag),
|
||||||
python_tag_or_abi_tag,
|
python_tag_or_abi_tag,
|
||||||
abi_tag_or_platform_tag,
|
abi_tag_or_platform_tag,
|
||||||
platform_tag,
|
platform_tag,
|
||||||
|
|
@ -148,6 +152,7 @@ impl WheelFilename {
|
||||||
(
|
(
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
|
None,
|
||||||
build_tag_or_python_tag,
|
build_tag_or_python_tag,
|
||||||
python_tag_or_abi_tag,
|
python_tag_or_abi_tag,
|
||||||
abi_tag_or_platform_tag,
|
abi_tag_or_platform_tag,
|
||||||
|
|
@ -158,9 +163,16 @@ impl WheelFilename {
|
||||||
.map_err(|err| WheelFilenameError::InvalidPackageName(filename.to_string(), err))?;
|
.map_err(|err| WheelFilenameError::InvalidPackageName(filename.to_string(), err))?;
|
||||||
let version = Version::from_str(version)
|
let version = Version::from_str(version)
|
||||||
.map_err(|err| WheelFilenameError::InvalidVersion(filename.to_string(), err))?;
|
.map_err(|err| WheelFilenameError::InvalidVersion(filename.to_string(), err))?;
|
||||||
|
let build_tag = build_tag
|
||||||
|
.map(|build_tag| {
|
||||||
|
BuildTag::from_str(build_tag)
|
||||||
|
.map_err(|err| WheelFilenameError::InvalidBuildTag(filename.to_string(), err))
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
version,
|
version,
|
||||||
|
build_tag,
|
||||||
python_tag: python_tag.split('.').map(String::from).collect(),
|
python_tag: python_tag.split('.').map(String::from).collect(),
|
||||||
abi_tag: abi_tag.split('.').map(String::from).collect(),
|
abi_tag: abi_tag.split('.').map(String::from).collect(),
|
||||||
platform_tag: platform_tag.split('.').map(String::from).collect(),
|
platform_tag: platform_tag.split('.').map(String::from).collect(),
|
||||||
|
|
@ -214,10 +226,12 @@ impl Serialize for WheelFilename {
|
||||||
pub enum WheelFilenameError {
|
pub enum WheelFilenameError {
|
||||||
#[error("The wheel filename \"{0}\" is invalid: {1}")]
|
#[error("The wheel filename \"{0}\" is invalid: {1}")]
|
||||||
InvalidWheelFileName(String, String),
|
InvalidWheelFileName(String, String),
|
||||||
#[error("The wheel filename \"{0}\" has an invalid version part: {1}")]
|
#[error("The wheel filename \"{0}\" has an invalid version: {1}")]
|
||||||
InvalidVersion(String, VersionParseError),
|
InvalidVersion(String, VersionParseError),
|
||||||
#[error("The wheel filename \"{0}\" has an invalid package name")]
|
#[error("The wheel filename \"{0}\" has an invalid package name")]
|
||||||
InvalidPackageName(String, InvalidNameError),
|
InvalidPackageName(String, InvalidNameError),
|
||||||
|
#[error("The wheel filename \"{0}\" has an invalid build tag: {1}")]
|
||||||
|
InvalidBuildTag(String, BuildTagError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -276,7 +290,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn err_invalid_version() {
|
fn err_invalid_version() {
|
||||||
let err = WheelFilename::from_str("foo-x.y.z-python-abi-platform.whl").unwrap_err();
|
let err = WheelFilename::from_str("foo-x.y.z-python-abi-platform.whl").unwrap_err();
|
||||||
insta::assert_snapshot!(err, @r###"The wheel filename "foo-x.y.z-python-abi-platform.whl" has an invalid version part: expected version to start with a number, but no leading ASCII digits were found"###);
|
insta::assert_snapshot!(err, @r###"The wheel filename "foo-x.y.z-python-abi-platform.whl" has an invalid version: expected version to start with a number, but no leading ASCII digits were found"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn err_invalid_build_tag() {
|
||||||
|
let err = WheelFilename::from_str("foo-1.2.3-tag-python-abi-platform.whl").unwrap_err();
|
||||||
|
insta::assert_snapshot!(err, @r###"The wheel filename "foo-1.2.3-tag-python-abi-platform.whl" has an invalid build tag: must start with a digit"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -294,7 +314,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn ok_build_tag() {
|
fn ok_build_tag() {
|
||||||
insta::assert_debug_snapshot!(WheelFilename::from_str(
|
insta::assert_debug_snapshot!(WheelFilename::from_str(
|
||||||
"foo-1.2.3-build-python-abi-platform.whl"
|
"foo-1.2.3-12-python-abi-platform.whl"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use distribution_filename::BuildTag;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
use pep440_rs::VersionSpecifiers;
|
use pep440_rs::VersionSpecifiers;
|
||||||
|
|
@ -134,7 +135,7 @@ impl Display for IncompatibleDist {
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum WheelCompatibility {
|
pub enum WheelCompatibility {
|
||||||
Incompatible(IncompatibleWheel),
|
Incompatible(IncompatibleWheel),
|
||||||
Compatible(HashComparison, TagPriority),
|
Compatible(HashComparison, TagPriority, Option<BuildTag>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
|
@ -245,7 +246,7 @@ impl PrioritizedDist {
|
||||||
// source distribution with a matching hash over a wheel with a mismatched hash. When
|
// source distribution with a matching hash over a wheel with a mismatched hash. When
|
||||||
// the outcomes are equivalent (e.g., both have a matching hash), prefer the wheel.
|
// the outcomes are equivalent (e.g., both have a matching hash), prefer the wheel.
|
||||||
(
|
(
|
||||||
Some((wheel, WheelCompatibility::Compatible(wheel_hash, tag_priority))),
|
Some((wheel, WheelCompatibility::Compatible(wheel_hash, tag_priority, ..))),
|
||||||
Some((sdist, SourceDistCompatibility::Compatible(sdist_hash))),
|
Some((sdist, SourceDistCompatibility::Compatible(sdist_hash))),
|
||||||
) => {
|
) => {
|
||||||
if sdist_hash > wheel_hash {
|
if sdist_hash > wheel_hash {
|
||||||
|
|
@ -262,7 +263,7 @@ impl PrioritizedDist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Prefer the highest-priority, platform-compatible wheel.
|
// Prefer the highest-priority, platform-compatible wheel.
|
||||||
(Some((wheel, WheelCompatibility::Compatible(_, tag_priority))), _) => {
|
(Some((wheel, WheelCompatibility::Compatible(_, tag_priority, ..))), _) => {
|
||||||
Some(CompatibleDist::CompatibleWheel {
|
Some(CompatibleDist::CompatibleWheel {
|
||||||
wheel,
|
wheel,
|
||||||
priority: *tag_priority,
|
priority: *tag_priority,
|
||||||
|
|
@ -309,7 +310,7 @@ impl PrioritizedDist {
|
||||||
.best_wheel_index
|
.best_wheel_index
|
||||||
.map(|i| &self.0.wheels[i])
|
.map(|i| &self.0.wheels[i])
|
||||||
.and_then(|(_, compatibility)| match compatibility {
|
.and_then(|(_, compatibility)| match compatibility {
|
||||||
WheelCompatibility::Compatible(_, _) => None,
|
WheelCompatibility::Compatible(_, _, _) => None,
|
||||||
WheelCompatibility::Incompatible(incompatibility) => Some(incompatibility),
|
WheelCompatibility::Incompatible(incompatibility) => Some(incompatibility),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -415,7 +416,7 @@ impl<'a> CompatibleDist<'a> {
|
||||||
|
|
||||||
impl WheelCompatibility {
|
impl WheelCompatibility {
|
||||||
pub fn is_compatible(&self) -> bool {
|
pub fn is_compatible(&self) -> bool {
|
||||||
matches!(self, Self::Compatible(_, _))
|
matches!(self, Self::Compatible(_, _, _))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the current compatibility is more compatible than another.
|
/// Return `true` if the current compatibility is more compatible than another.
|
||||||
|
|
@ -424,12 +425,14 @@ impl WheelCompatibility {
|
||||||
/// Compatible wheel ordering is determined by tag priority.
|
/// Compatible wheel ordering is determined by tag priority.
|
||||||
pub fn is_more_compatible(&self, other: &Self) -> bool {
|
pub fn is_more_compatible(&self, other: &Self) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Self::Compatible(_, _), Self::Incompatible(_)) => true,
|
(Self::Compatible(_, _, _), Self::Incompatible(_)) => true,
|
||||||
(
|
(
|
||||||
Self::Compatible(hash, tag_priority),
|
Self::Compatible(hash, tag_priority, build_tag),
|
||||||
Self::Compatible(other_hash, other_tag_priority),
|
Self::Compatible(other_hash, other_tag_priority, other_build_tag),
|
||||||
) => (hash, tag_priority) > (other_hash, other_tag_priority),
|
) => {
|
||||||
(Self::Incompatible(_), Self::Compatible(_, _)) => false,
|
(hash, tag_priority, build_tag) > (other_hash, other_tag_priority, other_build_tag)
|
||||||
|
}
|
||||||
|
(Self::Incompatible(_), Self::Compatible(_, _, _)) => false,
|
||||||
(Self::Incompatible(incompatibility), Self::Incompatible(other_incompatibility)) => {
|
(Self::Incompatible(incompatibility), Self::Incompatible(other_incompatibility)) => {
|
||||||
incompatibility.is_more_compatible(other_incompatibility)
|
incompatibility.is_more_compatible(other_incompatibility)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -614,7 +614,7 @@ impl CacheBucket {
|
||||||
Self::FlatIndex => "flat-index-v0",
|
Self::FlatIndex => "flat-index-v0",
|
||||||
Self::Git => "git-v0",
|
Self::Git => "git-v0",
|
||||||
Self::Interpreter => "interpreter-v1",
|
Self::Interpreter => "interpreter-v1",
|
||||||
Self::Simple => "simple-v7",
|
Self::Simple => "simple-v8",
|
||||||
Self::Wheels => "wheels-v1",
|
Self::Wheels => "wheels-v1",
|
||||||
Self::Archive => "archive-v0",
|
Self::Archive => "archive-v0",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ impl FlatIndex {
|
||||||
TagCompatibility::Compatible(priority) => priority,
|
TagCompatibility::Compatible(priority) => priority,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if hashes line up
|
// Check if hashes line up.
|
||||||
let hash = if let HashPolicy::Validate(required) = hasher.get_package(&filename.name) {
|
let hash = if let HashPolicy::Validate(required) = hasher.get_package(&filename.name) {
|
||||||
if hashes.is_empty() {
|
if hashes.is_empty() {
|
||||||
HashComparison::Missing
|
HashComparison::Missing
|
||||||
|
|
@ -188,7 +188,10 @@ impl FlatIndex {
|
||||||
HashComparison::Matched
|
HashComparison::Matched
|
||||||
};
|
};
|
||||||
|
|
||||||
WheelCompatibility::Compatible(hash, priority)
|
// Break ties with the build tag.
|
||||||
|
let build_tag = filename.build_tag.clone();
|
||||||
|
|
||||||
|
WheelCompatibility::Compatible(hash, priority, build_tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the [`FlatDistributions`] for the given package name.
|
/// Get the [`FlatDistributions`] for the given package name.
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ Ok(
|
||||||
"anyio",
|
"anyio",
|
||||||
),
|
),
|
||||||
version: "4.3.0",
|
version: "4.3.0",
|
||||||
|
build_tag: None,
|
||||||
python_tag: [
|
python_tag: [
|
||||||
"py3",
|
"py3",
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -554,7 +554,10 @@ impl VersionMapLazy {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
WheelCompatibility::Compatible(hash, priority)
|
// Break ties with the build tag.
|
||||||
|
let build_tag = filename.build_tag.clone();
|
||||||
|
|
||||||
|
WheelCompatibility::Compatible(hash, priority, build_tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue