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