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