Repository: pdm-project/pdm

Issue:
metadata.files sorting in pdm.lock changes between computers
## Steps to reproduce

* Add several package sources which contain the same wheels with different hashes (for example https://piwheels.org for deploying to raspberry pi)
* run `pdm lock` on different computers (linux vs windows)

## Actual behavior

The following diff is observed between the two computers
```diff
 "uritemplate 3.0.1" = [
-    {url = "https://files.pythonhosted.org/packages/bf/0c/60d82c077998feb631608dca3cc1fe19ac074e772bf0c24cf409b977b815/uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
     {url = "https://www.piwheels.org/simple/uritemplate/uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
+    {url = "https://files.pythonhosted.org/packages/bf/0c/60d82c077998feb631608dca3cc1fe19ac074e772bf0c24cf409b977b815/uritemplate-3.0.1-py2.py3-none-any.whl", hash = "sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f"},
     {url = "https://files.pythonhosted.org/packages/42/da/fa9aca2d866f932f17703b3b5edb7b17114bb261122b6e535ef0d9f618f8/uritemplate-3.0.1.tar.gz", hash = "sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"},
 ]
```

## Expected behavior

pdm.lock should be the same no matter which computer last locked it.

## Environment Information

Debian docker image with PDM 2.0.2 and WIndows 10 workstation with PDM 2.0.2


Comments:

Patch:
diff --git a/news/1256.bugfix.md b/news/1256.bugfix.md
new file mode 100644
index 00000000..c111cf60
--- /dev/null
+++ b/news/1256.bugfix.md
@@ -0,0 +1,1 @@
+Stabilize sorting of URLs in `metadata.files` in `pdm.lock`.
diff --git a/pdm/cli/utils.py b/pdm/cli/utils.py
index e822f7fc..725d0c94 100644
--- a/pdm/cli/utils.py
+++ b/pdm/cli/utils.py
@@ -465,7 +465,7 @@ def format_lockfile(
                 continue
             array = tomlkit.array().multiline(True)
             for link, hash_value in sorted(
-                v.hashes.items(), key=lambda l: l[0].filename
+                v.hashes.items(), key=lambda l_h: (l_h[0].url_without_fragment, l_h[1])
             ):
                 inline = make_inline_table(
                     {"url": link.url_without_fragment, "hash": hash_value}
diff --git a/pdm/resolver/core.py b/pdm/resolver/core.py
index 4a6993c2..195af3bc 100644
--- a/pdm/resolver/core.py
+++ b/pdm/resolver/core.py
@@ -20,11 +20,10 @@ def resolve(
     max_rounds: int = 10000,
 ) -> tuple[dict[str, Candidate], dict[str, list[Requirement]]]:
     """Core function to perform the actual resolve process.
-    Return a tuple containing 3 items:
+    Return a tuple containing 2 items:
 
         1. A map of pinned candidates
         2. A map of resolved dependencies for each dependency group
-        3. A map of package descriptions fetched from PyPI source
     """
     requirements.append(PythonRequirement.from_pyspec_set(requires_python))
     provider = cast(BaseProvider, resolver.provider)


Test patch:
diff --git a/tests/cli/test_lock.py b/tests/cli/test_lock.py
index 071ab9ff..80a10cf9 100644
--- a/tests/cli/test_lock.py
+++ b/tests/cli/test_lock.py
@@ -30,24 +30,23 @@ def test_lock_refresh(invoke, project, repository):
     assert project.is_lockfile_hash_match()
     assert not project.lockfile["metadata"]["files"].get("requests 2.19.1")
     project.add_dependencies({"requests": parse_requirement("requests>=2.0")})
-    repository.get_hashes = (
-        lambda c: {
-            Link(
-                "http://example.com/requests-2.19.1-py3-none-any.whl"
-            ): "sha256:abcdef123456"
-        }
+    url_hashes = {
+        "http://example.com/requests-2.19.1-py3-none-any.whl": "sha256:abcdef123456",
+        "http://example2.com/requests-2.19.1-py3-none-AMD64.whl": "sha256:abcdef123456",
+        "http://example1.com/requests-2.19.1-py3-none-any.whl": "sha256:abcdef123456",
+    }
+    repository.get_hashes = lambda c: (
+        {Link(url): hash for url, hash in url_hashes.items()}
         if c.identify() == "requests"
         else {}
     )
-    print(project.lockfile)
     assert not project.is_lockfile_hash_match()
     result = invoke(["lock", "--refresh", "-v"], obj=project)
     assert result.exit_code == 0
     assert project.is_lockfile_hash_match()
-    assert project.lockfile["metadata"]["files"]["requests 2.19.1"][0] == {
-        "url": "http://example.com/requests-2.19.1-py3-none-any.whl",
-        "hash": "sha256:abcdef123456",
-    }
+    assert project.lockfile["metadata"]["files"]["requests 2.19.1"] == [
+        {"url": url, "hash": hash} for url, hash in sorted(url_hashes.items())
+    ]
 
 
 def test_lock_refresh_keep_consistent(invoke, project, repository):