Chromium Code Reviews| Index: win_toolchain/get_toolchain_if_necessary.py |
| diff --git a/win_toolchain/get_toolchain_if_necessary.py b/win_toolchain/get_toolchain_if_necessary.py |
| index 07e82729eee15089c6fbe9d90f4d8e99d580a6c9..c6b641c9d91dee54d687626515d412bcc560e579 100755 |
| --- a/win_toolchain/get_toolchain_if_necessary.py |
| +++ b/win_toolchain/get_toolchain_if_necessary.py |
| @@ -78,19 +78,26 @@ def GetFileList(root): |
| return sorted(file_list, key=lambda s: s.replace('/', '\\')) |
| -def MakeTimestampsFileName(root): |
| - return os.path.join(root, '..', '.timestamps') |
| +def MakeTimestampsFileName(root, sha1): |
| + return os.path.join(root, os.pardir, '%s.timestamps' % sha1 if sha1 else '') |
|
scottmg
2016/01/28 23:04:11
Get rid of "if sha1 else ''".
Sébastien Marchand
2016/02/01 19:29:29
Done.
|
| -def CalculateHash(root): |
| +def CalculateHash(root, expected_hash): |
| """Calculates the sha1 of the paths to all files in the given |root| and the |
| - contents of those files, and returns as a hex string.""" |
| - file_list = GetFileList(root) |
| + contents of those files, and returns as a hex string. |
| - # Check whether we previously saved timestamps in $root/../.timestamps. If |
| - # we didn't, or they don't match, then do the full calculation, otherwise |
| + |expected_hash| is the expected hash value for this toolchain if it has |
| + already been installed. |
| + """ |
| + if expected_hash: |
| + full_root_path = os.path.join(root, expected_hash) |
| + else: |
| + full_root_path = root |
| + file_list = GetFileList(full_root_path) |
| + # Check whether we previously saved timestamps in $root/../{sha1}.timestamps. |
| + # If we didn't, or they don't match, then do the full calculation, otherwise |
| # return the saved value. |
| - timestamps_file = MakeTimestampsFileName(root) |
| + timestamps_file = MakeTimestampsFileName(root, expected_hash) |
| timestamps_data = {'files': [], 'sha1': ''} |
| if os.path.exists(timestamps_file): |
| with open(timestamps_file, 'rb') as f: |
| @@ -111,21 +118,38 @@ def CalculateHash(root): |
| digest = hashlib.sha1() |
| for path in file_list: |
| - digest.update(str(path).replace('/', '\\')) |
| + if expected_hash: |
| + path_without_hash = ( |
| + str(path).replace('/', '\\').replace(expected_hash, '').replace( |
|
scottmg
2016/01/28 23:04:11
Can you trim expected_hash only from where you exp
Sébastien Marchand
2016/02/01 19:29:29
Done.
|
| + '\\\\', '\\')) |
|
scottmg
2016/01/28 23:04:11
Where are the double \ coming from?
Sébastien Marchand
2016/02/01 19:29:29
It was because I was just removing {hash} from {ro
|
| + else: |
| + path_without_hash = str(path).replace('/', '\\') |
| + digest.update(path_without_hash) |
| with open(path, 'rb') as f: |
| digest.update(f.read()) |
| return digest.hexdigest() |
| +def CalculateToolchainHashes(root): |
| + """Calculate the hash of the different toolchains installed in the |root| |
| + directory.""" |
| + hashes = [] |
| + dir_list = [ |
| + d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))] |
| + for d in dir_list: |
| + hashes.append(CalculateHash(root, d)) |
| + return hashes |
| + |
| + |
| def SaveTimestampsAndHash(root, sha1): |
| """Saves timestamps and the final hash to be able to early-out more quickly |
| next time.""" |
| - file_list = GetFileList(root) |
| + file_list = GetFileList(os.path.join(root, sha1)) |
| timestamps_data = { |
| 'files': [[f, os.stat(f).st_mtime] for f in file_list], |
| 'sha1': sha1, |
| } |
| - with open(MakeTimestampsFileName(root), 'wb') as f: |
| + with open(MakeTimestampsFileName(root, sha1), 'wb') as f: |
| json.dump(timestamps_data, f) |
| @@ -235,6 +259,63 @@ def DoTreeMirror(target_dir, tree_sha1): |
| RmDir(temp_dir) |
| +def RemoveToolchain(root, sha1, delay_before_removing): |
| + """Remove the |sha1| version of the toolchain from |root|.""" |
| + toolchain_target_dir = os.path.join(root, sha1) |
| + if delay_before_removing: |
| + DelayBeforeRemoving(toolchain_target_dir) |
| + if sys.platform == 'win32': |
| + # These stay resident and will make the rmdir below fail. |
| + kill_list = [ |
| + 'mspdbsrv.exe', |
| + 'vctip.exe', # Compiler and tools experience improvement data uploader. |
| + ] |
| + for process_name in kill_list: |
| + with open(os.devnull, 'wb') as nul: |
| + subprocess.call(['taskkill', '/f', '/im', process_name], |
| + stdin=nul, stdout=nul, stderr=nul) |
| + if os.path.isdir(toolchain_target_dir): |
| + RmDir(toolchain_target_dir) |
| + |
| + timestamp_file = MakeTimestampsFileName(root, sha1) |
| + if os.path.exists(timestamp_file): |
| + os.remove(timestamp_file) |
| + |
| + |
| +def RemoveUnusedToolchains(root): |
| + """Remove the versions of the toolchain that haven't been used recently.""" |
| + valid_toolchains = [] |
| + dirs_to_remove = [] |
| + |
| + for d in os.listdir(root): |
| + full_path = os.path.join(root, d) |
| + if os.path.isdir(full_path): |
| + if not os.path.exists(MakeTimestampsFileName(root, d)): |
| + dirs_to_remove.append(d) |
| + else: |
| + valid_toolchains.append((os.stat(full_path).st_atime, d)) |
|
scottmg
2016/01/28 23:04:11
Hm, won't st_atime update for all of them every ti
Sébastien Marchand
2016/02/01 19:29:29
No, reading a file (via read()) doesn't seem to up
|
| + elif os.path.isfile(full_path): |
| + os.remove(full_path) |
| + |
| + for d in dirs_to_remove: |
| + print ('Removing %s as it doesn\'t correspond to any known toolchain.' % |
| + os.path.join(root, d)) |
| + # Use the RemoveToolchain function to remove these directories as they might |
| + # contain an older version of the toolchain. |
| + RemoveToolchain(root, d, False) |
| + |
| + # Remove the versions of the toolchains that haven't been used in the past 30 |
| + # days. |
| + toolchain_expiration_time = 60 * 60 * 24 * 30 |
|
scottmg
2016/01/28 23:04:11
I'm not sure about this heuristic now. What if a m
Sébastien Marchand
2016/02/01 19:29:29
No, I'm touching the timestamp file of the toolcha
|
| + for toolchain in valid_toolchains: |
| + toolchain_age_in_sec = time.time() - toolchain[0] |
| + if toolchain_age_in_sec > toolchain_expiration_time: |
| + print ('Removing version %s of the Win toolchain has it hasn\'t been used' |
| + ' in the past %d days.' % (toolchain[1], |
| + toolchain_age_in_sec / 60 / 60 / 24)) |
| + RemoveToolchain(root, toolchain[1], True) |
| + |
| + |
| def GetInstallerName(): |
| """Return the name of the Windows 10 Universal C Runtime installer for the |
| current platform, or None if installer is not needed or not applicable. |
| @@ -327,10 +408,9 @@ def main(): |
| sys.exit(subprocess.call(cmd)) |
| assert sys.platform != 'cygwin' |
| - # We assume that the Pro hash is the first one. |
| - desired_hashes = args |
| - if len(desired_hashes) == 0: |
| - sys.exit('Desired hashes are required.') |
| + if len(args) == 0: |
| + sys.exit('Desired hash is required.') |
| + desired_hash = args[0] |
| # Move to depot_tools\win_toolchain where we'll store our files, and where |
| # the downloader script is. |
| @@ -340,7 +420,11 @@ def main(): |
| target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs_files')) |
| else: |
| target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs2013_files')) |
| - abs_target_dir = os.path.abspath(target_dir) |
| + if not os.path.isdir(target_dir): |
| + os.mkdir(target_dir) |
| + toolchain_target_dir = os.path.join(target_dir, desired_hash) |
| + |
| + abs_toolchain_target_dir = os.path.abspath(toolchain_target_dir) |
| got_new_toolchain = False |
| @@ -348,8 +432,8 @@ def main(): |
| # Typically this script is only run when the .sha1 one file is updated, but |
| # directly calling "gclient runhooks" will also run it, so we cache |
| # based on timestamps to make that case fast. |
| - current_hash = CalculateHash(target_dir) |
| - if current_hash not in desired_hashes: |
| + current_hashes = CalculateToolchainHashes(target_dir) |
| + if desired_hash not in current_hashes: |
| should_use_gs = False |
| if (HaveSrcInternalAccess() or |
| LooksLikeGoogler() or |
| @@ -363,68 +447,61 @@ def main(): |
| 'build-instructions-windows\n\n') |
| return 1 |
| print('Windows toolchain out of date or doesn\'t exist, updating (Pro)...') |
| - print(' current_hash: %s' % current_hash) |
| - print(' desired_hashes: %s' % ', '.join(desired_hashes)) |
| + print(' current_hashes: %s' % ', '.join(current_hashes)) |
| + print(' desired_hash: %s' % desired_hash) |
| sys.stdout.flush() |
| - DelayBeforeRemoving(target_dir) |
| - if sys.platform == 'win32': |
| - # These stay resident and will make the rmdir below fail. |
| - kill_list = [ |
| - 'mspdbsrv.exe', |
| - 'vctip.exe', # Compiler and tools experience improvement data uploader. |
| - ] |
| - for process_name in kill_list: |
| - with open(os.devnull, 'wb') as nul: |
| - subprocess.call(['taskkill', '/f', '/im', process_name], |
| - stdin=nul, stdout=nul, stderr=nul) |
| - if os.path.isdir(target_dir): |
| - RmDir(target_dir) |
| - |
| - DoTreeMirror(target_dir, desired_hashes[0]) |
| + |
| + DoTreeMirror(toolchain_target_dir, desired_hash) |
| got_new_toolchain = True |
| + else: |
| + # Touch the timestamp file so we know that this version of the toolchain is |
| + # still used. |
| + os.utime(os.path.join(target_dir, desired_hash), None) |
| - win_sdk = os.path.join(abs_target_dir, 'win_sdk') |
| + win_sdk = os.path.join(abs_toolchain_target_dir, 'win_sdk') |
| try: |
| - with open(os.path.join(target_dir, 'VS_VERSION'), 'rb') as f: |
| + with open(os.path.join(toolchain_target_dir, 'VS_VERSION'), 'rb') as f: |
| vs_version = f.read().strip() |
| except IOError: |
| # Older toolchains didn't have the VS_VERSION file, and used 'win8sdk' |
| # instead of just 'win_sdk'. |
| vs_version = '2013' |
| - win_sdk = os.path.join(abs_target_dir, 'win8sdk') |
| + win_sdk = os.path.join(abs_toolchain_target_dir, 'win8sdk') |
| data = { |
| - 'path': abs_target_dir, |
| + 'path': abs_toolchain_target_dir, |
| 'version': vs_version, |
| 'win_sdk': win_sdk, |
| # Added for backwards compatibility with old toolchain packages. |
| 'win8sdk': win_sdk, |
| - 'wdk': os.path.join(abs_target_dir, 'wdk'), |
| + 'wdk': os.path.join(abs_toolchain_target_dir, 'wdk'), |
| 'runtime_dirs': [ |
| - os.path.join(abs_target_dir, 'sys64'), |
| - os.path.join(abs_target_dir, 'sys32'), |
| + os.path.join(abs_toolchain_target_dir, 'sys64'), |
| + os.path.join(abs_toolchain_target_dir, 'sys32'), |
| ], |
| } |
| with open(os.path.join(target_dir, '..', 'data.json'), 'w') as f: |
| json.dump(data, f) |
| if got_new_toolchain: |
| - current_hash = CalculateHash(target_dir) |
| - if current_hash not in desired_hashes: |
| + current_hashes = CalculateToolchainHashes(target_dir) |
| + if desired_hash not in current_hashes: |
| print >> sys.stderr, ( |
| 'Got wrong hash after pulling a new toolchain. ' |
| - 'Wanted one of \'%s\', got \'%s\'.' % ( |
| - ', '.join(desired_hashes), current_hash)) |
| + 'Wanted \'%s\', got one of \'%s\'.' % ( |
| + desired_hash, ', '.join(current_hashes))) |
| return 1 |
| - SaveTimestampsAndHash(target_dir, current_hash) |
| + SaveTimestampsAndHash(target_dir, desired_hash) |
| if options.output_json: |
| shutil.copyfile(os.path.join(target_dir, '..', 'data.json'), |
| options.output_json) |
| if os.environ.get('GYP_MSVS_VERSION') == '2015': |
| - InstallUniversalCRTIfNeeded(abs_target_dir) |
| + InstallUniversalCRTIfNeeded(abs_toolchain_target_dir) |
| + |
| + RemoveUnusedToolchains(target_dir) |
| return 0 |