Home | History | Annotate | Download | only in toolchain
      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