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