# Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Builds an Android target in a secure sandbox.""" import argparse import os from . import config from . import nsjail from . import rbe _DEFAULT_COMMAND_WRAPPER = \ '/src/tools/treble/build/sandbox/build_android_target.sh' def build(build_target, variant, nsjail_bin, chroot, dist_dir, build_id, max_cpus, build_goals, config_file=None, command_wrapper=_DEFAULT_COMMAND_WRAPPER, use_rbe=False, readonly_bind_mount=None, env=[]): """Builds an Android target in a secure sandbox. Args: build_target: A string with the name of the build target. variant: A string with the build variant. nsjail_bin: A string with the path to the nsjail binary. chroot: A string with the path to the chroot of the NsJail sandbox. dist_dir: A string with the path to the Android dist directory. build_id: A string with the Android build identifier. max_cpus: An integer with maximum number of CPUs. build_goals: A list of strings with the goals and options to provide to the build command. config_file: A string path to an overlay configuration file. command_wrapper: A string path to the command wrapper. use_rbe: If true, will attempt to use RBE for the build. readonly_bind_mount: A string path to a path to be mounted as read-only. env: An array of environment variables to define in the NsJail sandbox in the `var=val` syntax. Returns: A list of commands that were executed. Each command is a list of strings. """ if config_file: cfg = config.Config(config_file) android_target = cfg.get_build_config_android_target(build_target) if cfg.has_tag(build_target, 'skip'): print('Warning: skipping build_target "{}" due to tag being set'.format(build_target)) return [] else: android_target = build_target # All builds are required to run with the root of the # Android source tree as the current directory. source_dir = os.getcwd() command = [ command_wrapper, '%s-%s' % (android_target, variant), '/src', 'make', '-j', ] + build_goals readonly_bind_mounts = [] if readonly_bind_mount: readonly_bind_mounts = [readonly_bind_mount] extra_nsjail_args = [] cleanup = lambda: None nsjail_wrapper = [] if use_rbe: cleanup = rbe.setup(env) env = rbe.prepare_env(env) extra_nsjail_args.extend(rbe.get_extra_nsjail_args()) readonly_bind_mounts.extend(rbe.get_readonlybind_mounts()) nsjail_wrapper = rbe.get_nsjail_bin_wrapper() ret = nsjail.run( nsjail_bin=nsjail_bin, chroot=chroot, overlay_config=config_file, source_dir=source_dir, command=command, build_target=build_target, dist_dir=dist_dir, build_id=build_id, max_cpus=max_cpus, extra_nsjail_args=extra_nsjail_args, readonly_bind_mounts=readonly_bind_mounts, env=env, nsjail_wrapper=nsjail_wrapper) cleanup() return ret def arg_parser(): """Returns an ArgumentParser for sanboxed android builds.""" # Use the top level module docstring for the help description parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument( '--build_target', help='The build target.') parser.add_argument( '--variant', default='userdebug', help='The Android build variant.') parser.add_argument( '--nsjail_bin', required=True, help='Path to NsJail binary.') parser.add_argument( '--chroot', required=True, help='Path to the chroot to be used for building the Android ' 'platform. This will be mounted as the root filesystem in the ' 'NsJail sandbox.') parser.add_argument( '--config_file', required=True, help='Path to the overlay configuration file.') parser.add_argument( '--command_wrapper', default=_DEFAULT_COMMAND_WRAPPER, help='Path to the command wrapper. ' 'Defaults to \'%s\'.' % _DEFAULT_COMMAND_WRAPPER) parser.add_argument( '--readonly_bind_mount', help='Path to the a path to be mounted as readonly inside the secure ' 'build sandbox.') parser.add_argument( '--env', '-e', type=str, default=[], action='append', help='Specify an environment variable to the NSJail sandbox. Can be specified ' 'muliple times. Syntax: var_name=value') parser.add_argument( '--dist_dir', help='Path to the Android dist directory. This is where ' 'Android platform release artifacts will be written.') parser.add_argument( '--build_id', help='Build identifier what will label the Android platform ' 'release artifacts.') parser.add_argument( '--max_cpus', type=int, help='Limit of concurrent CPU cores that the NsJail sanbox ' 'can use.') parser.add_argument( '--context', action='append', default=[], help='One or more contexts used to select build goals from the ' 'configuration.') parser.add_argument( '--use_rbe', action='store_true', help='Executes the build on RBE') return parser def parse_args(parser): """Parses command line arguments. Returns: A dict of all the arguments parsed. """ # Convert the Namespace object to a dict return vars(parser.parse_args()) def main(): args = parse_args(arg_parser()) # The --build_target argument could not be required # using the standard 'required' argparse option because # the argparser is reused by merge_android_sandboxed.py which # does not require --build_target. if args['build_target'] is None: raise ValueError('--build_target is required.') cfg = config.Config(args['config_file']) build_goals = cfg.get_build_goals(args['build_target'], set(args['context'])) build( build_target=args['build_target'], variant=args['variant'], nsjail_bin=args['nsjail_bin'], chroot=args['chroot'], config_file=args['config_file'], command_wrapper=args['command_wrapper'], readonly_bind_mount=args['readonly_bind_mount'], env=args['env'], dist_dir=args['dist_dir'], build_id=args['build_id'], max_cpus=args['max_cpus'], use_rbe=args['use_rbe'], build_goals=build_goals) if __name__ == '__main__': main()