Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2013 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. | |
| 5 | |
| 6 """Downloads and unpacks a toolchain for building on Windows. The contents are | |
| 7 matched by sha1 which will be updated when the toolchain is updated. | |
| 8 | |
| 9 Having a toolchain script in depot_tools means that it's not versioned | |
| 10 directly with the source code. That is, if the toolchain is upgraded, but | |
| 11 you're trying to build an historical version of Chromium from before the | |
| 12 toolchain upgrade, this will cause you to build with a newer toolchain than | |
| 13 was available when that code was committed. This is done for a two main | |
| 14 reasons: 1) it would likely be annoying to have the up-to-date toolchain | |
| 15 removed and replaced by one without a service pack applied); 2) it would | |
| 16 require maintaining scripts that can build older not-up-to-date revisions of | |
| 17 the toolchain. This is likely to be a poorly tested code path that probably | |
| 18 won't be properly maintained. See http://crbug.com/323300. | |
| 19 | |
| 20 This does not extend to major versions of the toolchain however, on the | |
| 21 assumption that there are more likely to be source incompatibilities between | |
| 22 major revisions. This script calls a subscript (currently, toolchain2013.py) | |
| 23 to do the main work. It is expected that toolchain2013.py will always be able | |
| 24 to acquire/build the most current revision of a VS2013-based toolchain. In the | |
| 25 future when a hypothetical VS2015 is released, the 2013 script will be | |
| 26 maintained, and a new 2015 script would be added. | |
| 27 """ | |
| 28 | |
| 29 import ctypes.wintypes | |
| 30 import hashlib | |
| 31 import json | |
| 32 import os | |
| 33 import subprocess | |
| 34 import sys | |
| 35 | |
| 36 | |
| 37 BASEDIR = os.path.dirname(os.path.abspath(__file__)) | |
| 38 | |
| 39 | |
| 40 GetFileAttributes = ctypes.windll.kernel32.GetFileAttributesW | |
| 41 GetFileAttributes.argtypes = (ctypes.wintypes.LPWSTR,) | |
| 42 GetFileAttributes.restype = ctypes.wintypes.DWORD | |
| 43 FILE_ATTRIBUTE_HIDDEN = 0x2 | |
| 44 FILE_ATTRIBUTE_SYSTEM = 0x4 | |
| 45 | |
| 46 | |
| 47 def IsHidden(file_path): | |
| 48 """Returns whether the given |file_path| has the 'system' or 'hidden' | |
| 49 attribute set.""" | |
| 50 p = GetFileAttributes(file_path) | |
| 51 assert p != 0xffffffff | |
|
iannucci
2014/01/14 10:14:11
Is this the same as (p != -1)?
scottmg
2014/01/14 17:09:35
I would assume not in python?
iannucci
2014/01/15 21:55:26
Well... it depends if GetFileAttributes is conside
| |
| 52 return bool(p & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) | |
| 53 | |
| 54 | |
| 55 def GetFileList(root): | |
| 56 """Gets a normalized list of files under |root|.""" | |
| 57 assert not os.path.isabs(root) | |
| 58 assert os.path.normpath(root) == root | |
| 59 file_list = [] | |
| 60 for base, _, files in os.walk(root): | |
| 61 paths = [os.path.join(base, f) for f in files] | |
| 62 file_list.extend(x.lower() for x in paths if not IsHidden(x)) | |
| 63 return sorted(file_list) | |
| 64 | |
| 65 | |
| 66 def MakeTimestampsFileName(root): | |
| 67 return os.path.join(root, '..', '.timestamps') | |
| 68 | |
| 69 | |
| 70 def CalculateHash(root): | |
|
iannucci
2014/01/14 10:14:11
We may want to (eventually) integrate this with de
scottmg
2014/01/14 17:09:35
OK.
I don't think we can mirror VS for non-Google
iannucci
2014/01/15 21:55:26
Hm that /is/ unfortunate... We could maybe put it
| |
| 71 """Calculates the sha1 of the paths to all files in the given |root| and the | |
| 72 contents of those files, and returns as a hex string.""" | |
| 73 file_list = GetFileList(root) | |
| 74 | |
| 75 # Check whether we previously saved timestamps in $root/../.timestamps. If | |
| 76 # we didn't, or they don't match, then do the full calculation, otherwise | |
| 77 # return the saved value. | |
| 78 timestamps_file = MakeTimestampsFileName(root) | |
| 79 timestamps_data = {'files': [], 'sha1': ''} | |
| 80 if os.path.exists(timestamps_file): | |
| 81 with open(timestamps_file, 'rb') as f: | |
| 82 try: | |
| 83 timestamps_data = json.load(f) | |
| 84 except ValueError: | |
| 85 # json couldn't be loaded, empty data will force a re-hash. | |
| 86 pass | |
| 87 | |
| 88 matches = len(file_list) == len(timestamps_data['files']) | |
| 89 if matches: | |
| 90 for disk, cached in zip(file_list, timestamps_data['files']): | |
| 91 if disk != cached[0] or os.stat(disk).st_mtime != cached[1]: | |
| 92 matches = False | |
| 93 break | |
| 94 if matches: | |
| 95 return timestamps_data['sha1'] | |
| 96 | |
| 97 digest = hashlib.sha1() | |
| 98 for path in file_list: | |
| 99 digest.update(path) | |
| 100 with open(path, 'rb') as f: | |
| 101 digest.update(f.read()) | |
| 102 return digest.hexdigest() | |
| 103 | |
| 104 | |
| 105 def SaveTimestampsAndHash(root, sha1): | |
| 106 """Save timestamps and the final hash to be able to early-out more quickly | |
| 107 next time.""" | |
| 108 file_list = GetFileList(root) | |
| 109 timestamps_data = { | |
| 110 'files': [[f, os.stat(f).st_mtime] for f in file_list], | |
| 111 'sha1': sha1, | |
| 112 } | |
| 113 with open(MakeTimestampsFileName(root), 'wb') as f: | |
| 114 json.dump(timestamps_data, f) | |
| 115 | |
| 116 | |
| 117 def main(): | |
| 118 if sys.platform not in ('cygwin', 'win32'): | |
|
iannucci
2014/01/14 10:14:11
this should probably be `not sys.platform.startswi
scottmg
2014/01/14 17:09:35
Done.
| |
| 119 return 0 | |
| 120 | |
| 121 if len(sys.argv) != 1: | |
| 122 print >> sys.stderr, 'Unexpected arguments.' | |
| 123 return 1 | |
| 124 | |
| 125 # Move to depot_tools\win_toolchain where we'll store our files, and where | |
| 126 # the downloader script is. | |
| 127 os.chdir(os.path.normpath(os.path.join(BASEDIR))) | |
| 128 toolchain_dir = '.' | |
| 129 target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs2013_files')) | |
| 130 | |
| 131 sha1path = os.path.join(toolchain_dir, 'toolchain_vs2013.sha1') | |
| 132 desired_hash = '' | |
| 133 if os.path.isfile(sha1path): | |
| 134 with open(sha1path, 'rb') as f: | |
| 135 desired_hash = f.read().strip() | |
| 136 | |
| 137 # If the current hash doesn't match what we want in the file, nuke and pave. | |
| 138 # Typically this script is only run when the .sha1 one file is updated, but | |
| 139 # directly calling "gclient runhooks" will also run it, so we cache | |
| 140 # based on timestamps to make that case fast. | |
| 141 current_hash = CalculateHash(target_dir) | |
| 142 if current_hash != desired_hash: | |
| 143 print 'Windows toolchain out of date or doesn\'t exist, updating...' | |
| 144 if os.path.isdir(target_dir): | |
| 145 subprocess.check_call('rmdir /s/q "%s"' % target_dir, shell=True) | |
|
iannucci
2014/01/14 10:14:11
[Ew... I know this is win-only and all, but can't
scottmg
2014/01/14 17:09:36
shutil.rmtree sucks; it aborts if there's any file
iannucci
2014/01/15 21:55:26
*sigh* why is everything terrible?
| |
| 146 subprocess.check_call([ | |
| 147 sys.executable, | |
| 148 'toolchain2013.py', | |
| 149 '--targetdir', target_dir]) | |
| 150 current_hash = CalculateHash(target_dir) | |
| 151 if current_hash != desired_hash: | |
| 152 print >> sys.stderr, ( | |
| 153 'Got wrong hash after pulling a new toolchain. ' | |
| 154 'Wanted \'%s\', got \'%s\'.' % ( | |
| 155 desired_hash, current_hash)) | |
| 156 return 1 | |
| 157 SaveTimestampsAndHash(target_dir, current_hash) | |
| 158 | |
| 159 return 0 | |
| 160 | |
| 161 | |
| 162 if __name__ == '__main__': | |
| 163 sys.exit(main()) | |
| OLD | NEW |