• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright (C) 2021 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""" Builds all the revisions in channels.json and deploys them if --upload.
16
17See go/perfetto-ui-autopush for docs on how this works end-to-end.
18"""
19
20import argparse
21import json
22import os
23import re
24import shutil
25import subprocess
26import sys
27
28from os.path import dirname
29pjoin = os.path.join
30
31BUCKET_NAME = 'ui.perfetto.dev'
32CUR_DIR = dirname(os.path.abspath(__file__))
33ROOT_DIR = dirname(dirname(CUR_DIR))
34
35
36def check_call_and_log(args):
37  print(' '.join(args))
38  subprocess.check_call(args)
39
40
41def check_output(args):
42  return subprocess.check_output(args).decode().strip()
43
44
45def version_exists(version):
46  url = 'https://commondatastorage.googleapis.com/%s/%s/manifest.json' % (
47      BUCKET_NAME, version)
48  return 0 == subprocess.call(['curl', '-fLs', '-o', '/dev/null', url])
49
50
51def build_git_revision(channel, git_ref, tmp_dir):
52  workdir = pjoin(tmp_dir, channel)
53  check_call_and_log(['rm', '-rf', workdir])
54  check_call_and_log(['git', 'clone', '--quiet', '--shared', ROOT_DIR, workdir])
55  old_cwd = os.getcwd()
56  os.chdir(workdir)
57  try:
58    check_call_and_log(['git', 'reset', '--hard', git_ref])
59    check_call_and_log(['git', 'clean', '-dfx'])
60    git_sha = check_output(['git', 'rev-parse', 'HEAD'])
61    print('===================================================================')
62    print('Building UI for channel %s @ %s (%s)' % (channel, git_ref, git_sha))
63    print('===================================================================')
64    version = check_output(['tools/write_version_header.py', '--stdout'])
65    check_call_and_log(['tools/install-build-deps', '--ui'])
66    check_call_and_log(['ui/build'])
67    return version, pjoin(workdir, 'ui/out/dist')
68  finally:
69    os.chdir(old_cwd)
70
71
72def build_all_channels(channels, tmp_dir, merged_dist_dir):
73  channel_map = {}
74  for chan in channels:
75    channel = chan['name']
76    git_ref = chan['rev']
77    # version here is something like "v1.2.3".
78    version, dist_dir = build_git_revision(channel, git_ref, tmp_dir)
79    channel_map[channel] = version
80    check_call_and_log(['cp', '-an', pjoin(dist_dir, version), merged_dist_dir])
81    if channel != 'stable':
82      continue
83    # Copy also the /index.html and /service_worker.*, but only for the stable
84    # channel. The /index.html and SW must be shared between all channels,
85    # because they are all reachable through ui.perfetto.dev/. Both the index
86    # and the SQ are supposed to be version-independent (go/perfetto-channels).
87    # If an accidental incompatibility bug sneaks in, we should much rather
88    # crash canary (or any other channel) rather than stable. Hence why we copy
89    # the index+sw from the stable channel.
90    for fname in os.listdir(dist_dir):
91      fpath = pjoin(dist_dir, fname)
92      if os.path.isfile(fpath):
93        check_call_and_log(['cp', '-an', fpath, merged_dist_dir])
94  return channel_map
95
96
97def main():
98  parser = argparse.ArgumentParser()
99  parser.add_argument('--upload', action='store_true')
100  parser.add_argument('--tmp', default='/tmp/perfetto_ui')
101  args = parser.parse_args()
102
103  # Read the releases.json, which maps channel names to git refs, e.g.:
104  # {name:'stable', rev:'a0b1c2...0}, {name:'canary', rev:'HEAD'}
105  channels = []
106  with open(pjoin(CUR_DIR, 'channels.json')) as f:
107    channels = json.load(f)['channels']
108
109  merged_dist_dir = pjoin(args.tmp, 'dist')
110  check_call_and_log(['rm', '-rf', merged_dist_dir])
111  shutil.os.makedirs(merged_dist_dir)
112  channel_map = build_all_channels(channels, args.tmp, merged_dist_dir)
113
114  print('Updating index in ' + merged_dist_dir)
115  with open(pjoin(merged_dist_dir, 'index.html'), 'r+') as f:
116    index_html = f.read()
117    f.seek(0, 0)
118    f.truncate()
119    index_html = re.sub(r"data-perfetto_version='[^']*'",
120                        "data-perfetto_version='%s'" % json.dumps(channel_map),
121                        index_html)
122    f.write(index_html)
123
124  if not args.upload:
125    return
126
127  print('===================================================================')
128  print('Uploading to gs://%s' % BUCKET_NAME)
129  print('===================================================================')
130  cp_cmd = [
131      'gsutil', '-m', '-h', 'Cache-Control:public, max-age=3600', 'cp', '-j',
132      'html,js,css,wasm'
133  ]
134  for name in os.listdir(merged_dist_dir):
135    path = pjoin(merged_dist_dir, name)
136    if os.path.isdir(path):
137      if version_exists(name):
138        print('Skipping upload of %s because it already exists on GCS' % name)
139        continue
140      check_call_and_log(cp_cmd + ['-r', path, 'gs://%s/' % BUCKET_NAME])
141    else:
142      # /index.html or /service_worker.js{,.map}
143      check_call_and_log(cp_cmd + [path, 'gs://%s/%s' % (BUCKET_NAME, name)])
144
145
146if __name__ == '__main__':
147  sys.exit(main())
148