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