| # Copyright 2023 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """A small utility to patch a file in the repository context and repackage it using a Python interpreter | |
| Note, because we are patching a wheel file and we need a new RECORD file, this | |
| function will print a diff of the RECORD and will ask the user to include a | |
| RECORD patch in their patches that they maintain. This is to ensure that we can | |
| satisfy the following usecases: | |
| * Patch an invalid RECORD file. | |
| * Patch files within a wheel. | |
| If we were silently regenerating the RECORD file, we may be vulnerable to supply chain | |
| attacks (it is a very small chance) and keeping the RECORD patches next to the | |
| other patches ensures that the users have overview on exactly what has changed | |
| within the wheel. | |
| """ | |
| load("//python/private:repo_utils.bzl", "repo_utils") | |
| load(":parse_whl_name.bzl", "parse_whl_name") | |
| _rules_python_root = Label("//:BUILD.bazel") | |
| def patch_whl(rctx, *, python_interpreter, whl_path, patches, **kwargs): | |
| """Patch a whl file and repack it to ensure that the RECORD metadata stays correct. | |
| Args: | |
| rctx: repository_ctx | |
| python_interpreter: the python interpreter to use. | |
| whl_path: The whl file name to be patched. | |
| patches: a label-keyed-int dict that has the patch files as keys and | |
| the patch_strip as the value. | |
| **kwargs: extras passed to repo_utils.execute_checked. | |
| Returns: | |
| value of the repackaging action. | |
| """ | |
| # extract files into the current directory for patching as rctx.patch | |
| # does not support patching in another directory. | |
| whl_input = rctx.path(whl_path) | |
| # symlink to a zip file to use bazel's extract so that we can use bazel's | |
| # repository_ctx patch implementation. The whl file may be in a different | |
| # external repository. | |
| whl_file_zip = whl_input.basename + ".zip" | |
| rctx.symlink(whl_input, whl_file_zip) | |
| rctx.extract(whl_file_zip) | |
| if not rctx.delete(whl_file_zip): | |
| fail("Failed to remove the symlink after extracting") | |
| for patch_file, patch_strip in patches.items(): | |
| rctx.patch(patch_file, strip = patch_strip) | |
| # Generate an output filename, which we will be returning | |
| parsed_whl = parse_whl_name(whl_input.basename) | |
| whl_patched = "{}.whl".format("-".join([ | |
| parsed_whl.distribution, | |
| parsed_whl.version, | |
| (parsed_whl.build_tag or "") + "patched", | |
| parsed_whl.python_tag, | |
| parsed_whl.abi_tag, | |
| parsed_whl.platform_tag, | |
| ])) | |
| record_patch = rctx.path("RECORD.patch") | |
| repo_utils.execute_checked( | |
| rctx, | |
| arguments = [ | |
| python_interpreter, | |
| "-m", | |
| "python.private.pypi.repack_whl", | |
| "--record-patch", | |
| record_patch, | |
| whl_input, | |
| whl_patched, | |
| ], | |
| environment = { | |
| "PYTHONPATH": str(rctx.path(_rules_python_root).dirname), | |
| }, | |
| **kwargs | |
| ) | |
| if record_patch.exists: | |
| record_patch_contents = rctx.read(record_patch) | |
| warning_msg = """WARNING: the resultant RECORD file of the patch wheel is different | |
| If you are patching on Windows, you may see this warning because of | |
| a known issue (bazelbuild/rules_python#1639) with file endings. | |
| If you would like to silence the warning, you can apply the patch that is stored in | |
| {record_patch}. The contents of the file are below: | |
| {record_patch_contents}""".format( | |
| record_patch = record_patch, | |
| record_patch_contents = record_patch_contents, | |
| ) | |
| print(warning_msg) # buildifier: disable=print | |
| return rctx.path(whl_patched) |