blob: e5a6cab61a17283bc022b3929448d14bc9669204 [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]
70 compressed_path = out_path + ext
71 gs_prefix = 'gs://'
72 if not desired_profile_name.startswith(gs_prefix):
73 gs_url = os.path.join(GS_HTTP_URL, gs_url_base, desired_profile_name)
74 else:
75 gs_url = os.path.join(GS_HTTP_URL, desired_profile_name[len(gs_prefix):])
76
77 with contextlib.closing(urllib2.urlopen(gs_url)) as u:
78 with open(compressed_path, 'wb') as f:
79 while True:
80 buf = u.read(4096)
81 if not buf:
82 break
83 f.write(buf)
84
85 if ext == '.bz2':
86 # NOTE: we can't use Python's bzip module, since it doesn't support
87 # multi-stream bzip files. It will silently succeed and give us a garbage
88 # profile.
89 # bzip2 removes the compressed file on success.
90 CheckCallOrExit(['bzip2', '-d', compressed_path])
91 elif ext == '.xz':
92 # ...And we can't use the `lzma` module, since it was introduced in python3.
93 # xz removes the compressed file on success.
94 CheckCallOrExit(['xz', '-d', compressed_path])
95 else:
96 # Wait until after downloading the file to check the file extension, so the
97 # user has something usable locally if the file extension is unrecognized.
98 raise ValueError(
99 'Only bz2 and xz extensions are supported; "%s" is not' % ext)
100
101
102def main():
103 parser = argparse.ArgumentParser(
Yuke Liao001b62a52020-04-22 01:23:11104 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
Tiancong Wang6cfc1632019-07-25 21:32:37105 parser.add_argument(
106 '--newest_state',
107 required=True,
108 help='Path to the file with name of the newest profile. '
Yuke Liao001b62a52020-04-22 01:23:11109 'We use this file to track the name of the newest profile '
110 'we should pull.')
Tiancong Wang6cfc1632019-07-25 21:32:37111 parser.add_argument(
112 '--local_state',
113 required=True,
114 help='Path of the file storing name of the local profile. '
Yuke Liao001b62a52020-04-22 01:23:11115 'We use this file to track the most recent profile we\'ve '
116 'successfully pulled.')
Tiancong Wang6cfc1632019-07-25 21:32:37117 parser.add_argument(
118 '--gs_url_base',
119 required=True,
Yuke Liao001b62a52020-04-22 01:23:11120 help='The base GS URL to search for the profile.')
Tiancong Wang6cfc1632019-07-25 21:32:37121 parser.add_argument(
122 '--output_name',
123 required=True,
Yuke Liao001b62a52020-04-22 01:23:11124 help='Output name of the downloaded and uncompressed profile.')
Tiancong Wang6cfc1632019-07-25 21:32:37125 parser.add_argument(
Yuke Liao001b62a52020-04-22 01:23:11126 '-f',
127 '--force',
Tiancong Wang6cfc1632019-07-25 21:32:37128 action='store_true',
Yuke Liao001b62a52020-04-22 01:23:11129 help='Fetch a profile even if the local one is current.')
Tiancong Wang6cfc1632019-07-25 21:32:37130 args = parser.parse_args()
131
132 up_to_date_profile = ReadUpToDateProfileName(args.newest_state)
133 if not args.force:
134 local_profile_name = ReadLocalProfileName(args.local_state)
135 # In a perfect world, the local profile should always exist if we
136 # successfully read local_profile_name. If it's gone, though, the user
137 # probably removed it as a way to get us to download it again.
138 if local_profile_name == up_to_date_profile \
139 and os.path.exists(args.output_name):
140 return 0
141
142 new_tmpfile = args.output_name + '.new'
143 RetrieveProfile(up_to_date_profile, new_tmpfile, args.gs_url_base)
144 os.rename(new_tmpfile, args.output_name)
145 WriteLocalProfileName(up_to_date_profile, args.local_state)
146
147
148if __name__ == '__main__':
149 sys.exit(main())