xref: /aosp_15_r20/external/bazelbuild-rules_python/python/private/pypi/patch_whl.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1# Copyright 2023 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""A small utility to patch a file in the repository context and repackage it using a Python interpreter
16
17Note, because we are patching a wheel file and we need a new RECORD file, this
18function will print a diff of the RECORD and will ask the user to include a
19RECORD patch in their patches that they maintain. This is to ensure that we can
20satisfy the following usecases:
21* Patch an invalid RECORD file.
22* Patch files within a wheel.
23
24If we were silently regenerating the RECORD file, we may be vulnerable to supply chain
25attacks (it is a very small chance) and keeping the RECORD patches next to the
26other patches ensures that the users have overview on exactly what has changed
27within the wheel.
28"""
29
30load("//python/private:repo_utils.bzl", "repo_utils")
31load(":parse_whl_name.bzl", "parse_whl_name")
32
33_rules_python_root = Label("//:BUILD.bazel")
34
35def patch_whl(rctx, *, python_interpreter, whl_path, patches, **kwargs):
36    """Patch a whl file and repack it to ensure that the RECORD metadata stays correct.
37
38    Args:
39        rctx: repository_ctx
40        python_interpreter: the python interpreter to use.
41        whl_path: The whl file name to be patched.
42        patches: a label-keyed-int dict that has the patch files as keys and
43            the patch_strip as the value.
44        **kwargs: extras passed to repo_utils.execute_checked.
45
46    Returns:
47        value of the repackaging action.
48    """
49
50    # extract files into the current directory for patching as rctx.patch
51    # does not support patching in another directory.
52    whl_input = rctx.path(whl_path)
53
54    # symlink to a zip file to use bazel's extract so that we can use bazel's
55    # repository_ctx patch implementation. The whl file may be in a different
56    # external repository.
57    whl_file_zip = whl_input.basename + ".zip"
58    rctx.symlink(whl_input, whl_file_zip)
59    rctx.extract(whl_file_zip)
60    if not rctx.delete(whl_file_zip):
61        fail("Failed to remove the symlink after extracting")
62
63    for patch_file, patch_strip in patches.items():
64        rctx.patch(patch_file, strip = patch_strip)
65
66    # Generate an output filename, which we will be returning
67    parsed_whl = parse_whl_name(whl_input.basename)
68    whl_patched = "{}.whl".format("-".join([
69        parsed_whl.distribution,
70        parsed_whl.version,
71        (parsed_whl.build_tag or "") + "patched",
72        parsed_whl.python_tag,
73        parsed_whl.abi_tag,
74        parsed_whl.platform_tag,
75    ]))
76
77    record_patch = rctx.path("RECORD.patch")
78
79    repo_utils.execute_checked(
80        rctx,
81        arguments = [
82            python_interpreter,
83            "-m",
84            "python.private.pypi.repack_whl",
85            "--record-patch",
86            record_patch,
87            whl_input,
88            whl_patched,
89        ],
90        environment = {
91            "PYTHONPATH": str(rctx.path(_rules_python_root).dirname),
92        },
93        **kwargs
94    )
95
96    if record_patch.exists:
97        record_patch_contents = rctx.read(record_patch)
98        warning_msg = """WARNING: the resultant RECORD file of the patch wheel is different
99
100    If you are patching on Windows, you may see this warning because of
101    a known issue (bazelbuild/rules_python#1639) with file endings.
102
103    If you would like to silence the warning, you can apply the patch that is stored in
104      {record_patch}. The contents of the file are below:
105{record_patch_contents}""".format(
106            record_patch = record_patch,
107            record_patch_contents = record_patch_contents,
108        )
109        print(warning_msg)  # buildifier: disable=print
110
111    return rctx.path(whl_patched)
112