#!/usr/bin/env python3 # Copyright (C) 2019 The Android Open Source Project # # 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 # # http://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. from __future__ import print_function import argparse import distutils import errno import grp import os import readline import sys import shutil import subprocess from pipes import quote from subprocess import check_call try: from shutil import which as find_executable except AttributeError: from distutils.spawn import find_executable REPO_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) sys.path.append(os.path.join(REPO_ROOT, 'infra', 'ci')) from config import JOB_CONFIGS, SANDBOX_IMG try: input = raw_input except NameError: pass def user_in_docker_group(): try: group = grp.getgrnam('docker') except KeyError: return False else: return group.gr_gid in os.getgroups() def decision(question='Would you like to continue', confirm=True, default='n'): default = default.lower().strip() yes = default in {'y', 'yes'} no = default in {'n', 'no'} default = 'y' if yes else 'n' prompt = '%s? [%s/%s]: ' % (question, 'Y' if yes else 'y', 'N' if no else 'n') if not confirm: print('%sy' % prompt) return while True: choice = input(prompt).lower().strip() if not choice: choice = default if choice in {'y', 'yes'}: return elif choice in {'n', 'no'}: sys.exit(3) def main(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('config', choices=JOB_CONFIGS.keys()) parser.add_argument( '--runner', help='The container runner executable to use', choices=('podman', 'docker'), default='podman' if find_executable('podman') else 'docker') parser.add_argument( '--build', action='store_true', help='Will perform a build of sandbox image') group = parser.add_mutually_exclusive_group() group.add_argument( '--confirm', action='store_true', default=True, help='User confirmation of decision prompts') group.add_argument( '--no-confirm', dest='confirm', action='store_false', help='Forces confirmation of decision prompts') args = parser.parse_args() # Check that the directory is clean. git_cmd = ['git', '-C', REPO_ROOT, 'status', '--porcelain'] modified_files = subprocess.check_output(git_cmd).decode() if modified_files: print('The current Git repo has modified/untracked files.') print('The sandboxed VM will fetch the HEAD of your current git repo.') print('This is probably not the state you want to be in.') print('I suggest you stop, commit and then re-run this script') print('Modified files:\n' + modified_files) decision('Do you know what you are doing', confirm=args.confirm) if args.build: print('') print('About to build %r locally with %r' % (args.image, args.runner)) decision(confirm=args.confirm) check_call(('make', '-C', os.path.join(REPO_ROOT, 'infra', 'ci'), 'BUILDER=%s' % args.runner, 'build-sandbox')) bundle_path = '/tmp/perfetto-ci.bundle' check_call(['git', '-C', REPO_ROOT, 'bundle', 'create', bundle_path, 'HEAD' ]) os.chmod(bundle_path, 0o664) env = { 'PERFETTO_TEST_GIT_REF': bundle_path, } env.update(JOB_CONFIGS[args.config]) workdir = os.path.join(REPO_ROOT, 'out', 'tmp.ci') cmd = [] if args.runner == 'docker' and not user_in_docker_group(): cmd += ['sudo', '--'] cmd += [ args.runner, 'run', '-it', '--name', 'perfetto_ci', '--cap-add', 'SYS_PTRACE', '--rm', '--volume', '%s:/ci/ramdisk' % workdir, '--tmpfs', '/tmp:exec', '--volume=%s:%s:ro' % (bundle_path, bundle_path) ] for kv in env.items(): cmd += ['--env', '%s=%s' % kv] cmd += [SANDBOX_IMG] cmd += [ 'bash', '-c', 'cd /ci/ramdisk; bash /ci/init.sh || sudo -u perfetto -EH bash -i' ] print( 'About to run\n', ' '.join('\n ' + c if c.startswith('--') or c == 'bash' else quote(c) for c in cmd)) print('') print('The VM workdir /ci/ramdisk will be mounted into: %s' % workdir) print('The contents of %s will be deleted before starting the VM' % workdir) decision(confirm=args.confirm) try: shutil.rmtree(workdir) except EnvironmentError as e: if e.errno == errno.ENOENT: pass elif e.errno == errno.EACCES: print('') print('Removing previous volume %r' % workdir) check_call(('sudo', 'rm', '-r', quote(workdir))) else: raise os.makedirs(workdir) os.execvp(cmd[0], cmd) if __name__ == '__main__': sys.exit(main())