blob: ba4592dfcc85a0af1f3df28afb76aecfcf6d7b8e [file] [log] [blame]
Denis Nikitinc639662a92021-10-20 00:09:461#!/usr/bin/env vpython3
Avi Drissmandfd880852022-09-15 20:11:092# Copyright 2018 The Chromium Authors
Tiancong Wang6cfc1632019-07-25 21:32:373# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
Yuke Liao001b62a52020-04-22 01:23:115"""This script is used to update local profiles (AFDO, PGO or orderfiles)
Tiancong Wang6cfc1632019-07-25 21:32:376
Yuke Liao001b62a52020-04-22 01:23:117This uses profiles of Chrome, or orderfiles for compiling or linking. Though the
8profiles are available externally, the bucket they sit in is otherwise
9unreadable by non-Googlers. Gsutil usage with this bucket is therefore quite
10awkward: you can't do anything but `cp` certain files with an external account,
11and you can't even do that if you're not yet authenticated.
Tiancong Wang6cfc1632019-07-25 21:32:3712
Yuke Liao001b62a52020-04-22 01:23:1113No authentication is necessary if you pull these profiles directly over https.
Tiancong Wang6cfc1632019-07-25 21:32:3714"""
15
Raul Tambref3d9412e2019-09-24 05:31:4416from __future__ import print_function
17
Tiancong Wang6cfc1632019-07-25 21:32:3718import argparse
19import contextlib
20import os
21import subprocess
22import sys
Dirk Pranke926f89d2021-05-18 18:42:3123
Denis Nikitinc639662a92021-10-20 00:09:4624from urllib.request import urlopen
Tiancong Wang6cfc1632019-07-25 21:32:3725
26GS_HTTP_URL = 'https://storage.googleapis.com'
27
28
29def ReadUpToDateProfileName(newest_profile_name_path):
30 with open(newest_profile_name_path) as f:
31 return f.read().strip()
32
33
34def ReadLocalProfileName(local_profile_name_path):
35 try:
36 with open(local_profile_name_path) as f:
37 return f.read().strip()
38 except IOError:
39 # Assume it either didn't exist, or we couldn't read it. In either case, we
40 # should probably grab a new profile (and, in doing so, make this file sane
41 # again)
42 return None
43
44
45def WriteLocalProfileName(name, local_profile_name_path):
46 with open(local_profile_name_path, 'w') as f:
47 f.write(name)
48
49
50def CheckCallOrExit(cmd):
Denis Nikitinc639662a92021-10-20 00:09:4651 proc = subprocess.Popen(cmd,
52 stdout=subprocess.PIPE,
53 stderr=subprocess.PIPE,
54 encoding='utf-8')
Tiancong Wang6cfc1632019-07-25 21:32:3755 stdout, stderr = proc.communicate()
56 exit_code = proc.wait()
57 if not exit_code:
58 return
59
60 complaint_lines = [
61 '## %s failed with exit code %d' % (cmd[0], exit_code),
62 '## Full command: %s' % cmd,
63 '## Stdout:\n' + stdout,
64 '## Stderr:\n' + stderr,
65 ]
Raul Tambref3d9412e2019-09-24 05:31:4466 print('\n'.join(complaint_lines), file=sys.stderr)
Tiancong Wang6cfc1632019-07-25 21:32:3767 sys.exit(1)
68
69
70def RetrieveProfile(desired_profile_name, out_path, gs_url_base):
71 # vpython is > python 2.7.9, so we can expect urllib to validate HTTPS certs
72 # properly.
73 ext = os.path.splitext(desired_profile_name)[1]
Yuke Liao9eeef5e2020-04-24 18:59:1274 if ext in ['.bz2', '.xz']:
75 # For extension that requires explicit decompression, decompression will
76 # change the eventual file names by dropping the extension, and that's why
77 # an extra extension is appended here to make sure that the decompressed
78 # file path matches the |out_path| passed in as parameter.
79 out_path += ext
80
Tiancong Wang6cfc1632019-07-25 21:32:3781 gs_prefix = 'gs://'
82 if not desired_profile_name.startswith(gs_prefix):
Sebastien Marchande8939672020-04-27 17:35:4283 gs_url = '/'.join([GS_HTTP_URL, gs_url_base, desired_profile_name])
Tiancong Wang6cfc1632019-07-25 21:32:3784 else:
Sebastien Marchande8939672020-04-27 17:35:4285 gs_url = '/'.join([GS_HTTP_URL, desired_profile_name[len(gs_prefix):]])
Tiancong Wang6cfc1632019-07-25 21:32:3786
Dirk Pranke926f89d2021-05-18 18:42:3187 with contextlib.closing(urlopen(gs_url)) as u:
Yuke Liao9eeef5e2020-04-24 18:59:1288 with open(out_path, 'wb') as f:
Tiancong Wang6cfc1632019-07-25 21:32:3789 while True:
90 buf = u.read(4096)
91 if not buf:
92 break
93 f.write(buf)
94
95 if ext == '.bz2':
96 # NOTE: we can't use Python's bzip module, since it doesn't support
97 # multi-stream bzip files. It will silently succeed and give us a garbage
98 # profile.
99 # bzip2 removes the compressed file on success.
Yuke Liao9eeef5e2020-04-24 18:59:12100 CheckCallOrExit(['bzip2', '-d', out_path])
Tiancong Wang6cfc1632019-07-25 21:32:37101 elif ext == '.xz':
102 # ...And we can't use the `lzma` module, since it was introduced in python3.
103 # xz removes the compressed file on success.
Yuke Liao9eeef5e2020-04-24 18:59:12104 CheckCallOrExit(['xz', '-d', out_path])
Tiancong Wang6cfc1632019-07-25 21:32:37105
106
107def main():
108 parser = argparse.ArgumentParser(
Yuke Liao001b62a52020-04-22 01:23:11109 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
Tiancong Wang6cfc1632019-07-25 21:32:37110 parser.add_argument(
111 '--newest_state',
112 required=True,
113 help='Path to the file with name of the newest profile. '
Yuke Liao001b62a52020-04-22 01:23:11114 'We use this file to track the name of the newest profile '
115 'we should pull.')
Tiancong Wang6cfc1632019-07-25 21:32:37116 parser.add_argument(
117 '--local_state',
118 required=True,
119 help='Path of the file storing name of the local profile. '
Yuke Liao001b62a52020-04-22 01:23:11120 'We use this file to track the most recent profile we\'ve '
121 'successfully pulled.')
Tiancong Wang6cfc1632019-07-25 21:32:37122 parser.add_argument(
123 '--gs_url_base',
124 required=True,
Yuke Liao001b62a52020-04-22 01:23:11125 help='The base GS URL to search for the profile.')
Tiancong Wang6cfc1632019-07-25 21:32:37126 parser.add_argument(
127 '--output_name',
128 required=True,
Yuke Liao001b62a52020-04-22 01:23:11129 help='Output name of the downloaded and uncompressed profile.')
Tiancong Wang6cfc1632019-07-25 21:32:37130 parser.add_argument(
Yuke Liao001b62a52020-04-22 01:23:11131 '-f',
132 '--force',
Tiancong Wang6cfc1632019-07-25 21:32:37133 action='store_true',
Yuke Liao001b62a52020-04-22 01:23:11134 help='Fetch a profile even if the local one is current.')
Tiancong Wang6cfc1632019-07-25 21:32:37135 args = parser.parse_args()
136
137 up_to_date_profile = ReadUpToDateProfileName(args.newest_state)
138 if not args.force:
139 local_profile_name = ReadLocalProfileName(args.local_state)
140 # In a perfect world, the local profile should always exist if we
141 # successfully read local_profile_name. If it's gone, though, the user
142 # probably removed it as a way to get us to download it again.
143 if local_profile_name == up_to_date_profile \
144 and os.path.exists(args.output_name):
145 return 0
146
147 new_tmpfile = args.output_name + '.new'
148 RetrieveProfile(up_to_date_profile, new_tmpfile, args.gs_url_base)
149 os.rename(new_tmpfile, args.output_name)
150 WriteLocalProfileName(up_to_date_profile, args.local_state)
151
152
153if __name__ == '__main__':
154 sys.exit(main())