feat(toolchain): add patch_strip attr for python_repository (#2201)

This value has been hardcoded for a long time, so let's add the
`patch_strip`
attribute to the `WORKSPACE` setups now so that we add less tech debt in
the
`bzlmod` world later and need to migrate `bzlmod` users from the hard
coded
value to something that can be configured.

This should be backwards compatible because of the default `int` value
of `1`
for the `patch_strip` attribute.

Summary:
- feat: add `patch_strip` to `python_repository`.
- feat: add the default value of patch_strip to get_release_info.
- refactor: handle patch_strip key in `TOOL_VERSIONS`.

Work towards #2081.

---------

Co-authored-by: Richard Levasseur <richardlev@gmail.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e2c54e3..7e2f9bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -63,6 +63,13 @@
   have it installed.
 * (docs) Automatically generated documentation for {bzl:obj}`python_register_toolchains`
   and related symbols.
+* (toolchains) Added {attr}`python_repository.patch_strip` attribute for
+  allowing values that are other than `1`, which has been hard-coded up until
+  now. If you are relying on the undocumented `patches` support in
+  `TOOL_VERSIONS` for registering patched toolchains please consider setting
+  the `patch_strip` explicitly to `1` if you depend on this value - in the
+  future the value may change to default to `0`.
+
 
 ### Removed
 * (toolchains): Removed accidentally exposed `http_archive` symbol from
diff --git a/examples/bzlmod/MODULE.bazel.lock b/examples/bzlmod/MODULE.bazel.lock
index df0c96d..6964e11 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": "NoDlhLqt5TG5tTcsLeBN6mnecYE+w8R4pvOKOF0ctC4=",
+        "bzlTransitiveDigest": "ofS5ggL4YnyCZejN3Rimf+XXWMSwuWypjGghv3fbAGY=",
         "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": "sh8kec0fTFOgrFmLOXLvQRfq+2g3Uv7rF0gj5xqnzKQ=",
+        "bzlTransitiveDigest": "LJJOkml9ZwY4i0WJnh+pN5fZUQLciL7hPL27L8kNYeI=",
         "usagesDigest": "Y8ihY+R57BAFhalrVLVdJFrpwlbsiKz9JPJ99ljF7HA=",
         "recordedFileInputs": {
           "@@rules_python~//tools/publish/requirements.txt": "031e35d03dde03ae6305fe4b3d1f58ad7bdad857379752deede0f93649991b8a",
diff --git a/python/private/python_repositories.bzl b/python/private/python_repositories.bzl
index 8675399..46b2e29 100644
--- a/python/private/python_repositories.bzl
+++ b/python/private/python_repositories.bzl
@@ -179,7 +179,7 @@
     if patches:
         for patch in patches:
             # Should take the strip as an attr, but this is fine for the moment
-            rctx.patch(patch, strip = 1)
+            rctx.patch(patch, strip = rctx.attr.patch_strip)
 
     # Write distutils.cfg to the Python installation.
     if "windows" in platform:
@@ -450,6 +450,7 @@
         "ignore_root_user_error": rctx.attr.ignore_root_user_error,
         "name": rctx.attr.name,
         "netrc": rctx.attr.netrc,
+        "patch_strip": rctx.attr.patch_strip,
         "patches": rctx.attr.patches,
         "platform": platform,
         "python_version": python_version,
@@ -515,6 +516,21 @@
         "netrc": attr.string(
             doc = ".netrc file to use for authentication; mirrors the eponymous attribute from http_archive",
         ),
+        "patch_strip": attr.int(
+            doc = """
+Same as the --strip argument of Unix patch.
+
+:::{note}
+In the future the default value will be set to `0`, to mimic the well known
+function defaults (e.g. `single_version_override` for `MODULE.bazel` files.
+:::
+
+:::{versionadded} 0.36.0
+:::
+""",
+            default = 1,
+            mandatory = False,
+        ),
         "patches": attr.label_list(
             doc = "A list of patch files to apply to the unpacked interpreter",
             mandatory = False,
@@ -627,7 +643,7 @@
             continue
 
         loaded_platforms.append(platform)
-        (release_filename, urls, strip_prefix, patches) = get_release_info(platform, python_version, base_url, tool_versions)
+        (release_filename, urls, strip_prefix, patches, patch_strip) = get_release_info(platform, python_version, base_url, tool_versions)
 
         # allow passing in a tool version
         coverage_tool = None
@@ -653,6 +669,7 @@
             ),
             sha256 = sha256,
             patches = patches,
+            patch_strip = patch_strip,
             platform = platform,
             python_version = python_version,
             release_filename = release_filename,
diff --git a/python/versions.bzl b/python/versions.bzl
index 2cf9b39..79e388d 100644
--- a/python/versions.bzl
+++ b/python/versions.bzl
@@ -41,7 +41,7 @@
 #       "strip_prefix": "python",
 #   },
 #
-# It is possible to provide lists in "url".
+# It is possible to provide lists in "url". It is also possible to provide patches or patch_strip.
 #
 # buildifier: disable=unsorted-dict-items
 TOOL_VERSIONS = {
@@ -636,7 +636,7 @@
         tool_versions: A dict listing the interpreter versions, their SHAs and URL
 
     Returns:
-        A tuple of (filename, url, and archive strip prefix)
+        A tuple of (filename, url, archive strip prefix, patches, patch_strip)
     """
 
     url = tool_versions[python_version]["url"]
@@ -673,8 +673,14 @@
             patches = patches[platform]
         else:
             patches = []
+    patch_strip = tool_versions[python_version].get("patch_strip", None)
+    if type(patch_strip) == type({}):
+        if platform in patch_strip.keys():
+            patch_strip = patch_strip[platform]
+        else:
+            patch_strip = None
 
-    return (release_filename, rendered_urls, strip_prefix, patches)
+    return (release_filename, rendered_urls, strip_prefix, patches, patch_strip)
 
 def print_toolchains_checksums(name):
     native.genrule(