feat: handle url req in wheelmaker (#3569)

Adapt `wheelmaker` so that it now can also handle PEP 508 URL
requirements using the `req.url` attribute.

---------

Co-authored-by: Ignas Anikevicius <240938+aignas@users.noreply.github.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15118f5..96e7dcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -102,6 +102,8 @@
   {obj}`PyExecutableInfo.interpreter_args`,
   {obj}`PyExecutableInfo.stage2_bootstrap`, and
   {obj}`PyExecutableInfo.venv_python_exe`.
+* (tools/wheelmaker.py) Added support for URL requirements according to PEP 508
+  in Requires-Dist metadata. ([#3569](https://github.com/bazel-contrib/rules_python/pull/3569))
 
 {#v1-8-3}
 ## [1.8.3] - 2026-01-27
diff --git a/tests/tools/wheelmaker_test.py b/tests/tools/wheelmaker_test.py
index 288dde7..85094af 100644
--- a/tests/tools/wheelmaker_test.py
+++ b/tests/tools/wheelmaker_test.py
@@ -71,5 +71,41 @@
                 self.assertEqual(got, want)
 
 
+class GetNewRequirementLineTest(unittest.TestCase):
+    def test_requirement(self):
+        result = wheelmaker.get_new_requirement_line("requests>=2.0", "")
+        self.assertEqual(result, "Requires-Dist: requests>=2.0")
+
+    def test_requirement_and_extra(self):
+        result = wheelmaker.get_new_requirement_line("requests>=2.0", "extra=='dev'")
+        self.assertEqual(result, "Requires-Dist: requests>=2.0; extra=='dev'")
+
+    def test_requirement_with_url(self):
+        result = wheelmaker.get_new_requirement_line(
+            "requests @ git+https://github.com/psf/requests.git@3aa6386c3", ""
+        )
+        self.assertEqual(
+            result,
+            "Requires-Dist: requests @ git+https://github.com/psf/requests.git@3aa6386c3",
+        )
+
+    def test_requirement_with_marker(self):
+        result = wheelmaker.get_new_requirement_line(
+            "requests>=2.0; python_version>='3.6'", ""
+        )
+        self.assertEqual(
+            result, 'Requires-Dist: requests>=2.0; python_version >= "3.6"'
+        )
+
+    def test_requirement_with_marker_and_extra(self):
+        result = wheelmaker.get_new_requirement_line(
+            "requests>=2.0; python_version>='3.6'", "extra=='dev'"
+        )
+        self.assertEqual(
+            result,
+            "Requires-Dist: requests>=2.0; (python_version >= \"3.6\") and extra=='dev'",
+        )
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py
index 4390df3..7124ae7 100644
--- a/tools/wheelmaker.py
+++ b/tools/wheelmaker.py
@@ -330,9 +330,7 @@
 Wheel-Version: 1.0
 Generator: bazel-wheelmaker 1.0
 Root-Is-Purelib: {}
-""".format(
-            "true" if self._platform == "any" else "false"
-        )
+""".format("true" if self._platform == "any" else "false")
         for tag in self.disttags():
             wheel_contents += "Tag: %s\n" % tag
         self._whlfile.add_string(self.distinfo_path("WHEEL"), wheel_contents)
@@ -365,6 +363,32 @@
     return files
 
 
+def get_new_requirement_line(reqs_text: str, extra: str) -> str:
+    """Formats a requirement text into a Requires-Dist metadata line."""
+    from packaging.requirements import Requirement
+
+    req = Requirement(reqs_text.strip())
+    req_extra_deps = f"[{','.join(req.extras)}]" if req.extras else ""
+
+    # Handle URL requirements (PEP 508)
+    if req.url:
+        req_spec = f" @ {req.url}"
+    else:
+        req_spec = str(req.specifier)
+
+    base = f"Requires-Dist: {req.name}{req_extra_deps}{req_spec}"
+
+    if req.marker:
+        if extra:
+            return f"{base}; ({req.marker}) and {extra}"
+        else:
+            return f"{base}; {req.marker}"
+    elif extra:
+        return f"{base}; {extra}"
+    else:
+        return base
+
+
 def resolve_argument_stamp(
     argument: str, volatile_status_stamp: Path, stable_status_stamp: Path
 ) -> str:
@@ -430,7 +454,7 @@
     output_group.add_argument(
         "--name_file",
         type=Path,
-        help="A file where the canonical name of the " "wheel will be written",
+        help="A file where the canonical name of the wheel will be written",
     )
 
     output_group.add_argument(
@@ -578,19 +602,6 @@
         # Search for any `Requires-Dist` entries that refer to other files and
         # expand them.
 
-        def get_new_requirement_line(reqs_text, extra):
-            req = Requirement(reqs_text.strip())
-            req_extra_deps = f"[{','.join(req.extras)}]" if req.extras else ""
-            if req.marker:
-                if extra:
-                    return f"Requires-Dist: {req.name}{req_extra_deps}{req.specifier}; ({req.marker}) and {extra}"
-                else:
-                    return f"Requires-Dist: {req.name}{req_extra_deps}{req.specifier}; {req.marker}"
-            else:
-                return f"Requires-Dist: {req.name}{req_extra_deps}{req.specifier}; {extra}".strip(
-                    " ;"
-                )
-
         for meta_line in metadata.splitlines():
             if not meta_line.startswith("Requires-Dist: "):
                 continue