aboutsummaryrefslogtreecommitdiff
path: root/build/sandbox/build_android_sandboxed.py
blob: 851807499ef42c4f0af2f94cc42689960ed8a736 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# 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_mounts=[],
          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_mounts: A list of string paths 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

  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',
      type=str,
      default=[],
      action='append',
      help='Path to the a path to be mounted as readonly inside the secure '
      'build sandbox. Can be specified multiple times')
  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_flags = cfg.get_build_flags(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_mounts=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 + build_flags)


if __name__ == '__main__':
  main()