Repository: pypa/pip

Issue:
Sort wheel files such that the build number is used only as a tie-breaker
**pip version**

21.0.1

**Python version**

N/A

**OS**

N/A

**Additional information**

_No response_

**Description**

Pip currently [sorts a wheel](https://github.com/pypa/pip/blob/a48ad5385b234097d51283b08c3d933fd81ef534/src/pip/_internal/index/package_finder.py#L537-L540)'s build number higher than the other tags in a wheel.



**Expected behavior**

The build number should be used to break ties between wheel files when all other details are equal (i.e. name, version, and other tags). See the [update to PEP 427](https://github.com/python/peps/commit/9e81dc8092505810dc144ccf6c4adc8f1a3becba) for more details.

**How to Reproduce**

_No response_

**Output**

_No response_

**Code of Conduct**

- [X] I agree to follow the PSF Code of Conduct




Discussion:
The implementation is easy, just flip the last two components here: https://github.com/pypa/pip/blob/bbf8466088655d22cd46b286c8f0b8150754c1d9/src/pip/_internal/index/package_finder.py#L537-L540

(i.e. `pri, build_tag` instead of `build_tag, pri`.)

The bulk of the work would be to come up with suitable test cases for this.
I am working on a test for this

Resolving patch:
diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py
--- a/src/pip/_internal/index/package_finder.py
+++ b/src/pip/_internal/index/package_finder.py
@@ -44,7 +44,7 @@
 
 BuildTag = Union[Tuple[()], Tuple[int, str]]
 CandidateSortingKey = (
-    Tuple[int, int, int, _BaseVersion, BuildTag, Optional[int]]
+    Tuple[int, int, int, _BaseVersion, Optional[int], BuildTag]
 )
 
 
@@ -530,7 +530,7 @@ def _sort_key(self, candidate):
         yank_value = -1 * int(link.is_yanked)  # -1 for yanked.
         return (
             has_allowed_hash, yank_value, binary_preference, candidate.version,
-            build_tag, pri,
+            pri, build_tag,
         )
 
     def sort_best_candidate(


Test patch:
diff --git a/tests/unit/test_finder.py b/tests/unit/test_finder.py
--- a/tests/unit/test_finder.py
+++ b/tests/unit/test_finder.py
@@ -209,6 +209,8 @@ def test_existing_over_wheel_priority(self, data):
         with pytest.raises(BestVersionAlreadyInstalled):
             finder.find_requirement(req, True)
 
+
+class TestCandidateEvaluator:
     def test_link_sorting(self):
         """
         Test link sorting
@@ -249,7 +251,8 @@ def test_link_sorting(self):
         results = sorted(links, key=sort_key, reverse=True)
         results2 = sorted(reversed(links), key=sort_key, reverse=True)
 
-        assert links == results == results2, results2
+        assert links == results, results
+        assert links == results2, results2
 
     def test_link_sorting_wheels_with_build_tags(self):
         """Verify build tags affect sorting."""
@@ -274,7 +277,47 @@ def test_link_sorting_wheels_with_build_tags(self):
         sort_key = candidate_evaluator._sort_key
         results = sorted(links, key=sort_key, reverse=True)
         results2 = sorted(reversed(links), key=sort_key, reverse=True)
-        assert links == results == results2, results2
+
+        assert links == results, results
+        assert links == results2, results2
+
+    def test_build_tag_is_less_important_than_other_tags(self):
+        links = [
+            InstallationCandidate(
+                "simple",
+                "1.0",
+                Link('simple-1.0-1-py3-abi3-linux_x86_64.whl'),
+            ),
+            InstallationCandidate(
+                "simple",
+                '1.0',
+                Link('simple-1.0-2-py3-abi3-linux_i386.whl'),
+            ),
+            InstallationCandidate(
+                "simple",
+                '1.0',
+                Link('simple-1.0-2-py3-any-none.whl'),
+            ),
+            InstallationCandidate(
+                "simple",
+                '1.0',
+                Link('simple-1.0.tar.gz'),
+            ),
+        ]
+        valid_tags = [
+            Tag('py3', 'abi3', 'linux_x86_64'),
+            Tag('py3', 'abi3', 'linux_i386'),
+            Tag('py3', 'any', 'none'),
+        ]
+        evaluator = CandidateEvaluator(
+            'my-project', supported_tags=valid_tags, specifier=SpecifierSet(),
+        )
+        sort_key = evaluator._sort_key
+        results = sorted(links, key=sort_key, reverse=True)
+        results2 = sorted(reversed(links), key=sort_key, reverse=True)
+
+        assert links == results, results
+        assert links == results2, results2
 
 
 def test_finder_priority_file_over_page(data):