fix(bzlmod): correctly wire the extra_pip_args (#2258)

Before this PR we were just dropping the `extra_pip_args` passed to
`pip.parse` and were just using the args passed through the requirements
file. Thanks to @swarren12 for pointing this out.

This PR also passes `extra_pip_args` to `sdist` `whl_library` instances
so that users can build the `sdists` correctly when using
`experimental_index_url` feature.

Summary:
- pass `extra_pip_args` when building sdists in experimental mode
- join `extra_pip_args` from the file and the pip.parse attr
- test: add a test to ensure that the extra args are joined

Fixes #2239
Closes #2254
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a16a5e3..7235e89 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,11 @@
 * Nothing yet
 
 ### Fixed
+* (bzlmod) correctly wire the {attr}`pip.parse.extra_pip_args` all the
+  way to {obj}`whl_library`. What is more we will pass the `extra_pip_args` to
+  {obj}`whl_library` for `sdist` distributions when using
+  {attr}`pip.parse.experimental_index_url`. See
+  [#2239](https://github.com/bazelbuild/rules_python/issues/2239).
 * (whl_filegroup): Provide per default also the `RECORD` file
 
 ### Added
diff --git a/examples/bzlmod/MODULE.bazel.lock b/examples/bzlmod/MODULE.bazel.lock
index cb8fbe2..604aef1 100644
--- a/examples/bzlmod/MODULE.bazel.lock
+++ b/examples/bzlmod/MODULE.bazel.lock
@@ -1231,7 +1231,7 @@
     },
     "@@rules_python~//python/extensions:pip.bzl%pip": {
       "general": {
-        "bzlTransitiveDigest": "ED3oUrLQz/MTptq8JOZ03sjD7HZ3naUeFS3XFpxz4tg=",
+        "bzlTransitiveDigest": "W8FWi7aL0uqh7djg6csTMzkL1RB6WGRgfO/MOcbqYZI=",
         "usagesDigest": "MChlcSw99EuW3K7OOoMcXQIdcJnEh6YmfyjJm+9mxIg=",
         "recordedFileInputs": {
           "@@other_module~//requirements_lock_3_11.txt": "a7d0061366569043d5efcf80e34a32c732679367cb3c831c4cdc606adc36d314",
@@ -6140,7 +6140,7 @@
     },
     "@@rules_python~//python/private/pypi:pip.bzl%pip_internal": {
       "general": {
-        "bzlTransitiveDigest": "vEOIMpxlh8qbHkABunGFRr+IDbabjCM/hUF0V3GGTus=",
+        "bzlTransitiveDigest": "9aEp4XU4yVL6sJqODxio5iSCoqf37Ro3o+Mt1izDnq8=",
         "usagesDigest": "Y8ihY+R57BAFhalrVLVdJFrpwlbsiKz9JPJ99ljF7HA=",
         "recordedFileInputs": {
           "@@rules_python~//tools/publish/requirements.txt": "031e35d03dde03ae6305fe4b3d1f58ad7bdad857379752deede0f93649991b8a",
diff --git a/python/private/pypi/extension.bzl b/python/private/pypi/extension.bzl
index 77a4778..712d2fe 100644
--- a/python/private/pypi/extension.bzl
+++ b/python/private/pypi/extension.bzl
@@ -182,6 +182,7 @@
             python_version = major_minor,
             logger = logger,
         ),
+        extra_pip_args = pip_attr.extra_pip_args,
         get_index_urls = get_index_urls,
         # NOTE @aignas 2024-08-02: , we will execute any interpreter that we find either
         # in the PATH or if specified as a label. We will configure the env
@@ -275,8 +276,13 @@
                     if pip_attr.auth_patterns:
                         whl_library_args["auth_patterns"] = pip_attr.auth_patterns
 
-                    # pip is not used to download wheels and the python `whl_library` helpers are only extracting things
-                    whl_library_args.pop("extra_pip_args", None)
+                    if distribution.filename.endswith(".whl"):
+                        # pip is not used to download wheels and the python `whl_library` helpers are only extracting things
+                        whl_library_args.pop("extra_pip_args", None)
+                    else:
+                        # For sdists, they will be built by `pip`, so we still
+                        # need to pass the extra args there.
+                        pass
 
                     # This is no-op because pip is not used to download the wheel.
                     whl_library_args.pop("download_only", None)
diff --git a/python/private/pypi/parse_requirements.bzl b/python/private/pypi/parse_requirements.bzl
index eee97d7..c72f5d4 100644
--- a/python/private/pypi/parse_requirements.bzl
+++ b/python/private/pypi/parse_requirements.bzl
@@ -48,7 +48,7 @@
             different package versions (or different packages) for different
             os, arch combinations.
         extra_pip_args (string list): Extra pip arguments to perform extra validations and to
-            be joined with args fined in files.
+            be joined with args found in files.
         get_index_urls: Callable[[ctx, list[str]], dict], a callable to get all
             of the distribution URLs from a PyPI index. Accepts ctx and
             distribution names to query.
diff --git a/tests/pypi/parse_requirements/parse_requirements_tests.bzl b/tests/pypi/parse_requirements/parse_requirements_tests.bzl
index 25d2961..c719ad6 100644
--- a/tests/pypi/parse_requirements/parse_requirements_tests.bzl
+++ b/tests/pypi/parse_requirements/parse_requirements_tests.bzl
@@ -22,6 +22,11 @@
         "requirements_direct": """\
 foo[extra] @ https://some-url
 """,
+        "requirements_extra_args": """\
+--index-url=example.org
+
+foo[extra]==0.0.1 --hash=sha256:deadbeef
+""",
         "requirements_linux": """\
 foo==0.0.3 --hash=sha256:deadbaaf
 """,
@@ -93,6 +98,43 @@
 
 _tests.append(_test_simple)
 
+def _test_extra_pip_args(env):
+    got = parse_requirements(
+        ctx = _mock_ctx(),
+        requirements_by_platform = {
+            "requirements_extra_args": ["linux_x86_64"],
+        },
+        extra_pip_args = ["--trusted-host=example.org"],
+    )
+    env.expect.that_dict(got).contains_exactly({
+        "foo": [
+            struct(
+                distribution = "foo",
+                extra_pip_args = ["--index-url=example.org", "--trusted-host=example.org"],
+                requirement_line = "foo[extra]==0.0.1 --hash=sha256:deadbeef",
+                srcs = struct(
+                    requirement = "foo[extra]==0.0.1",
+                    shas = ["deadbeef"],
+                    version = "0.0.1",
+                ),
+                target_platforms = [
+                    "linux_x86_64",
+                ],
+                whls = [],
+                sdist = None,
+                is_exposed = True,
+            ),
+        ],
+    })
+    env.expect.that_str(
+        select_requirement(
+            got["foo"],
+            platform = "linux_x86_64",
+        ).srcs.version,
+    ).equals("0.0.1")
+
+_tests.append(_test_extra_pip_args)
+
 def _test_dupe_requirements(env):
     got = parse_requirements(
         ctx = _mock_ctx(),