blob: 713774774f414e03a85c87f1d44756baf5d7c42e [file] [log] [blame]
Kaido Kert56d7c4e2024-04-13 12:59:27 -07001#!/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', 'ci'),
114 'BUILDER=%s' % args.runner, 'build-sandbox'))
115
116 bundle_path = '/tmp/perfetto-ci.bundle'
117 check_call(['git', '-C', REPO_ROOT, 'bundle', 'create', bundle_path, 'HEAD'])
118 os.chmod(bundle_path, 0o664)
119 env = {
120 'PERFETTO_TEST_GIT_REF': bundle_path,
121 }
122 env.update(JOB_CONFIGS[args.config])
123
124 workdir = os.path.join(REPO_ROOT, 'out', 'tmp.ci')
125 cmd = []
126 if args.runner == 'docker' and not user_in_docker_group():
127 cmd += ['sudo', '--']
128 cmd += [
129 args.runner, 'run', '-it', '--name', 'perfetto_ci', '--cap-add',
130 'SYS_PTRACE', '--rm', '--volume',
131 '%s:/ci/ramdisk' % workdir, '--tmpfs', '/tmp:exec',
132 '--volume=%s:%s:ro' % (bundle_path, bundle_path)
133 ]
134 for kv in env.items():
135 cmd += ['--env', '%s=%s' % kv]
136 cmd += [SANDBOX_IMG]
137 cmd += [
138 'bash', '-c',
139 'cd /ci/ramdisk; bash /ci/init.sh || sudo -u perfetto -EH bash -i'
140 ]
141
142 print(
143 'About to run\n',
144 ' '.join('\n ' + c if c.startswith('--') or c == 'bash' else quote(c)
145 for c in cmd))
146 print('')
147 print('The VM workdir /ci/ramdisk will be mounted into: %s' % workdir)
148 print('The contents of %s will be deleted before starting the VM' % workdir)
149 decision(confirm=args.confirm)
150
151 try:
152 shutil.rmtree(workdir)
153 except EnvironmentError as e:
154 if e.errno == errno.ENOENT:
155 pass
156 elif e.errno == errno.EACCES:
157 print('')
158 print('Removing previous volume %r' % workdir)
159 check_call(('sudo', 'rm', '-r', quote(workdir)))
160 else:
161 raise
162
163 os.makedirs(workdir)
164 os.execvp(cmd[0], cmd)
165
166
167if __name__ == '__main__':
168 sys.exit(main())