blob: 9e3119f744d4fb1f7d38a72926732889eac4d799 [file] [log] [blame]
# 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: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 rctx.execute.
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")
result = rctx.execute(
[
python_interpreter,
"-m",
"python.private.repack_whl",
"--record-patch",
record_patch,
whl_input,
whl_patched,
],
environment = {
"PYTHONPATH": str(rctx.path(_rules_python_root).dirname),
},
**kwargs
)
if result.return_code:
fail(
"repackaging .whl {whl} failed: with exit code '{return_code}':\n{stdout}\n\nstderr:\n{stderr}".format(
whl = whl_input.basename,
stdout = result.stdout,
stderr = result.stderr,
return_code = result.return_code,
),
)
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)