| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Downloads and unpacks a toolchain for building on Windows. The contents are | 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. | 7 matched by sha1 which will be updated when the toolchain is updated. |
| 8 | 8 |
| 9 Having a toolchain script in depot_tools means that it's not versioned | 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 | 10 directly with the source code. That is, if the toolchain is upgraded, but |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 assert os.path.normpath(root) == root | 73 assert os.path.normpath(root) == root |
| 74 file_list = [] | 74 file_list = [] |
| 75 for base, _, files in os.walk(root): | 75 for base, _, files in os.walk(root): |
| 76 paths = [os.path.join(base, f) for f in files] | 76 paths = [os.path.join(base, f) for f in files] |
| 77 # Ignore WER ReportQueue entries that vctip/cl leave in the bin dir if/when | 77 # Ignore WER ReportQueue entries that vctip/cl leave in the bin dir if/when |
| 78 # they crash. | 78 # they crash. |
| 79 file_list.extend(x.lower() for x in paths if 'WER\\ReportQueue' not in x) | 79 file_list.extend(x.lower() for x in paths if 'WER\\ReportQueue' not in x) |
| 80 return sorted(file_list, key=lambda s: s.replace('/', '\\')) | 80 return sorted(file_list, key=lambda s: s.replace('/', '\\')) |
| 81 | 81 |
| 82 | 82 |
| 83 def MakeTimestampsFileName(root, sha1): | 83 def MakeTimestampsFileName(root): |
| 84 return os.path.join(root, os.pardir, '%s.timestamps' % sha1) | 84 return os.path.join(root, '..', '.timestamps') |
| 85 | 85 |
| 86 | 86 |
| 87 def CalculateHash(root, expected_hash): | 87 def CalculateHash(root): |
| 88 """Calculates the sha1 of the paths to all files in the given |root| and the | 88 """Calculates the sha1 of the paths to all files in the given |root| and the |
| 89 contents of those files, and returns as a hex string. | 89 contents of those files, and returns as a hex string.""" |
| 90 file_list = GetFileList(root) |
| 90 | 91 |
| 91 |expected_hash| is the expected hash value for this toolchain if it has | 92 # Check whether we previously saved timestamps in $root/../.timestamps. If |
| 92 already been installed. | 93 # we didn't, or they don't match, then do the full calculation, otherwise |
| 93 """ | |
| 94 if expected_hash: | |
| 95 full_root_path = os.path.join(root, expected_hash) | |
| 96 else: | |
| 97 full_root_path = root | |
| 98 file_list = GetFileList(full_root_path) | |
| 99 # Check whether we previously saved timestamps in $root/../{sha1}.timestamps. | |
| 100 # If we didn't, or they don't match, then do the full calculation, otherwise | |
| 101 # return the saved value. | 94 # return the saved value. |
| 102 timestamps_file = MakeTimestampsFileName(root, expected_hash) | 95 timestamps_file = MakeTimestampsFileName(root) |
| 103 timestamps_data = {'files': [], 'sha1': ''} | 96 timestamps_data = {'files': [], 'sha1': ''} |
| 104 if os.path.exists(timestamps_file): | 97 if os.path.exists(timestamps_file): |
| 105 with open(timestamps_file, 'rb') as f: | 98 with open(timestamps_file, 'rb') as f: |
| 106 try: | 99 try: |
| 107 timestamps_data = json.load(f) | 100 timestamps_data = json.load(f) |
| 108 except ValueError: | 101 except ValueError: |
| 109 # json couldn't be loaded, empty data will force a re-hash. | 102 # json couldn't be loaded, empty data will force a re-hash. |
| 110 pass | 103 pass |
| 111 | 104 |
| 112 matches = len(file_list) == len(timestamps_data['files']) | 105 matches = len(file_list) == len(timestamps_data['files']) |
| 113 # Don't check the timestamp of the version file as we touch this file to | |
| 114 # indicates which versions of the toolchain are still being used. | |
| 115 vc_dir = os.path.join(full_root_path, 'VC').lower() | |
| 116 if matches: | 106 if matches: |
| 117 for disk, cached in zip(file_list, timestamps_data['files']): | 107 for disk, cached in zip(file_list, timestamps_data['files']): |
| 118 if disk != cached[0] or ( | 108 if disk != cached[0] or os.stat(disk).st_mtime != cached[1]: |
| 119 disk != vc_dir and os.path.getmtime(disk) != cached[1]): | |
| 120 matches = False | 109 matches = False |
| 121 break | 110 break |
| 122 if matches: | 111 if matches: |
| 123 return timestamps_data['sha1'] | 112 return timestamps_data['sha1'] |
| 124 | 113 |
| 125 digest = hashlib.sha1() | 114 digest = hashlib.sha1() |
| 126 for path in file_list: | 115 for path in file_list: |
| 127 path_without_hash = str(path).replace('/', '\\') | 116 digest.update(str(path).replace('/', '\\')) |
| 128 if expected_hash: | |
| 129 path_without_hash = path_without_hash.replace( | |
| 130 os.path.join(root, expected_hash), root) | |
| 131 digest.update(path_without_hash) | |
| 132 with open(path, 'rb') as f: | 117 with open(path, 'rb') as f: |
| 133 digest.update(f.read()) | 118 digest.update(f.read()) |
| 134 return digest.hexdigest() | 119 return digest.hexdigest() |
| 135 | 120 |
| 136 | 121 |
| 137 def CalculateToolchainHashes(root): | |
| 138 """Calculate the hash of the different toolchains installed in the |root| | |
| 139 directory.""" | |
| 140 hashes = [] | |
| 141 dir_list = [ | |
| 142 d for d in os.listdir(root) if os.path.isdir(os.path.join(root, d))] | |
| 143 for d in dir_list: | |
| 144 hashes.append(CalculateHash(root, d)) | |
| 145 return hashes | |
| 146 | |
| 147 | |
| 148 def SaveTimestampsAndHash(root, sha1): | 122 def SaveTimestampsAndHash(root, sha1): |
| 149 """Saves timestamps and the final hash to be able to early-out more quickly | 123 """Saves timestamps and the final hash to be able to early-out more quickly |
| 150 next time.""" | 124 next time.""" |
| 151 file_list = GetFileList(os.path.join(root, sha1)) | 125 file_list = GetFileList(root) |
| 152 timestamps_data = { | 126 timestamps_data = { |
| 153 'files': [[f, os.path.getmtime(f)] for f in file_list], | 127 'files': [[f, os.stat(f).st_mtime] for f in file_list], |
| 154 'sha1': sha1, | 128 'sha1': sha1, |
| 155 } | 129 } |
| 156 with open(MakeTimestampsFileName(root, sha1), 'wb') as f: | 130 with open(MakeTimestampsFileName(root), 'wb') as f: |
| 157 json.dump(timestamps_data, f) | 131 json.dump(timestamps_data, f) |
| 158 | 132 |
| 159 | 133 |
| 160 def HaveSrcInternalAccess(): | 134 def HaveSrcInternalAccess(): |
| 161 """Checks whether access to src-internal is available.""" | 135 """Checks whether access to src-internal is available.""" |
| 162 with open(os.devnull, 'w') as nul: | 136 with open(os.devnull, 'w') as nul: |
| 163 if subprocess.call( | 137 if subprocess.call( |
| 164 ['svn', 'ls', '--non-interactive', | 138 ['svn', 'ls', '--non-interactive', |
| 165 'svn://svn.chromium.org/chrome-internal/trunk/src-internal/'], | 139 'svn://svn.chromium.org/chrome-internal/trunk/src-internal/'], |
| 166 shell=True, stdin=nul, stdout=nul, stderr=nul) == 0: | 140 shell=True, stdin=nul, stdout=nul, stderr=nul) == 0: |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 256 else: | 230 else: |
| 257 temp_dir, local_zip = DownloadUsingGsutil(tree_sha1 + '.zip') | 231 temp_dir, local_zip = DownloadUsingGsutil(tree_sha1 + '.zip') |
| 258 sys.stdout.write('Extracting %s...\n' % local_zip) | 232 sys.stdout.write('Extracting %s...\n' % local_zip) |
| 259 sys.stdout.flush() | 233 sys.stdout.flush() |
| 260 with zipfile.ZipFile(local_zip, 'r', zipfile.ZIP_DEFLATED, True) as zf: | 234 with zipfile.ZipFile(local_zip, 'r', zipfile.ZIP_DEFLATED, True) as zf: |
| 261 zf.extractall(target_dir) | 235 zf.extractall(target_dir) |
| 262 if temp_dir: | 236 if temp_dir: |
| 263 RmDir(temp_dir) | 237 RmDir(temp_dir) |
| 264 | 238 |
| 265 | 239 |
| 266 def RemoveToolchain(root, sha1, delay_before_removing): | |
| 267 """Remove the |sha1| version of the toolchain from |root|.""" | |
| 268 toolchain_target_dir = os.path.join(root, sha1) | |
| 269 if delay_before_removing: | |
| 270 DelayBeforeRemoving(toolchain_target_dir) | |
| 271 if sys.platform == 'win32': | |
| 272 # These stay resident and will make the rmdir below fail. | |
| 273 kill_list = [ | |
| 274 'mspdbsrv.exe', | |
| 275 'vctip.exe', # Compiler and tools experience improvement data uploader. | |
| 276 ] | |
| 277 for process_name in kill_list: | |
| 278 with open(os.devnull, 'wb') as nul: | |
| 279 subprocess.call(['taskkill', '/f', '/im', process_name], | |
| 280 stdin=nul, stdout=nul, stderr=nul) | |
| 281 if os.path.isdir(toolchain_target_dir): | |
| 282 RmDir(toolchain_target_dir) | |
| 283 | |
| 284 timestamp_file = MakeTimestampsFileName(root, sha1) | |
| 285 if os.path.exists(timestamp_file): | |
| 286 os.remove(timestamp_file) | |
| 287 | |
| 288 | |
| 289 def RemoveUnusedToolchains(root): | |
| 290 """Remove the versions of the toolchain that haven't been used recently.""" | |
| 291 valid_toolchains = [] | |
| 292 dirs_to_remove = [] | |
| 293 | |
| 294 for d in os.listdir(root): | |
| 295 full_path = os.path.join(root, d) | |
| 296 if os.path.isdir(full_path): | |
| 297 if not os.path.exists(MakeTimestampsFileName(root, d)): | |
| 298 dirs_to_remove.append(d) | |
| 299 else: | |
| 300 vc_dir = os.path.join(full_path, 'VC') | |
| 301 valid_toolchains.append((os.path.getmtime(vc_dir), d)) | |
| 302 elif os.path.isfile(full_path): | |
| 303 os.remove(full_path) | |
| 304 | |
| 305 for d in dirs_to_remove: | |
| 306 print ('Removing %s as it doesn\'t correspond to any known toolchain.' % | |
| 307 os.path.join(root, d)) | |
| 308 # Use the RemoveToolchain function to remove these directories as they might | |
| 309 # contain an older version of the toolchain. | |
| 310 RemoveToolchain(root, d, False) | |
| 311 | |
| 312 # Remove the versions of the toolchains that haven't been used in the past 30 | |
| 313 # days. | |
| 314 toolchain_expiration_time = 60 * 60 * 24 * 30 | |
| 315 for toolchain in valid_toolchains: | |
| 316 toolchain_age_in_sec = time.time() - toolchain[0] | |
| 317 if toolchain_age_in_sec > toolchain_expiration_time: | |
| 318 print ('Removing version %s of the Win toolchain has it hasn\'t been used' | |
| 319 ' in the past %d days.' % (toolchain[1], | |
| 320 toolchain_age_in_sec / 60 / 60 / 24)) | |
| 321 RemoveToolchain(root, toolchain[1], True) | |
| 322 | |
| 323 | |
| 324 def GetInstallerName(): | 240 def GetInstallerName(): |
| 325 """Return the name of the Windows 10 Universal C Runtime installer for the | 241 """Return the name of the Windows 10 Universal C Runtime installer for the |
| 326 current platform, or None if installer is not needed or not applicable. | 242 current platform, or None if installer is not needed or not applicable. |
| 327 The registry has to be used instead of sys.getwindowsversion() because | 243 The registry has to be used instead of sys.getwindowsversion() because |
| 328 Python 2.7 is only manifested as being compatible up to Windows 8, so the | 244 Python 2.7 is only manifested as being compatible up to Windows 8, so the |
| 329 version APIs helpfully return a maximum of 6.2 (Windows 8). | 245 version APIs helpfully return a maximum of 6.2 (Windows 8). |
| 330 """ | 246 """ |
| 331 key_name = r'Software\Microsoft\Windows NT\CurrentVersion' | 247 key_name = r'Software\Microsoft\Windows NT\CurrentVersion' |
| 332 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_name) | 248 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_name) |
| 333 value, keytype = winreg.QueryValueEx(key, "CurrentVersion") | 249 value, keytype = winreg.QueryValueEx(key, "CurrentVersion") |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 406 def winpath(path): | 322 def winpath(path): |
| 407 return subprocess.check_output(['cygpath', '-w', path]).strip() | 323 return subprocess.check_output(['cygpath', '-w', path]).strip() |
| 408 python = os.path.join(DEPOT_TOOLS_PATH, 'python.bat') | 324 python = os.path.join(DEPOT_TOOLS_PATH, 'python.bat') |
| 409 cmd = [python, winpath(__file__)] | 325 cmd = [python, winpath(__file__)] |
| 410 if options.output_json: | 326 if options.output_json: |
| 411 cmd.extend(['--output-json', winpath(options.output_json)]) | 327 cmd.extend(['--output-json', winpath(options.output_json)]) |
| 412 cmd.extend(args) | 328 cmd.extend(args) |
| 413 sys.exit(subprocess.call(cmd)) | 329 sys.exit(subprocess.call(cmd)) |
| 414 assert sys.platform != 'cygwin' | 330 assert sys.platform != 'cygwin' |
| 415 | 331 |
| 416 if len(args) == 0: | 332 # We assume that the Pro hash is the first one. |
| 417 sys.exit('Desired hash is required.') | 333 desired_hashes = args |
| 418 desired_hash = args[0] | 334 if len(desired_hashes) == 0: |
| 335 sys.exit('Desired hashes are required.') |
| 419 | 336 |
| 420 # Move to depot_tools\win_toolchain where we'll store our files, and where | 337 # Move to depot_tools\win_toolchain where we'll store our files, and where |
| 421 # the downloader script is. | 338 # the downloader script is. |
| 422 os.chdir(os.path.normpath(os.path.join(BASEDIR))) | 339 os.chdir(os.path.normpath(os.path.join(BASEDIR))) |
| 423 toolchain_dir = '.' | 340 toolchain_dir = '.' |
| 424 if os.environ.get('GYP_MSVS_VERSION') == '2015': | 341 if os.environ.get('GYP_MSVS_VERSION') == '2015': |
| 425 target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs_files')) | 342 target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs_files')) |
| 426 else: | 343 else: |
| 427 target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs2013_files')) | 344 target_dir = os.path.normpath(os.path.join(toolchain_dir, 'vs2013_files')) |
| 428 if not os.path.isdir(target_dir): | 345 abs_target_dir = os.path.abspath(target_dir) |
| 429 os.mkdir(target_dir) | |
| 430 toolchain_target_dir = os.path.join(target_dir, desired_hash) | |
| 431 | |
| 432 abs_toolchain_target_dir = os.path.abspath(toolchain_target_dir) | |
| 433 | 346 |
| 434 got_new_toolchain = False | 347 got_new_toolchain = False |
| 435 | 348 |
| 436 # If the current hash doesn't match what we want in the file, nuke and pave. | 349 # If the current hash doesn't match what we want in the file, nuke and pave. |
| 437 # Typically this script is only run when the .sha1 one file is updated, but | 350 # Typically this script is only run when the .sha1 one file is updated, but |
| 438 # directly calling "gclient runhooks" will also run it, so we cache | 351 # directly calling "gclient runhooks" will also run it, so we cache |
| 439 # based on timestamps to make that case fast. | 352 # based on timestamps to make that case fast. |
| 440 current_hashes = CalculateToolchainHashes(target_dir) | 353 current_hash = CalculateHash(target_dir) |
| 441 if desired_hash not in current_hashes: | 354 if current_hash not in desired_hashes: |
| 442 should_use_gs = False | 355 should_use_gs = False |
| 443 if (HaveSrcInternalAccess() or | 356 if (HaveSrcInternalAccess() or |
| 444 LooksLikeGoogler() or | 357 LooksLikeGoogler() or |
| 445 CanAccessToolchainBucket()): | 358 CanAccessToolchainBucket()): |
| 446 should_use_gs = True | 359 should_use_gs = True |
| 447 if not CanAccessToolchainBucket(): | 360 if not CanAccessToolchainBucket(): |
| 448 RequestGsAuthentication() | 361 RequestGsAuthentication() |
| 449 if not should_use_gs: | 362 if not should_use_gs: |
| 450 print('\n\n\nPlease follow the instructions at ' | 363 print('\n\n\nPlease follow the instructions at ' |
| 451 'https://www.chromium.org/developers/how-tos/' | 364 'https://www.chromium.org/developers/how-tos/' |
| 452 'build-instructions-windows\n\n') | 365 'build-instructions-windows\n\n') |
| 453 return 1 | 366 return 1 |
| 454 print('Windows toolchain out of date or doesn\'t exist, updating (Pro)...') | 367 print('Windows toolchain out of date or doesn\'t exist, updating (Pro)...') |
| 455 print(' current_hashes: %s' % ', '.join(current_hashes)) | 368 print(' current_hash: %s' % current_hash) |
| 456 print(' desired_hash: %s' % desired_hash) | 369 print(' desired_hashes: %s' % ', '.join(desired_hashes)) |
| 457 sys.stdout.flush() | 370 sys.stdout.flush() |
| 371 DelayBeforeRemoving(target_dir) |
| 372 if sys.platform == 'win32': |
| 373 # These stay resident and will make the rmdir below fail. |
| 374 kill_list = [ |
| 375 'mspdbsrv.exe', |
| 376 'vctip.exe', # Compiler and tools experience improvement data uploader. |
| 377 ] |
| 378 for process_name in kill_list: |
| 379 with open(os.devnull, 'wb') as nul: |
| 380 subprocess.call(['taskkill', '/f', '/im', process_name], |
| 381 stdin=nul, stdout=nul, stderr=nul) |
| 382 if os.path.isdir(target_dir): |
| 383 RmDir(target_dir) |
| 458 | 384 |
| 459 DoTreeMirror(toolchain_target_dir, desired_hash) | 385 DoTreeMirror(target_dir, desired_hashes[0]) |
| 460 | 386 |
| 461 got_new_toolchain = True | 387 got_new_toolchain = True |
| 462 | 388 |
| 463 win_sdk = os.path.join(abs_toolchain_target_dir, 'win_sdk') | 389 win_sdk = os.path.join(abs_target_dir, 'win_sdk') |
| 464 try: | 390 try: |
| 465 version_file = os.path.join(toolchain_target_dir, 'VS_VERSION') | 391 with open(os.path.join(target_dir, 'VS_VERSION'), 'rb') as f: |
| 466 vc_dir = os.path.join(toolchain_target_dir, 'VC') | |
| 467 with open(version_file, 'rb') as f: | |
| 468 vs_version = f.read().strip() | 392 vs_version = f.read().strip() |
| 469 # Touch the VC directory so we can use its timestamp to know when this | |
| 470 # version of the toolchain has been used for the last time. | |
| 471 os.utime(vc_dir, None) | |
| 472 except IOError: | 393 except IOError: |
| 473 # Older toolchains didn't have the VS_VERSION file, and used 'win8sdk' | 394 # Older toolchains didn't have the VS_VERSION file, and used 'win8sdk' |
| 474 # instead of just 'win_sdk'. | 395 # instead of just 'win_sdk'. |
| 475 vs_version = '2013' | 396 vs_version = '2013' |
| 476 win_sdk = os.path.join(abs_toolchain_target_dir, 'win8sdk') | 397 win_sdk = os.path.join(abs_target_dir, 'win8sdk') |
| 477 | 398 |
| 478 data = { | 399 data = { |
| 479 'path': abs_toolchain_target_dir, | 400 'path': abs_target_dir, |
| 480 'version': vs_version, | 401 'version': vs_version, |
| 481 'win_sdk': win_sdk, | 402 'win_sdk': win_sdk, |
| 482 # Added for backwards compatibility with old toolchain packages. | 403 # Added for backwards compatibility with old toolchain packages. |
| 483 'win8sdk': win_sdk, | 404 'win8sdk': win_sdk, |
| 484 'wdk': os.path.join(abs_toolchain_target_dir, 'wdk'), | 405 'wdk': os.path.join(abs_target_dir, 'wdk'), |
| 485 'runtime_dirs': [ | 406 'runtime_dirs': [ |
| 486 os.path.join(abs_toolchain_target_dir, 'sys64'), | 407 os.path.join(abs_target_dir, 'sys64'), |
| 487 os.path.join(abs_toolchain_target_dir, 'sys32'), | 408 os.path.join(abs_target_dir, 'sys32'), |
| 488 ], | 409 ], |
| 489 } | 410 } |
| 490 with open(os.path.join(target_dir, '..', 'data.json'), 'w') as f: | 411 with open(os.path.join(target_dir, '..', 'data.json'), 'w') as f: |
| 491 json.dump(data, f) | 412 json.dump(data, f) |
| 492 | 413 |
| 493 if got_new_toolchain: | 414 if got_new_toolchain: |
| 494 current_hashes = CalculateToolchainHashes(target_dir) | 415 current_hash = CalculateHash(target_dir) |
| 495 if desired_hash not in current_hashes: | 416 if current_hash not in desired_hashes: |
| 496 print >> sys.stderr, ( | 417 print >> sys.stderr, ( |
| 497 'Got wrong hash after pulling a new toolchain. ' | 418 'Got wrong hash after pulling a new toolchain. ' |
| 498 'Wanted \'%s\', got one of \'%s\'.' % ( | 419 'Wanted one of \'%s\', got \'%s\'.' % ( |
| 499 desired_hash, ', '.join(current_hashes))) | 420 ', '.join(desired_hashes), current_hash)) |
| 500 return 1 | 421 return 1 |
| 501 SaveTimestampsAndHash(target_dir, desired_hash) | 422 SaveTimestampsAndHash(target_dir, current_hash) |
| 502 | 423 |
| 503 if options.output_json: | 424 if options.output_json: |
| 504 shutil.copyfile(os.path.join(target_dir, '..', 'data.json'), | 425 shutil.copyfile(os.path.join(target_dir, '..', 'data.json'), |
| 505 options.output_json) | 426 options.output_json) |
| 506 | 427 |
| 507 if os.environ.get('GYP_MSVS_VERSION') == '2015': | 428 if os.environ.get('GYP_MSVS_VERSION') == '2015': |
| 508 InstallUniversalCRTIfNeeded(abs_toolchain_target_dir) | 429 InstallUniversalCRTIfNeeded(abs_target_dir) |
| 509 | |
| 510 RemoveUnusedToolchains(target_dir) | |
| 511 | 430 |
| 512 return 0 | 431 return 0 |
| 513 | 432 |
| 514 | 433 |
| 515 if __name__ == '__main__': | 434 if __name__ == '__main__': |
| 516 sys.exit(main()) | 435 sys.exit(main()) |
| OLD | NEW |