blob: 24b8a0b565a17a2651aa60543f09c83aa708097c [file] [log] [blame]
Ignas Anikeviciusc0e18ed2023-10-20 09:35:37 +09001# 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:parse_whl_name.bzl", "parse_whl_name")
31
32_rules_python_root = Label("//:BUILD.bazel")
33
34def patch_whl(rctx, *, python_interpreter, whl_path, patches, **kwargs):
35 """Patch a whl file and repack it to ensure that the RECORD metadata stays correct.
36
37 Args:
38 rctx: repository_ctx
39 python_interpreter: the python interpreter to use.
40 whl_path: The whl file name to be patched.
41 patches: a label-keyed-int dict that has the patch files as keys and
42 the patch_strip as the value.
43 **kwargs: extras passed to rctx.execute.
44
45 Returns:
46 value of the repackaging action.
47 """
48
49 # extract files into the current directory for patching as rctx.patch
50 # does not support patching in another directory.
51 whl_input = rctx.path(whl_path)
52
53 # symlink to a zip file to use bazel's extract so that we can use bazel's
54 # repository_ctx patch implementation. The whl file may be in a different
55 # external repository.
56 whl_file_zip = whl_input.basename + ".zip"
57 rctx.symlink(whl_input, whl_file_zip)
58 rctx.extract(whl_file_zip)
59 if not rctx.delete(whl_file_zip):
60 fail("Failed to remove the symlink after extracting")
61
62 for patch_file, patch_strip in patches.items():
63 rctx.patch(patch_file, strip = patch_strip)
64
65 # Generate an output filename, which we will be returning
66 parsed_whl = parse_whl_name(whl_input.basename)
67 whl_patched = "{}.whl".format("-".join([
68 parsed_whl.distribution,
69 parsed_whl.version,
70 (parsed_whl.build_tag or "") + "patched",
71 parsed_whl.python_tag,
72 parsed_whl.abi_tag,
73 parsed_whl.platform_tag,
74 ]))
75
76 result = rctx.execute(
77 [
78 python_interpreter,
79 "-m",
80 "python.private.repack_whl",
81 whl_input,
82 whl_patched,
83 ],
84 environment = {
85 "PYTHONPATH": str(rctx.path(_rules_python_root).dirname),
86 },
87 **kwargs
88 )
89
90 if result.return_code:
91 fail(
92 "repackaging .whl {whl} failed: with exit code '{return_code}':\n{stdout}\n\nstderr:\n{stderr}".format(
93 whl = whl_input.basename,
94 stdout = result.stdout,
95 stderr = result.stderr,
96 return_code = result.return_code,
97 ),
98 )
99
100 return rctx.path(whl_patched)