• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2024 The Android Open Source Project
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
15import argparse
16import os
17import pathlib
18import sys
19from datetime import date
20from typing import Tuple, Optional
21
22_BAZEL_REL_PATH = "prebuilts/kernel-build-tools/bazel/linux-x86_64/bazel"
23
24
25def _partition(lst: list[str], index: Optional[int]) \
26        -> Tuple[list[str], Optional[str], list[str]]:
27    """Returns the triple split by index.
28
29    That is, return a tuple:
30    (everything before index, the element at index, everything after index)
31
32    If index is None, return (the list, None, empty list)
33    """
34    if index is None:
35        return lst[:], None, []
36    return lst[:index], lst[index], lst[index + 1:]
37
38
39class BazelWrapper(object):
40    def __init__(self, workspace_dir: pathlib.Path, bazel_args: list[str]):
41        """Splits arguments to the bazel binary based on the functionality.
42
43        bazel [startup_options] command         [command_args] --               [target_patterns]
44                                 ^- command_idx                ^- dash_dash_idx
45
46        See https://bazel.build/reference/command-line-reference
47
48        Args:
49            workspace_dir: root of workspace.
50            bazel_args: The list of arguments the user provides through command line
51            env: existing environment
52        """
53
54        self.workspace_dir = workspace_dir
55
56        self.bazel_path = self.workspace_dir / _BAZEL_REL_PATH
57
58        command_idx = None
59        for idx, arg in enumerate(bazel_args):
60            if not arg.startswith("-"):
61                command_idx = idx
62                break
63
64        self.startup_options, self.command, remaining_args = _partition(bazel_args,
65                                                                        command_idx)
66
67        # Split command_args into `command_args -- target_patterns`
68        dash_dash_idx = None
69        try:
70            dash_dash_idx = remaining_args.index("--")
71        except ValueError:
72            # If -- is not found, put everything in command_args. These arguments
73            # are not provided to the Bazel executable target.
74            pass
75
76        self.command_args, self.dash_dash, self.target_patterns = _partition(remaining_args,
77                                                                             dash_dash_idx)
78
79        self._parse_startup_options()
80        self._parse_command_args()
81        self._add_extra_startup_options()
82        self._add_build_number_command_args()
83
84    def add_startup_option_to_parser(self, parser):
85        parser.add_argument(
86            "-h", "--help", action="store_true",
87            help="show this help message and exit"
88        )
89
90    def _parse_startup_options(self):
91        """Parses the given list of startup_options.
92
93        After calling this function, the following attributes are set:
94        - absolute_user_root: A path holding bazel build output location
95        - transformed_startup_options: The transformed list of startup_options to replace
96          existing startup_options to be fed to the Bazel binary
97        """
98
99        parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
100        self.add_startup_option_to_parser(parser)
101
102        self.known_startup_options, self.user_startup_options = \
103            parser.parse_known_args(self.startup_options)
104
105        self.absolute_out_dir = self.workspace_dir / "out"
106        self.absolute_user_root = \
107            self.absolute_out_dir / "bazel/output_user_root"
108
109        if self.known_startup_options.help:
110            self.transformed_startup_options = [
111                "--help"
112            ]
113
114        if not self.known_startup_options.help:
115            javatmp = self.absolute_out_dir / "bazel/javatmp"
116            self.transformed_startup_options = [
117                f"--host_jvm_args=-Djava.io.tmpdir={javatmp}",
118            ]
119
120        # See _add_extra_startup_options for extra startup options
121
122    def _parse_command_args(self):
123        """Parses the given list of command_args.
124
125        After calling this function, the following attributes are set:
126        - known_args: A namespace holding options known by this Bazel wrapper script
127        - transformed_command_args: The transformed list of command_args to replace
128          existing command_args to be fed to the Bazel binary
129        - env: A dictionary containing the new environment variables for the subprocess.
130        """
131
132        parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
133
134        # TODO: Delete these args once build bots no longer specify them
135        parser.add_argument(
136            "--make_jobs", metavar="JOBS", type=int, default=None,
137            help="unused")
138        parser.add_argument(
139            "--make_keep_going", action="store_true", default=False,
140            help="unused")
141        parser.add_argument(
142            "--repo_manifest", metavar="<repo_root>:<manifest.xml>",
143            help="unused")
144
145        # Skip startup options (before command) and target_patterns (after --)
146        _, self.transformed_command_args = parser.parse_known_args(
147            self.command_args)
148
149    def _add_build_number_command_args(self):
150        """Adds options for BUILD_NUMBER."""
151        build_number = os.environ.get("BUILD_NUMBER")
152        if build_number is None:
153            # Changing the commandline causes rebuild. In order to *not* cause
154            # superfluous rebuilds, append a low-precision timestamp.
155            build_number = f"eng.{os.environ.get('USER')}.{date.today()}"
156        self.transformed_command_args += ["--action_env", f"BUILD_NUMBER={build_number}"]
157
158    def _add_extra_startup_options(self):
159        """Adds extra startup options after command args are parsed."""
160
161        self.transformed_startup_options += self.user_startup_options
162
163        if not self.known_startup_options.help:
164            self.transformed_startup_options.append(
165                f"--output_user_root={self.absolute_user_root}")
166
167    def _build_final_args(self) -> list[str]:
168        """Builds the final arguments for the subprocess."""
169        # final_args:
170        # bazel [startup_options] [additional_startup_options] command [transformed_command_args] -- [target_patterns]
171
172        final_args = [self.bazel_path] + self.transformed_startup_options
173
174        if self.command is not None:
175            final_args.append(self.command)
176        final_args += self.transformed_command_args
177        if self.dash_dash is not None:
178            final_args.append(self.dash_dash)
179        final_args += self.target_patterns
180
181        return final_args
182
183    def run(self) -> int:
184        """Runs the wrapper.
185
186        Returns:
187            doesn't return"""
188        final_args = self._build_final_args()
189
190        os.execve(path=self.bazel_path, argv=final_args, env=os.environ)
191
192
193def _bazel_wrapper_main():
194    # <workspace_dir>/bootable/libbootloader/gbl/bazel.py
195    workspace_dir = (
196        pathlib.Path(__file__).resolve().parent.parent.parent.parent)
197    return BazelWrapper(workspace_dir=workspace_dir,
198                        bazel_args=sys.argv[1:]).run()
199
200
201if __name__ == "__main__":
202    sys.exit(_bazel_wrapper_main())
203