|  | #!/usr/bin/env python3 | 
|  | # Copyright (C) 2021 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. | 
|  | """ Builds all the revisions in channels.json and deploys them if --upload. | 
|  |  | 
|  | See go/perfetto-ui-autopush for docs on how this works end-to-end. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import json | 
|  | import os | 
|  | import re | 
|  | import shutil | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  | from os.path import dirname | 
|  | pjoin = os.path.join | 
|  |  | 
|  | BUCKET_NAME = 'ui.perfetto.dev' | 
|  | CUR_DIR = dirname(os.path.abspath(__file__)) | 
|  | ROOT_DIR = dirname(dirname(CUR_DIR)) | 
|  |  | 
|  |  | 
|  | def check_call_and_log(args): | 
|  | print(' '.join(args)) | 
|  | subprocess.check_call(args) | 
|  |  | 
|  |  | 
|  | def check_output(args): | 
|  | return subprocess.check_output(args).decode().strip() | 
|  |  | 
|  |  | 
|  | def version_exists(version): | 
|  | url = 'https://commondatastorage.googleapis.com/%s/%s/manifest.json' % ( | 
|  | BUCKET_NAME, version) | 
|  | return 0 == subprocess.call(['curl', '-fLs', '-o', '/dev/null', url]) | 
|  |  | 
|  |  | 
|  | def build_git_revision(channel, git_ref, tmp_dir): | 
|  | workdir = pjoin(tmp_dir, channel) | 
|  | check_call_and_log(['rm', '-rf', workdir]) | 
|  | check_call_and_log(['git', 'clone', '--quiet', '--shared', ROOT_DIR, workdir]) | 
|  | old_cwd = os.getcwd() | 
|  | os.chdir(workdir) | 
|  | try: | 
|  | check_call_and_log(['git', 'reset', '--hard', git_ref]) | 
|  | check_call_and_log(['git', 'clean', '-dfx']) | 
|  | git_sha = check_output(['git', 'rev-parse', 'HEAD']) | 
|  | print('===================================================================') | 
|  | print('Building UI for channel %s @ %s (%s)' % (channel, git_ref, git_sha)) | 
|  | print('===================================================================') | 
|  | version = check_output(['tools/write_version_header.py', '--stdout']) | 
|  | check_call_and_log(['tools/install-build-deps', '--ui']) | 
|  | check_call_and_log(['ui/build']) | 
|  | return version, pjoin(workdir, 'ui/out/dist') | 
|  | finally: | 
|  | os.chdir(old_cwd) | 
|  |  | 
|  |  | 
|  | def build_all_channels(channels, tmp_dir, merged_dist_dir): | 
|  | channel_map = {} | 
|  | for chan in channels: | 
|  | channel = chan['name'] | 
|  | git_ref = chan['rev'] | 
|  | # version here is something like "v1.2.3". | 
|  | version, dist_dir = build_git_revision(channel, git_ref, tmp_dir) | 
|  | channel_map[channel] = version | 
|  | check_call_and_log(['cp', '-an', pjoin(dist_dir, version), merged_dist_dir]) | 
|  | if channel != 'stable': | 
|  | continue | 
|  | # Copy also the /index.html and /service_worker.*, but only for the stable | 
|  | # channel. The /index.html and SW must be shared between all channels, | 
|  | # because they are all reachable through ui.perfetto.dev/. Both the index | 
|  | # and the SQ are supposed to be version-independent (go/perfetto-channels). | 
|  | # If an accidental incompatibility bug sneaks in, we should much rather | 
|  | # crash canary (or any other channel) rather than stable. Hence why we copy | 
|  | # the index+sw from the stable channel. | 
|  | for fname in os.listdir(dist_dir): | 
|  | fpath = pjoin(dist_dir, fname) | 
|  | if os.path.isfile(fpath): | 
|  | check_call_and_log(['cp', '-an', fpath, merged_dist_dir]) | 
|  | return channel_map | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument('--upload', action='store_true') | 
|  | parser.add_argument('--tmp', default='/tmp/perfetto_ui') | 
|  | args = parser.parse_args() | 
|  |  | 
|  | # Read the releases.json, which maps channel names to git refs, e.g.: | 
|  | # {name:'stable', rev:'a0b1c2...0}, {name:'canary', rev:'HEAD'} | 
|  | channels = [] | 
|  | with open(pjoin(CUR_DIR, 'channels.json')) as f: | 
|  | channels = json.load(f)['channels'] | 
|  |  | 
|  | merged_dist_dir = pjoin(args.tmp, 'dist') | 
|  | check_call_and_log(['rm', '-rf', merged_dist_dir]) | 
|  | shutil.os.makedirs(merged_dist_dir) | 
|  | channel_map = build_all_channels(channels, args.tmp, merged_dist_dir) | 
|  |  | 
|  | print('Updating index in ' + merged_dist_dir) | 
|  | with open(pjoin(merged_dist_dir, 'index.html'), 'r+') as f: | 
|  | index_html = f.read() | 
|  | f.seek(0, 0) | 
|  | f.truncate() | 
|  | index_html = re.sub(r"data-perfetto_version='[^']*'", | 
|  | "data-perfetto_version='%s'" % json.dumps(channel_map), | 
|  | index_html) | 
|  | f.write(index_html) | 
|  |  | 
|  | if not args.upload: | 
|  | return | 
|  |  | 
|  | print('===================================================================') | 
|  | print('Uploading to gs://%s' % BUCKET_NAME) | 
|  | print('===================================================================') | 
|  | cp_cmd = [ | 
|  | 'gsutil', '-m', '-h', 'Cache-Control:public, max-age=3600', 'cp', '-j', | 
|  | 'html,js,css,wasm' | 
|  | ] | 
|  | for name in os.listdir(merged_dist_dir): | 
|  | path = pjoin(merged_dist_dir, name) | 
|  | if os.path.isdir(path): | 
|  | if version_exists(name): | 
|  | print('Skipping upload of %s because it already exists on GCS' % name) | 
|  | continue | 
|  | check_call_and_log(cp_cmd + ['-r', path, 'gs://%s/' % BUCKET_NAME]) | 
|  | else: | 
|  | # /index.html or /service_worker.js{,.map} | 
|  | check_call_and_log(cp_cmd + [path, 'gs://%s/%s' % (BUCKET_NAME, name)]) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |