blob: 69955873ef693a49311742f98d47da5856de9ac1 [file] [log] [blame]
Tiancong Wang6cfc1632019-07-25 21:32:371#!/usr/bin/python
2# Copyright 2018 The Chromium Authors. All rights reserved.
3# 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
23import urllib2
24
25GS_HTTP_URL = 'https://storage.googleapis.com'
26
27
28def ReadUpToDateProfileName(newest_profile_name_path):
29 with open(newest_profile_name_path) as f:
30 return f.read().strip()
31
32
33def ReadLocalProfileName(local_profile_name_path):
34 try:
35 with open(local_profile_name_path) as f:
36 return f.read().strip()
37 except IOError:
38 # Assume it either didn't exist, or we couldn't read it. In either case, we
39 # should probably grab a new profile (and, in doing so, make this file sane
40 # again)
41 return None
42
43
44def WriteLocalProfileName(name, local_profile_name_path):
45 with open(local_profile_name_path, 'w') as f:
46 f.write(name)
47
48
49def CheckCallOrExit(cmd):
50 proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
51 stdout, stderr = proc.communicate()
52 exit_code = proc.wait()
53 if not exit_code:
54 return
55
56 complaint_lines = [
57 '## %s failed with exit code %d' % (cmd[0], exit_code),
58 '## Full command: %s' % cmd,
59 '## Stdout:\n' + stdout,
60 '## Stderr:\n' + stderr,
61 ]
Raul Tambref3d9412e2019-09-24 05:31:4462 print('\n'.join(complaint_lines), file=sys.stderr)
Tiancong Wang6cfc1632019-07-25 21:32:3763 sys.exit(1)
64
65
66def RetrieveProfile(desired_profile_name, out_path, gs_url_base):
67 # vpython is > python 2.7.9, so we can expect urllib to validate HTTPS certs
68 # properly.
69 ext = os.path.splitext(desired_profile_name)[1]
Yuke Liao9eeef5e2020-04-24 18:59:1270 if ext in ['.bz2', '.xz']:
71 # For extension that requires explicit decompression, decompression will
72 # change the eventual file names by dropping the extension, and that's why
73 # an extra extension is appended here to make sure that the decompressed
74 # file path matches the |out_path| passed in as parameter.
75 out_path += ext
76
Tiancong Wang6cfc1632019-07-25 21:32:3777 gs_prefix = 'gs://'
78 if not desired_profile_name.startswith(gs_prefix):
Sebastien Marchande8939672020-04-27 17:35:4279 gs_url = '/'.join([GS_HTTP_URL, gs_url_base, desired_profile_name])
Tiancong Wang6cfc1632019-07-25 21:32:3780 else:
Sebastien Marchande8939672020-04-27 17:35:4281 gs_url = '/'.join([GS_HTTP_URL, desired_profile_name[len(gs_prefix):]])
Tiancong Wang6cfc1632019-07-25 21:32:3782
83 with contextlib.closing(urllib2.urlopen(gs_url)) as u:
Yuke Liao9eeef5e2020-04-24 18:59:1284 with open(out_path, 'wb') as f:
Tiancong Wang6cfc1632019-07-25 21:32:3785 while True:
86 buf = u.read(4096)
87 if not buf:
88 break
89 f.write(buf)
90
91 if ext == '.bz2':
92 # NOTE: we can't use Python's bzip module, since it doesn't support
93 # multi-stream bzip files. It will silently succeed and give us a garbage
94 # profile.
95 # bzip2 removes the compressed file on success.
Yuke Liao9eeef5e2020-04-24 18:59:1296 CheckCallOrExit(['bzip2', '-d', out_path])
Tiancong Wang6cfc1632019-07-25 21:32:3797 elif ext == '.xz':
98 # ...And we can't use the `lzma` module, since it was introduced in python3.
99 # xz removes the compressed file on success.
Yuke Liao9eeef5e2020-04-24 18:59:12100 CheckCallOrExit(['xz', '-d', out_path])
Tiancong Wang6cfc1632019-07-25 21:32:37101
102
103def main():
104 parser = argparse.ArgumentParser(
Yuke Liao001b62a52020-04-22 01:23:11105 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
Tiancong Wang6cfc1632019-07-25 21:32:37106 parser.add_argument(
107 '--newest_state',
108 required=True,
109 help='Path to the file with name of the newest profile. '
Yuke Liao001b62a52020-04-22 01:23:11110 'We use this file to track the name of the newest profile '
111 'we should pull.')
Tiancong Wang6cfc1632019-07-25 21:32:37112 parser.add_argument(
113 '--local_state',
114 required=True,
115 help='Path of the file storing name of the local profile. '
Yuke Liao001b62a52020-04-22 01:23:11116 'We use this file to track the most recent profile we\'ve '
117 'successfully pulled.')
Tiancong Wang6cfc1632019-07-25 21:32:37118 parser.add_argument(
119 '--gs_url_base',
120 required=True,
Yuke Liao001b62a52020-04-22 01:23:11121 help='The base GS URL to search for the profile.')
Tiancong Wang6cfc1632019-07-25 21:32:37122 parser.add_argument(
123 '--output_name',
124 required=True,
Yuke Liao001b62a52020-04-22 01:23:11125 help='Output name of the downloaded and uncompressed profile.')
Tiancong Wang6cfc1632019-07-25 21:32:37126 parser.add_argument(
Yuke Liao001b62a52020-04-22 01:23:11127 '-f',
128 '--force',
Tiancong Wang6cfc1632019-07-25 21:32:37129 action='store_true',
Yuke Liao001b62a52020-04-22 01:23:11130 help='Fetch a profile even if the local one is current.')
Tiancong Wang6cfc1632019-07-25 21:32:37131 args = parser.parse_args()
132
133 up_to_date_profile = ReadUpToDateProfileName(args.newest_state)
134 if not args.force:
135 local_profile_name = ReadLocalProfileName(args.local_state)
136 # In a perfect world, the local profile should always exist if we
137 # successfully read local_profile_name. If it's gone, though, the user
138 # probably removed it as a way to get us to download it again.
139 if local_profile_name == up_to_date_profile \
140 and os.path.exists(args.output_name):
141 return 0
142
143 new_tmpfile = args.output_name + '.new'
144 RetrieveProfile(up_to_date_profile, new_tmpfile, args.gs_url_base)
145 os.rename(new_tmpfile, args.output_name)
146 WriteLocalProfileName(up_to_date_profile, args.local_state)
147
148
149if __name__ == '__main__':
150 sys.exit(main())