1# Copyright 2020 Google LLC 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# https://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"""Builds an Android target in a secure sandbox.""" 15 16import argparse 17import os 18from . import config 19from . import nsjail 20from . import rbe 21 22_DEFAULT_COMMAND_WRAPPER = \ 23 '/src/tools/treble/build/sandbox/build_android_target.sh' 24 25 26def build(build_target, 27 variant, 28 nsjail_bin, 29 chroot, 30 dist_dir, 31 build_id, 32 max_cpus, 33 build_goals, 34 config_file=None, 35 command_wrapper=_DEFAULT_COMMAND_WRAPPER, 36 use_rbe=False, 37 readonly_bind_mounts=[], 38 env=[]): 39 """Builds an Android target in a secure sandbox. 40 41 Args: 42 build_target: A string with the name of the build target. 43 variant: A string with the build variant. 44 nsjail_bin: A string with the path to the nsjail binary. 45 chroot: A string with the path to the chroot of the NsJail sandbox. 46 dist_dir: A string with the path to the Android dist directory. 47 build_id: A string with the Android build identifier. 48 max_cpus: An integer with maximum number of CPUs. 49 build_goals: A list of strings with the goals and options to provide to the 50 build command. 51 config_file: A string path to an overlay configuration file. 52 command_wrapper: A string path to the command wrapper. 53 use_rbe: If true, will attempt to use RBE for the build. 54 readonly_bind_mounts: A list of string paths to be mounted as read-only. 55 env: An array of environment variables to define in the NsJail sandbox in 56 the `var=val` syntax. 57 58 Returns: 59 A list of commands that were executed. Each command is a list of strings. 60 """ 61 if config_file: 62 cfg = config.Config(config_file) 63 android_target = cfg.get_build_config_android_target(build_target) 64 if cfg.has_tag(build_target, 'skip'): 65 print('Warning: skipping build_target "{}" due to tag being set'.format( 66 build_target)) 67 return [] 68 else: 69 android_target = build_target 70 71 # All builds are required to run with the root of the 72 # Android source tree as the current directory. 73 source_dir = os.getcwd() 74 command = [ 75 command_wrapper, 76 '%s-%s' % (android_target, variant), 77 '/src', 78 'make', 79 '-j', 80 ] + build_goals 81 82 extra_nsjail_args = [] 83 cleanup = lambda: None 84 nsjail_wrapper = [] 85 if use_rbe: 86 cleanup = rbe.setup(env) 87 env = rbe.prepare_env(env) 88 extra_nsjail_args.extend(rbe.get_extra_nsjail_args()) 89 readonly_bind_mounts.extend(rbe.get_readonlybind_mounts()) 90 nsjail_wrapper = rbe.get_nsjail_bin_wrapper() 91 92 ret = nsjail.run( 93 nsjail_bin=nsjail_bin, 94 chroot=chroot, 95 overlay_config=config_file, 96 source_dir=source_dir, 97 command=command, 98 build_target=build_target, 99 dist_dir=dist_dir, 100 build_id=build_id, 101 max_cpus=max_cpus, 102 extra_nsjail_args=extra_nsjail_args, 103 readonly_bind_mounts=readonly_bind_mounts, 104 env=env, 105 nsjail_wrapper=nsjail_wrapper) 106 107 cleanup() 108 109 return ret 110 111 112def arg_parser(): 113 """Returns an ArgumentParser for sanboxed android builds.""" 114 # Use the top level module docstring for the help description 115 parser = argparse.ArgumentParser( 116 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) 117 parser.add_argument('--build_target', help='The build target.') 118 parser.add_argument( 119 '--variant', default='userdebug', help='The Android build variant.') 120 parser.add_argument( 121 '--nsjail_bin', required=True, help='Path to NsJail binary.') 122 parser.add_argument( 123 '--chroot', 124 required=True, 125 help='Path to the chroot to be used for building the Android ' 126 'platform. This will be mounted as the root filesystem in the ' 127 'NsJail sandbox.') 128 parser.add_argument( 129 '--config_file', 130 required=True, 131 help='Path to the overlay configuration file.') 132 parser.add_argument( 133 '--command_wrapper', 134 default=_DEFAULT_COMMAND_WRAPPER, 135 help='Path to the command wrapper. ' 136 'Defaults to \'%s\'.' % _DEFAULT_COMMAND_WRAPPER) 137 parser.add_argument( 138 '--readonly_bind_mount', 139 type=str, 140 default=[], 141 action='append', 142 help='Path to the a path to be mounted as readonly inside the secure ' 143 'build sandbox. Can be specified multiple times') 144 parser.add_argument( 145 '--env', 146 '-e', 147 type=str, 148 default=[], 149 action='append', 150 help='Specify an environment variable to the NSJail sandbox. Can be specified ' 151 'muliple times. Syntax: var_name=value') 152 parser.add_argument( 153 '--dist_dir', 154 help='Path to the Android dist directory. This is where ' 155 'Android platform release artifacts will be written.') 156 parser.add_argument( 157 '--build_id', 158 help='Build identifier what will label the Android platform ' 159 'release artifacts.') 160 parser.add_argument( 161 '--max_cpus', 162 type=int, 163 help='Limit of concurrent CPU cores that the NsJail sanbox ' 164 'can use.') 165 parser.add_argument( 166 '--context', 167 action='append', 168 default=[], 169 help='One or more contexts used to select build goals from the ' 170 'configuration.') 171 parser.add_argument( 172 '--use_rbe', action='store_true', help='Executes the build on RBE') 173 return parser 174 175 176def parse_args(parser): 177 """Parses command line arguments. 178 179 Returns: 180 A dict of all the arguments parsed. 181 """ 182 # Convert the Namespace object to a dict 183 return vars(parser.parse_args()) 184 185 186def main(): 187 args = parse_args(arg_parser()) 188 189 # The --build_target argument could not be required 190 # using the standard 'required' argparse option because 191 # the argparser is reused by merge_android_sandboxed.py which 192 # does not require --build_target. 193 if args['build_target'] is None: 194 raise ValueError('--build_target is required.') 195 196 cfg = config.Config(args['config_file']) 197 build_goals = cfg.get_build_goals(args['build_target'], set(args['context'])) 198 build_flags = cfg.get_build_flags(args['build_target'], set(args['context'])) 199 200 build( 201 build_target=args['build_target'], 202 variant=args['variant'], 203 nsjail_bin=args['nsjail_bin'], 204 chroot=args['chroot'], 205 config_file=args['config_file'], 206 command_wrapper=args['command_wrapper'], 207 readonly_bind_mounts=args['readonly_bind_mount'], 208 env=args['env'], 209 dist_dir=args['dist_dir'], 210 build_id=args['build_id'], 211 max_cpus=args['max_cpus'], 212 use_rbe=args['use_rbe'], 213 build_goals=build_goals + build_flags) 214 215 216if __name__ == '__main__': 217 main() 218