• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright (C) 2019 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from __future__ import print_function
17import argparse
18import distutils
19import errno
20import grp
21import os
22import readline
23import sys
24import shutil
25import subprocess
26from pipes import quote
27from subprocess import check_call
28
29try:
30  from shutil import which as find_executable
31except AttributeError:
32  from distutils.spawn import find_executable
33
34REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
35sys.path.append(os.path.join(REPO_ROOT, 'infra', 'ci'))
36from config import JOB_CONFIGS, SANDBOX_IMG
37
38try:
39  input = raw_input
40except NameError:
41  pass
42
43
44def user_in_docker_group():
45  try:
46    group = grp.getgrnam('docker')
47  except KeyError:
48    return False
49  else:
50    return group.gr_gid in os.getgroups()
51
52
53def decision(question='Would you like to continue', confirm=True, default='n'):
54  default = default.lower().strip()
55  yes = default in {'y', 'yes'}
56  no = default in {'n', 'no'}
57  default = 'y' if yes else 'n'
58  prompt = '%s? [%s/%s]: ' % (question, 'Y' if yes else 'y', 'N' if no else 'n')
59  if not confirm:
60    print('%sy' % prompt)
61    return
62  while True:
63    choice = input(prompt).lower().strip()
64    if not choice:
65      choice = default
66    if choice in {'y', 'yes'}:
67      return
68    elif choice in {'n', 'no'}:
69      sys.exit(3)
70
71
72def main():
73  parser = argparse.ArgumentParser(
74      formatter_class=argparse.ArgumentDefaultsHelpFormatter)
75  parser.add_argument('config', choices=JOB_CONFIGS.keys())
76  parser.add_argument(
77      '--runner',
78      help='The container runner executable to use',
79      choices=('podman', 'docker'),
80      default='podman' if find_executable('podman') else 'docker')
81  parser.add_argument(
82      '--build',
83      action='store_true',
84      help='Will perform a build of sandbox image')
85  group = parser.add_mutually_exclusive_group()
86  group.add_argument(
87      '--confirm',
88      action='store_true',
89      default=True,
90      help='User confirmation of decision prompts')
91  group.add_argument(
92      '--no-confirm',
93      dest='confirm',
94      action='store_false',
95      help='Forces confirmation of decision prompts')
96  args = parser.parse_args()
97
98  # Check that the directory is clean.
99  git_cmd = ['git', '-C', REPO_ROOT, 'status', '--porcelain']
100  modified_files = subprocess.check_output(git_cmd).decode()
101  if modified_files:
102    print('The current Git repo has modified/untracked files.')
103    print('The sandboxed VM will fetch the HEAD of your current git repo.')
104    print('This is probably not the state you want to be in.')
105    print('I suggest you stop, commit and then re-run this script')
106    print('Modified files:\n' + modified_files)
107    decision('Do you know what you are doing', confirm=args.confirm)
108
109  if args.build:
110    print('')
111    print('About to build %r locally with %r' % (args.image, args.runner))
112    decision(confirm=args.confirm)
113    check_call(('make', '-C', os.path.join(REPO_ROOT, 'infra',
114                                           'ci'),
115                'BUILDER=%s' % args.runner, 'build-sandbox'))
116
117  bundle_path = '/tmp/perfetto-ci.bundle'
118  check_call(['git', '-C', REPO_ROOT, 'bundle', 'create', bundle_path, 'HEAD' ])
119  os.chmod(bundle_path, 0o664)
120  env = {
121      'PERFETTO_TEST_GIT_REF': bundle_path,
122  }
123  env.update(JOB_CONFIGS[args.config])
124
125  workdir = os.path.join(REPO_ROOT, 'out', 'tmp.ci')
126  cmd = []
127  if args.runner == 'docker' and not user_in_docker_group():
128    cmd += ['sudo', '--']
129  cmd += [
130      args.runner, 'run', '-it', '--name', 'perfetto_ci', '--cap-add',
131      'SYS_PTRACE', '--rm', '--volume',
132      '%s:/ci/ramdisk' % workdir, '--tmpfs', '/tmp:exec',
133      '--volume=%s:%s:ro' % (bundle_path, bundle_path)
134  ]
135  for kv in env.items():
136    cmd += ['--env', '%s=%s' % kv]
137  cmd += [SANDBOX_IMG]
138  cmd += [
139      'bash', '-c',
140      'cd /ci/ramdisk; bash /ci/init.sh || sudo -u perfetto -EH bash -i'
141  ]
142
143  print(
144      'About to run\n',
145      ' '.join('\n  ' + c if c.startswith('--') or c == 'bash' else quote(c)
146               for c in cmd))
147  print('')
148  print('The VM workdir /ci/ramdisk will be mounted into: %s' % workdir)
149  print('The contents of %s will be deleted before starting the VM' % workdir)
150  decision(confirm=args.confirm)
151
152  try:
153    shutil.rmtree(workdir)
154  except EnvironmentError as e:
155    if e.errno == errno.ENOENT:
156      pass
157    elif e.errno == errno.EACCES:
158      print('')
159      print('Removing previous volume %r' % workdir)
160      check_call(('sudo', 'rm', '-r', quote(workdir)))
161    else:
162      raise
163
164  os.makedirs(workdir)
165  os.execvp(cmd[0], cmd)
166
167
168if __name__ == '__main__':
169  sys.exit(main())
170