1 # Copyright 2013 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import ctypes.wintypes 6 import hashlib 7 import json 8 import os 9 import subprocess 10 import sys 11 12 13 BASEDIR = os.path.dirname(os.path.abspath(__file__)) 14 15 16 GetFileAttributes = ctypes.windll.kernel32.GetFileAttributesW 17 GetFileAttributes.argtypes = (ctypes.wintypes.LPWSTR,) 18 GetFileAttributes.restype = ctypes.wintypes.DWORD 19 FILE_ATTRIBUTE_HIDDEN = 0x2 20 FILE_ATTRIBUTE_SYSTEM = 0x4 21 22 23 def IsHidden(file_path): 24 """Returns whether the given |file_path| has the 'system' or 'hidden' 25 attribute set.""" 26 p = GetFileAttributes(file_path) 27 assert p != 0xffffffff 28 return bool(p & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) 29 30 31 def GetFileList(root): 32 """Gets a normalized list of files under |root|.""" 33 assert not os.path.isabs(root) 34 assert os.path.normpath(root) == root 35 file_list = [] 36 for base, _, files in os.walk(root): 37 paths = [os.path.join(base, f) for f in files] 38 file_list.extend(x.lower() for x in paths if not IsHidden(x)) 39 return sorted(file_list) 40 41 42 def MakeTimestampsFileName(root): 43 return os.path.join(root, '..', '.timestamps') 44 45 46 def CalculateHash(root): 47 """Calculates the sha1 of the paths to all files in the given |root| and the 48 contents of those files, and returns as a hex string.""" 49 file_list = GetFileList(root) 50 51 # Check whether we previously saved timestamps in $root/../.timestamps. If 52 # we didn't, or they don't match, then do the full calculation, otherwise 53 # return the saved value. 54 timestamps_file = MakeTimestampsFileName(root) 55 timestamps_data = {'files': [], 'sha1': ''} 56 if os.path.exists(timestamps_file): 57 with open(timestamps_file, 'rb') as f: 58 try: 59 timestamps_data = json.load(f) 60 except ValueError: 61 # json couldn't be loaded, empty data will force a re-hash. 62 pass 63 64 matches = len(file_list) == len(timestamps_data['files']) 65 if matches: 66 for disk, cached in zip(file_list, timestamps_data['files']): 67 if disk != cached[0] or os.stat(disk).st_mtime != cached[1]: 68 matches = False 69 break 70 if matches: 71 return timestamps_data['sha1'] 72 73 digest = hashlib.sha1() 74 for path in file_list: 75 digest.update(path) 76 with open(path, 'rb') as f: 77 digest.update(f.read()) 78 return digest.hexdigest() 79 80 81 def SaveTimestampsAndHash(root, sha1): 82 """Save timestamps and the final hash to be able to early-out more quickly 83 next time.""" 84 file_list = GetFileList(root) 85 timestamps_data = { 86 'files': [[f, os.stat(f).st_mtime] for f in file_list], 87 'sha1': sha1, 88 } 89 with open(MakeTimestampsFileName(root), 'wb') as f: 90 json.dump(timestamps_data, f) 91 92 93 def main(): 94 if sys.platform not in ('win32', 'cygwin'): 95 return 0 96 97 if len(sys.argv) != 1: 98 print >> sys.stderr, 'Unexpected arguments.' 99 return 1 100 101 # Move to same location as .gclient. This is a no-op when run via gclient. 102 os.chdir(os.path.normpath(os.path.join(BASEDIR, '..\\..\\..\\..'))) 103 toolchain_dir = 'src\\third_party\\win_toolchain' 104 target_dir = os.path.join(toolchain_dir, 'files') 105 106 sha1path = os.path.join(toolchain_dir, 'toolchain.sha1') 107 desired_hash = '' 108 if os.path.isfile(sha1path): 109 with open(sha1path, 'rb') as f: 110 desired_hash = f.read().strip() 111 112 # If the current hash doesn't match what we want in the file, nuke and pave. 113 # Typically this script is only run when the .sha1 one file is updated, but 114 # directly calling "gclient runhooks" will also run it, so we cache 115 # based on timestamps to make that case fast. 116 current_hash = CalculateHash(target_dir) 117 if current_hash != desired_hash: 118 print 'Windows toolchain out of date or doesn\'t exist, updating...' 119 if os.path.isdir(target_dir): 120 subprocess.check_call('rmdir /s/q "%s"' % target_dir, shell=True) 121 subprocess.check_call([ 122 sys.executable, 123 'src\\tools\\win\\toolchain\\toolchain2013.py', 124 '--targetdir', target_dir]) 125 current_hash = CalculateHash(target_dir) 126 if current_hash != desired_hash: 127 print >> sys.stderr, ( 128 'Got wrong hash after pulling a new toolchain. ' 129 'Wanted \'%s\', got \'%s\'.' % ( 130 desired_hash, current_hash)) 131 return 1 132 SaveTimestampsAndHash(target_dir, current_hash) 133 134 return 0 135 136 137 if __name__ == '__main__': 138 sys.exit(main()) 139