1#!/usr/bin/env python 2# Copyright 2014 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 6from __future__ import print_function 7 8import collections 9import glob 10import json 11import os 12import pipes 13import platform 14import re 15import shutil 16import stat 17import subprocess 18import sys 19 20from gn_helpers import ToGNString 21 22 23script_dir = os.path.dirname(os.path.realpath(__file__)) 24json_data_file = os.path.join(script_dir, 'win_toolchain.json') 25 26# VS versions are listed in descending order of priority (highest first). 27MSVS_VERSIONS = collections.OrderedDict([ 28 ('2019', '16.0'), 29 ('2017', '15.0'), 30]) 31 32# List of preferred VC toolset version based on MSVS 33MSVC_TOOLSET_VERSION = { 34 '2019' : 'VC142', 35 '2017' : 'VC141', 36} 37 38def _HostIsWindows(): 39 """Returns True if running on a Windows host (including under cygwin).""" 40 return sys.platform in ('win32', 'cygwin') 41 42def SetEnvironmentAndGetRuntimeDllDirs(): 43 """Sets up os.environ to use the depot_tools VS toolchain with gyp, and 44 returns the location of the VC runtime DLLs so they can be copied into 45 the output directory after gyp generation. 46 47 Return value is [x64path, x86path, 'Arm64Unused'] or None. arm64path is 48 generated separately because there are multiple folders for the arm64 VC 49 runtime. 50 """ 51 vs_runtime_dll_dirs = None 52 depot_tools_win_toolchain = \ 53 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) 54 # When running on a non-Windows host, only do this if the SDK has explicitly 55 # been downloaded before (in which case json_data_file will exist). 56 if ((_HostIsWindows() or os.path.exists(json_data_file)) 57 and depot_tools_win_toolchain): 58 if ShouldUpdateToolchain(): 59 if len(sys.argv) > 1 and sys.argv[1] == 'update': 60 update_result = Update() 61 else: 62 update_result = Update(no_download=True) 63 if update_result != 0: 64 raise Exception('Failed to update, error code %d.' % update_result) 65 with open(json_data_file, 'r') as tempf: 66 toolchain_data = json.load(tempf) 67 68 toolchain = toolchain_data['path'] 69 version = toolchain_data['version'] 70 win_sdk = toolchain_data.get('win_sdk') 71 if not win_sdk: 72 win_sdk = toolchain_data['win8sdk'] 73 wdk = toolchain_data['wdk'] 74 # TODO(scottmg): The order unfortunately matters in these. They should be 75 # split into separate keys for x64/x86/arm64. (See CopyDlls call below). 76 # http://crbug.com/345992 77 vs_runtime_dll_dirs = toolchain_data['runtime_dirs'] 78 # The number of runtime_dirs in the toolchain_data was two (x64/x86) but 79 # changed to three (x64/x86/arm64) and this code needs to handle both 80 # possibilities, which can change independently from this code. 81 if len(vs_runtime_dll_dirs) == 2: 82 vs_runtime_dll_dirs.append('Arm64Unused') 83 84 os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain 85 86 os.environ['WINDOWSSDKDIR'] = win_sdk 87 os.environ['WDK_DIR'] = wdk 88 # Include the VS runtime in the PATH in case it's not machine-installed. 89 runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs) 90 os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH'] 91 elif sys.platform == 'win32' and not depot_tools_win_toolchain: 92 if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ: 93 os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath() 94 95 # When using an installed toolchain these files aren't needed in the output 96 # directory in order to run binaries locally, but they are needed in order 97 # to create isolates or the mini_installer. Copying them to the output 98 # directory ensures that they are available when needed. 99 bitness = platform.architecture()[0] 100 # When running 64-bit python the x64 DLLs will be in System32 101 # ARM64 binaries will not be available in the system directories because we 102 # don't build on ARM64 machines. 103 x64_path = 'System32' if bitness == '64bit' else 'Sysnative' 104 x64_path = os.path.join(os.path.expandvars('%windir%'), x64_path) 105 vs_runtime_dll_dirs = [x64_path, 106 os.path.join(os.path.expandvars('%windir%'), 107 'SysWOW64'), 108 'Arm64Unused'] 109 110 return vs_runtime_dll_dirs 111 112 113def _RegistryGetValueUsingWinReg(key, value): 114 """Use the _winreg module to obtain the value of a registry key. 115 116 Args: 117 key: The registry key. 118 value: The particular registry value to read. 119 Return: 120 contents of the registry key's value, or None on failure. Throws 121 ImportError if _winreg is unavailable. 122 """ 123 import _winreg 124 try: 125 root, subkey = key.split('\\', 1) 126 assert root == 'HKLM' # Only need HKLM for now. 127 with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey: 128 return _winreg.QueryValueEx(hkey, value)[0] 129 except WindowsError: 130 return None 131 132 133def _RegistryGetValue(key, value): 134 try: 135 return _RegistryGetValueUsingWinReg(key, value) 136 except ImportError: 137 raise Exception('The python library _winreg not found.') 138 139 140def GetVisualStudioVersion(): 141 """Return best available version of Visual Studio. 142 """ 143 supported_versions = list(MSVS_VERSIONS.keys()) 144 145 # VS installed in depot_tools for Googlers 146 if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))): 147 return supported_versions[0] 148 149 # VS installed in system for external developers 150 supported_versions_str = ', '.join('{} ({})'.format(v,k) 151 for k,v in MSVS_VERSIONS.items()) 152 available_versions = [] 153 for version in supported_versions: 154 for path in ( 155 os.environ.get('vs%s_install' % version), 156 os.path.expandvars('%ProgramFiles(x86)%' + 157 '/Microsoft Visual Studio/%s' % version)): 158 if path and any(os.path.exists(os.path.join(path, edition)) for edition in 159 ('Enterprise', 'Professional', 'Community', 'Preview')): 160 available_versions.append(version) 161 break 162 163 if not available_versions: 164 raise Exception('No supported Visual Studio can be found.' 165 ' Supported versions are: %s.' % supported_versions_str) 166 return available_versions[0] 167 168 169def DetectVisualStudioPath(): 170 """Return path to the installed Visual Studio. 171 """ 172 173 # Note that this code is used from 174 # build/toolchain/win/setup_toolchain.py as well. 175 version_as_year = GetVisualStudioVersion() 176 177 # The VC++ >=2017 install location needs to be located using COM instead of 178 # the registry. For details see: 179 # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/ 180 # For now we use a hardcoded default with an environment variable override. 181 for path in ( 182 os.environ.get('vs%s_install' % version_as_year), 183 os.path.expandvars('%ProgramFiles(x86)%' + 184 '/Microsoft Visual Studio/%s/Enterprise' % 185 version_as_year), 186 os.path.expandvars('%ProgramFiles(x86)%' + 187 '/Microsoft Visual Studio/%s/Professional' % 188 version_as_year), 189 os.path.expandvars('%ProgramFiles(x86)%' + 190 '/Microsoft Visual Studio/%s/Community' % 191 version_as_year), 192 os.path.expandvars('%ProgramFiles(x86)%' + 193 '/Microsoft Visual Studio/%s/Preview' % 194 version_as_year)): 195 if path and os.path.exists(path): 196 return path 197 198 raise Exception('Visual Studio Version %s not found.' % version_as_year) 199 200 201def _CopyRuntimeImpl(target, source, verbose=True): 202 """Copy |source| to |target| if it doesn't already exist or if it needs to be 203 updated (comparing last modified time as an approximate float match as for 204 some reason the values tend to differ by ~1e-07 despite being copies of the 205 same file... https://crbug.com/603603). 206 """ 207 if (os.path.isdir(os.path.dirname(target)) and 208 (not os.path.isfile(target) or 209 abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)): 210 if verbose: 211 print('Copying %s to %s...' % (source, target)) 212 if os.path.exists(target): 213 # Make the file writable so that we can delete it now, and keep it 214 # readable. 215 os.chmod(target, stat.S_IWRITE | stat.S_IREAD) 216 os.unlink(target) 217 shutil.copy2(source, target) 218 # Make the file writable so that we can overwrite or delete it later, 219 # keep it readable. 220 os.chmod(target, stat.S_IWRITE | stat.S_IREAD) 221 222def _SortByHighestVersionNumberFirst(list_of_str_versions): 223 """This sorts |list_of_str_versions| according to version number rules 224 so that version "1.12" is higher than version "1.9". Does not work 225 with non-numeric versions like 1.4.a8 which will be higher than 226 1.4.a12. It does handle the versions being embedded in file paths. 227 """ 228 def to_int_if_int(x): 229 try: 230 return int(x) 231 except ValueError: 232 return x 233 234 def to_number_sequence(x): 235 part_sequence = re.split(r'[\\/\.]', x) 236 return [to_int_if_int(x) for x in part_sequence] 237 238 list_of_str_versions.sort(key=to_number_sequence, reverse=True) 239 240def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, dll_pattern, suffix): 241 """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't 242 exist, but the target directory does exist.""" 243 if target_cpu == 'arm64': 244 # Windows ARM64 VCRuntime is located at {toolchain_root}/VC/Redist/MSVC/ 245 # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/. 246 # Select VC toolset directory based on Visual Studio version 247 vc_redist_root = FindVCRedistRoot() 248 if suffix.startswith('.'): 249 vc_toolset_dir = 'Microsoft.{}.CRT' \ 250 .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) 251 source_dir = os.path.join(vc_redist_root, 252 'arm64', vc_toolset_dir) 253 else: 254 vc_toolset_dir = 'Microsoft.{}.DebugCRT' \ 255 .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) 256 source_dir = os.path.join(vc_redist_root, 'debug_nonredist', 257 'arm64', vc_toolset_dir) 258 for file_part in ('msvcp', 'vccorlib', 'vcruntime'): 259 dll = dll_pattern % file_part 260 target = os.path.join(target_dir, dll) 261 source = os.path.join(source_dir, dll) 262 _CopyRuntimeImpl(target, source) 263 # Copy the UCRT files from the Windows SDK. This location includes the 264 # api-ms-win-crt-*.dll files that are not found in the Windows directory. 265 # These files are needed for component builds. If WINDOWSSDKDIR is not set 266 # use the default SDK path. This will be the case when 267 # DEPOT_TOOLS_WIN_TOOLCHAIN=0 and vcvarsall.bat has not been run. 268 win_sdk_dir = os.path.normpath( 269 os.environ.get('WINDOWSSDKDIR', 270 os.path.expandvars('%ProgramFiles(x86)%' 271 '\\Windows Kits\\10'))) 272 # ARM64 doesn't have a redist for the ucrt DLLs because they are always 273 # present in the OS. 274 if target_cpu != 'arm64': 275 # Starting with the 10.0.17763 SDK the ucrt files are in a version-named 276 # directory - this handles both cases. 277 redist_dir = os.path.join(win_sdk_dir, 'Redist') 278 version_dirs = glob.glob(os.path.join(redist_dir, '10.*')) 279 if len(version_dirs) > 0: 280 _SortByHighestVersionNumberFirst(version_dirs) 281 redist_dir = version_dirs[0] 282 ucrt_dll_dirs = os.path.join(redist_dir, 'ucrt', 'DLLs', target_cpu) 283 ucrt_files = glob.glob(os.path.join(ucrt_dll_dirs, 'api-ms-win-*.dll')) 284 assert len(ucrt_files) > 0 285 for ucrt_src_file in ucrt_files: 286 file_part = os.path.basename(ucrt_src_file) 287 ucrt_dst_file = os.path.join(target_dir, file_part) 288 _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False) 289 # We must copy ucrtbase.dll for x64/x86, and ucrtbased.dll for all CPU types. 290 if target_cpu != 'arm64' or not suffix.startswith('.'): 291 if not suffix.startswith('.'): 292 # ucrtbased.dll is located at {win_sdk_dir}/bin/{a.b.c.d}/{target_cpu}/ 293 # ucrt/. 294 sdk_redist_root = os.path.join(win_sdk_dir, 'bin') 295 sdk_bin_sub_dirs = os.listdir(sdk_redist_root) 296 # Select the most recent SDK if there are multiple versions installed. 297 _SortByHighestVersionNumberFirst(sdk_bin_sub_dirs) 298 for directory in sdk_bin_sub_dirs: 299 sdk_redist_root_version = os.path.join(sdk_redist_root, directory) 300 if not os.path.isdir(sdk_redist_root_version): 301 continue 302 if re.match(r'10\.\d+\.\d+\.\d+', directory): 303 source_dir = os.path.join(sdk_redist_root_version, target_cpu, 'ucrt') 304 break 305 _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix), 306 os.path.join(source_dir, 'ucrtbase' + suffix)) 307 308 309def FindVCComponentRoot(component): 310 """Find the most recent Tools or Redist or other directory in an MSVC install. 311 Typical results are {toolchain_root}/VC/{component}/MSVC/{x.y.z}. The {x.y.z} 312 version number part changes frequently so the highest version number found is 313 used. 314 """ 315 316 SetEnvironmentAndGetRuntimeDllDirs() 317 assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ) 318 vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'], 319 'VC', component, 'MSVC') 320 vc_component_msvc_contents = os.listdir(vc_component_msvc_root) 321 # Select the most recent toolchain if there are several. 322 _SortByHighestVersionNumberFirst(vc_component_msvc_contents) 323 for directory in vc_component_msvc_contents: 324 if not os.path.isdir(os.path.join(vc_component_msvc_root, directory)): 325 continue 326 if re.match(r'14\.\d+\.\d+', directory): 327 return os.path.join(vc_component_msvc_root, directory) 328 raise Exception('Unable to find the VC %s directory.' % component) 329 330 331def FindVCRedistRoot(): 332 """In >=VS2017, Redist binaries are located in 333 {toolchain_root}/VC/Redist/MSVC/{x.y.z}/{target_cpu}/. 334 335 This returns the '{toolchain_root}/VC/Redist/MSVC/{x.y.z}/' path. 336 """ 337 return FindVCComponentRoot('Redist') 338 339 340def _CopyRuntime(target_dir, source_dir, target_cpu, debug): 341 """Copy the VS runtime DLLs, only if the target doesn't exist, but the target 342 directory does exist. Handles VS 2015, 2017 and 2019.""" 343 suffix = 'd.dll' if debug else '.dll' 344 # VS 2015, 2017 and 2019 use the same CRT DLLs. 345 _CopyUCRTRuntime(target_dir, source_dir, target_cpu, '%s140' + suffix, 346 suffix) 347 348 349def CopyDlls(target_dir, configuration, target_cpu): 350 """Copy the VS runtime DLLs into the requested directory as needed. 351 352 configuration is one of 'Debug' or 'Release'. 353 target_cpu is one of 'x86', 'x64' or 'arm64'. 354 355 The debug configuration gets both the debug and release DLLs; the 356 release config only the latter. 357 """ 358 vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() 359 if not vs_runtime_dll_dirs: 360 return 361 362 x64_runtime, x86_runtime, arm64_runtime = vs_runtime_dll_dirs 363 if target_cpu == 'x64': 364 runtime_dir = x64_runtime 365 elif target_cpu == 'x86': 366 runtime_dir = x86_runtime 367 elif target_cpu == 'arm64': 368 runtime_dir = arm64_runtime 369 else: 370 raise Exception('Unknown target_cpu: ' + target_cpu) 371 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False) 372 if configuration == 'Debug': 373 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True) 374 _CopyDebugger(target_dir, target_cpu) 375 376 377def _CopyDebugger(target_dir, target_cpu): 378 """Copy dbghelp.dll and dbgcore.dll into the requested directory as needed. 379 380 target_cpu is one of 'x86', 'x64' or 'arm64'. 381 382 dbghelp.dll is used when Chrome needs to symbolize stacks. Copying this file 383 from the SDK directory avoids using the system copy of dbghelp.dll which then 384 ensures compatibility with recent debug information formats, such as VS 385 2017 /debug:fastlink PDBs. 386 387 dbgcore.dll is needed when using some functions from dbghelp.dll (like 388 MinidumpWriteDump). 389 """ 390 win_sdk_dir = SetEnvironmentAndGetSDKDir() 391 if not win_sdk_dir: 392 return 393 394 # List of debug files that should be copied, the first element of the tuple is 395 # the name of the file and the second indicates if it's optional. 396 debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)] 397 for debug_file, is_optional in debug_files: 398 full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file) 399 if not os.path.exists(full_path): 400 if is_optional: 401 continue 402 else: 403 # TODO(crbug.com/773476): remove version requirement. 404 raise Exception('%s not found in "%s"\r\nYou must install the ' 405 '"Debugging Tools for Windows" feature from the Windows' 406 ' 10 SDK.' 407 % (debug_file, full_path)) 408 target_path = os.path.join(target_dir, debug_file) 409 _CopyRuntimeImpl(target_path, full_path) 410 411 412def _GetDesiredVsToolchainHashes(): 413 """Load a list of SHA1s corresponding to the toolchains that we want installed 414 to build with. 415 416 When updating the toolchain, consider the following areas impacted by the 417 toolchain version: 418 419 * //base/win/windows_version.cc NTDDI preprocessor check 420 Triggers a compiler error if the available SDK is older than the minimum. 421 * //build/config/win/BUILD.gn NTDDI_VERSION value 422 Affects the availability of APIs in the toolchain headers. 423 * //docs/windows_build_instructions.md mentions of VS or Windows SDK. 424 Keeps the document consistent with the toolchain version. 425 """ 426 # VS 2019 Update 9 (16.3.29324.140) with 10.0.18362 SDK, 10.0.17763 version of 427 # Debuggers, and 10.0.17134 version of d3dcompiler_47.dll, with ARM64 428 # libraries and UWP support. 429 # See go/chromium-msvc-toolchain for instructions about how to update the 430 # toolchain. 431 toolchain_hash = '9ff60e43ba91947baca460d0ca3b1b980c3a2c23' 432 # Third parties that do not have access to the canonical toolchain can map 433 # canonical toolchain version to their own toolchain versions. 434 toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % toolchain_hash 435 return [os.environ.get(toolchain_hash_mapping_key, toolchain_hash)] 436 437 438def ShouldUpdateToolchain(): 439 """Check if the toolchain should be upgraded.""" 440 if not os.path.exists(json_data_file): 441 return True 442 with open(json_data_file, 'r') as tempf: 443 toolchain_data = json.load(tempf) 444 version = toolchain_data['version'] 445 env_version = GetVisualStudioVersion() 446 # If there's a mismatch between the version set in the environment and the one 447 # in the json file then the toolchain should be updated. 448 return version != env_version 449 450 451def Update(force=False, no_download=False): 452 """Requests an update of the toolchain to the specific hashes we have at 453 this revision. The update outputs a .json of the various configuration 454 information required to pass to gyp which we use in |GetToolchainDir()|. 455 If no_download is true then the toolchain will be configured if present but 456 will not be downloaded. 457 """ 458 if force != False and force != '--force': 459 print('Unknown parameter "%s"' % force, file=sys.stderr) 460 return 1 461 if force == '--force' or os.path.exists(json_data_file): 462 force = True 463 464 depot_tools_win_toolchain = \ 465 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) 466 if (_HostIsWindows() or force) and depot_tools_win_toolchain: 467 import find_depot_tools 468 depot_tools_path = find_depot_tools.add_depot_tools_to_path() 469 470 # On Linux, the file system is usually case-sensitive while the Windows 471 # SDK only works on case-insensitive file systems. If it doesn't already 472 # exist, set up a ciopfs fuse mount to put the SDK in a case-insensitive 473 # part of the file system. 474 toolchain_dir = os.path.join(depot_tools_path, 'win_toolchain', 'vs_files') 475 # For testing this block, unmount existing mounts with 476 # fusermount -u third_party/depot_tools/win_toolchain/vs_files 477 if sys.platform.startswith('linux') and not os.path.ismount(toolchain_dir): 478 import distutils.spawn 479 ciopfs = distutils.spawn.find_executable('ciopfs') 480 if not ciopfs: 481 # ciopfs not found in PATH; try the one downloaded from the DEPS hook. 482 ciopfs = os.path.join(script_dir, 'ciopfs') 483 if not os.path.isdir(toolchain_dir): 484 os.mkdir(toolchain_dir) 485 if not os.path.isdir(toolchain_dir + '.ciopfs'): 486 os.mkdir(toolchain_dir + '.ciopfs') 487 # Without use_ino, clang's #pragma once and Wnonportable-include-path 488 # both don't work right, see https://llvm.org/PR34931 489 # use_ino doesn't slow down builds, so it seems there's no drawback to 490 # just using it always. 491 subprocess.check_call([ 492 ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs', toolchain_dir]) 493 494 get_toolchain_args = [ 495 sys.executable, 496 os.path.join(depot_tools_path, 497 'win_toolchain', 498 'get_toolchain_if_necessary.py'), 499 '--output-json', json_data_file, 500 ] + _GetDesiredVsToolchainHashes() 501 if force: 502 get_toolchain_args.append('--force') 503 if no_download: 504 get_toolchain_args.append('--no-download') 505 subprocess.check_call(get_toolchain_args) 506 507 return 0 508 509 510def NormalizePath(path): 511 while path.endswith('\\'): 512 path = path[:-1] 513 return path 514 515 516def SetEnvironmentAndGetSDKDir(): 517 """Gets location information about the current sdk (must have been 518 previously updated by 'update'). This is used for the GN build.""" 519 SetEnvironmentAndGetRuntimeDllDirs() 520 521 # If WINDOWSSDKDIR is not set, search the default SDK path and set it. 522 if not 'WINDOWSSDKDIR' in os.environ: 523 default_sdk_path = os.path.expandvars('%ProgramFiles(x86)%' 524 '\\Windows Kits\\10') 525 if os.path.isdir(default_sdk_path): 526 os.environ['WINDOWSSDKDIR'] = default_sdk_path 527 528 return NormalizePath(os.environ['WINDOWSSDKDIR']) 529 530 531def GetToolchainDir(): 532 """Gets location information about the current toolchain (must have been 533 previously updated by 'update'). This is used for the GN build.""" 534 runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() 535 win_sdk_dir = SetEnvironmentAndGetSDKDir() 536 537 print('''vs_path = %s 538sdk_path = %s 539vs_version = %s 540wdk_dir = %s 541runtime_dirs = %s 542''' % (ToGNString(NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH'])), 543 ToGNString(win_sdk_dir), ToGNString(GetVisualStudioVersion()), 544 ToGNString(NormalizePath(os.environ.get('WDK_DIR', ''))), 545 ToGNString(os.path.pathsep.join(runtime_dll_dirs or ['None'])))) 546 547 548def main(): 549 commands = { 550 'update': Update, 551 'get_toolchain_dir': GetToolchainDir, 552 'copy_dlls': CopyDlls, 553 } 554 if len(sys.argv) < 2 or sys.argv[1] not in commands: 555 print('Expected one of: %s' % ', '.join(commands), file=sys.stderr) 556 return 1 557 return commands[sys.argv[1]](*sys.argv[2:]) 558 559 560if __name__ == '__main__': 561 sys.exit(main()) 562