Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 1 | #!/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 Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 5 | """This script is used to update local profiles (AFDO, PGO or orderfiles) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 6 | |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 7 | This uses profiles of Chrome, or orderfiles for compiling or linking. Though the |
| 8 | profiles are available externally, the bucket they sit in is otherwise |
| 9 | unreadable by non-Googlers. Gsutil usage with this bucket is therefore quite |
| 10 | awkward: you can't do anything but `cp` certain files with an external account, |
| 11 | and you can't even do that if you're not yet authenticated. |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 12 | |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 13 | No authentication is necessary if you pull these profiles directly over https. |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 14 | """ |
| 15 | |
Raul Tambre | f3d9412e | 2019-09-24 05:31:44 | [diff] [blame] | 16 | from __future__ import print_function |
| 17 | |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 18 | import argparse |
| 19 | import contextlib |
| 20 | import os |
| 21 | import subprocess |
| 22 | import sys |
| 23 | import urllib2 |
| 24 | |
| 25 | GS_HTTP_URL = 'https://storage.googleapis.com' |
| 26 | |
| 27 | |
| 28 | def ReadUpToDateProfileName(newest_profile_name_path): |
| 29 | with open(newest_profile_name_path) as f: |
| 30 | return f.read().strip() |
| 31 | |
| 32 | |
| 33 | def 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 | |
| 44 | def WriteLocalProfileName(name, local_profile_name_path): |
| 45 | with open(local_profile_name_path, 'w') as f: |
| 46 | f.write(name) |
| 47 | |
| 48 | |
| 49 | def 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 Tambre | f3d9412e | 2019-09-24 05:31:44 | [diff] [blame] | 62 | print('\n'.join(complaint_lines), file=sys.stderr) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 63 | sys.exit(1) |
| 64 | |
| 65 | |
| 66 | def 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 Liao | 9eeef5e | 2020-04-24 18:59:12 | [diff] [blame] | 70 | 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 Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 77 | gs_prefix = 'gs://' |
| 78 | if not desired_profile_name.startswith(gs_prefix): |
Sebastien Marchand | e893967 | 2020-04-27 17:35:42 | [diff] [blame] | 79 | gs_url = '/'.join([GS_HTTP_URL, gs_url_base, desired_profile_name]) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 80 | else: |
Sebastien Marchand | e893967 | 2020-04-27 17:35:42 | [diff] [blame] | 81 | gs_url = '/'.join([GS_HTTP_URL, desired_profile_name[len(gs_prefix):]]) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 82 | |
| 83 | with contextlib.closing(urllib2.urlopen(gs_url)) as u: |
Yuke Liao | 9eeef5e | 2020-04-24 18:59:12 | [diff] [blame] | 84 | with open(out_path, 'wb') as f: |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 85 | 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 Liao | 9eeef5e | 2020-04-24 18:59:12 | [diff] [blame] | 96 | CheckCallOrExit(['bzip2', '-d', out_path]) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 97 | 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 Liao | 9eeef5e | 2020-04-24 18:59:12 | [diff] [blame] | 100 | CheckCallOrExit(['xz', '-d', out_path]) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 101 | |
| 102 | |
| 103 | def main(): |
| 104 | parser = argparse.ArgumentParser( |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 105 | description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 106 | parser.add_argument( |
| 107 | '--newest_state', |
| 108 | required=True, |
| 109 | help='Path to the file with name of the newest profile. ' |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 110 | 'We use this file to track the name of the newest profile ' |
| 111 | 'we should pull.') |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 112 | parser.add_argument( |
| 113 | '--local_state', |
| 114 | required=True, |
| 115 | help='Path of the file storing name of the local profile. ' |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 116 | 'We use this file to track the most recent profile we\'ve ' |
| 117 | 'successfully pulled.') |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 118 | parser.add_argument( |
| 119 | '--gs_url_base', |
| 120 | required=True, |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 121 | help='The base GS URL to search for the profile.') |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 122 | parser.add_argument( |
| 123 | '--output_name', |
| 124 | required=True, |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 125 | help='Output name of the downloaded and uncompressed profile.') |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 126 | parser.add_argument( |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 127 | '-f', |
| 128 | '--force', |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 129 | action='store_true', |
Yuke Liao | 001b62a5 | 2020-04-22 01:23:11 | [diff] [blame] | 130 | help='Fetch a profile even if the local one is current.') |
Tiancong Wang | 6cfc163 | 2019-07-25 21:32:37 | [diff] [blame] | 131 | 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 | |
| 149 | if __name__ == '__main__': |
| 150 | sys.exit(main()) |