1#!/usr/bin/env python3 2# Copyright 2014 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6 7import collections 8import glob 9import json 10import os 11import platform 12import re 13import shutil 14import stat 15import subprocess 16import sys 17 18from gn_helpers import ToGNString 19 20# VS 2022 17.9.2 with 10.0.22621.2428 SDK with ARM64 libraries and UWP support. 21# See go/win-toolchain-reference for instructions about how to update the 22# toolchain. 23# 24# When updating the toolchain, consider the following areas impacted by the 25# toolchain version: 26# 27# * This file -- SDK_VERSION and TOOLCHAIN_HASH 28# Determines which version of the toolchain is used by gclient. The hash 29# is the name of the toolchain package (minus the zip) in gcloud, and 30# SDK_VERSION should match the SDK version in that package. 31# 32# * This file -- MSVS_VERSIONS 33# Records the supported versions of Visual Studio, in priority order. 34# 35# * This file -- MSVC_TOOLSET_VERSION 36# Determines the expected MSVC toolset for each version of Visual Studio. 37# The packaged toolset version can be seen at <package>/VC/redist/MSVC; 38# there will be a folder named `v143` or similar. 39# 40# * build/toolchain/win/setup_toolchain.py -- SDK_VERSION 41# Secondary specification of the SDK Version, to make sure we're loading the 42# right one. Should always match SDK_VERSION in this file. 43# 44# * base/win/windows_version.cc -- NTDDI preprocessor check 45# Forces developers to have a specific SDK version (or newer). Triggers a 46# compiler error if the available SDK is older than the minimum. 47# 48# * build/config/win/BUILD.gn -- NTDDI_VERSION 49# Specifies which SDK/WDK version is installed. Some of the toolchain headers 50# check this macro to conditionally compile code. 51# 52# * build/config/win/BUILD.gn -- WINVER and _WIN32_WINNT 53# Specify the minimum supported Windows version. These very rarely need to 54# be changed. 55# 56# * tools/win/setenv.py -- list of accepted `vs_version`s 57# Add/remove VS versions when upgrading to a new VS version. 58# 59# * docs/windows_build_instructions.md 60# Make sure any version numbers in the documentation match the code. 61# 62TOOLCHAIN_HASH = '7393122652' 63SDK_VERSION = '10.0.22621.0' 64 65# Visual Studio versions are listed in descending order of priority. 66# The first version is assumed by this script to be the one that is packaged, 67# which makes a difference for the arm64 runtime. 68# The second number is an alternate version number, only used in an error string 69MSVS_VERSIONS = collections.OrderedDict([ 70 ('2022', '17.0'), # The VS version in our packaged toolchain. 71 ('2019', '16.0'), 72 ('2017', '15.0'), 73]) 74 75# List of preferred VC toolset version based on MSVS 76# Order is not relevant for this dictionary. 77MSVC_TOOLSET_VERSION = { 78 '2022': 'VC143', 79 '2019': 'VC142', 80 '2017': 'VC141', 81} 82 83script_dir = os.path.dirname(os.path.realpath(__file__)) 84json_data_file = os.path.join(script_dir, 'win_toolchain.json') 85 86 87def _HostIsWindows(): 88 """Returns True if running on a Windows host (including under cygwin).""" 89 return sys.platform in ('win32', 'cygwin') 90 91def SetEnvironmentAndGetRuntimeDllDirs(): 92 """Sets up os.environ to use the depot_tools VS toolchain with gyp, and 93 returns the location of the VC runtime DLLs so they can be copied into 94 the output directory after gyp generation. 95 96 Return value is [x64path, x86path, 'Arm64Unused'] or None. arm64path is 97 generated separately because there are multiple folders for the arm64 VC 98 runtime. 99 """ 100 vs_runtime_dll_dirs = None 101 depot_tools_win_toolchain = \ 102 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) 103 # When running on a non-Windows host, only do this if the SDK has explicitly 104 # been downloaded before (in which case json_data_file will exist). 105 if ((_HostIsWindows() or os.path.exists(json_data_file)) 106 and depot_tools_win_toolchain): 107 if ShouldUpdateToolchain(): 108 if len(sys.argv) > 1 and sys.argv[1] == 'update': 109 update_result = Update() 110 else: 111 update_result = Update(no_download=True) 112 if update_result != 0: 113 raise Exception('Failed to update, error code %d.' % update_result) 114 with open(json_data_file, 'r') as tempf: 115 toolchain_data = json.load(tempf) 116 117 toolchain = toolchain_data['path'] 118 version = toolchain_data['version'] 119 win_sdk = toolchain_data.get('win_sdk') 120 wdk = toolchain_data['wdk'] 121 # TODO(scottmg): The order unfortunately matters in these. They should be 122 # split into separate keys for x64/x86/arm64. (See CopyDlls call below). 123 # http://crbug.com/345992 124 vs_runtime_dll_dirs = toolchain_data['runtime_dirs'] 125 # The number of runtime_dirs in the toolchain_data was two (x64/x86) but 126 # changed to three (x64/x86/arm64) and this code needs to handle both 127 # possibilities, which can change independently from this code. 128 if len(vs_runtime_dll_dirs) == 2: 129 vs_runtime_dll_dirs.append('Arm64Unused') 130 131 os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain 132 133 os.environ['WINDOWSSDKDIR'] = win_sdk 134 os.environ['WDK_DIR'] = wdk 135 # Include the VS runtime in the PATH in case it's not machine-installed. 136 runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs) 137 os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH'] 138 elif sys.platform == 'win32' and not depot_tools_win_toolchain: 139 if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ: 140 os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath() 141 142 # When using an installed toolchain these files aren't needed in the output 143 # directory in order to run binaries locally, but they are needed in order 144 # to create isolates or the mini_installer. Copying them to the output 145 # directory ensures that they are available when needed. 146 bitness = platform.architecture()[0] 147 # When running 64-bit python the x64 DLLs will be in System32 148 # ARM64 binaries will not be available in the system directories because we 149 # don't build on ARM64 machines. 150 x64_path = 'System32' if bitness == '64bit' else 'Sysnative' 151 x64_path = os.path.join(os.path.expandvars('%windir%'), x64_path) 152 vs_runtime_dll_dirs = [x64_path, 153 os.path.join(os.path.expandvars('%windir%'), 154 'SysWOW64'), 155 'Arm64Unused'] 156 157 return vs_runtime_dll_dirs 158 159 160def _RegistryGetValueUsingWinReg(key, value): 161 """Use the _winreg module to obtain the value of a registry key. 162 163 Args: 164 key: The registry key. 165 value: The particular registry value to read. 166 Return: 167 contents of the registry key's value, or None on failure. Throws 168 ImportError if _winreg is unavailable. 169 """ 170 import _winreg 171 try: 172 root, subkey = key.split('\\', 1) 173 assert root == 'HKLM' # Only need HKLM for now. 174 with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey: 175 return _winreg.QueryValueEx(hkey, value)[0] 176 except WindowsError: 177 return None 178 179 180def _RegistryGetValue(key, value): 181 try: 182 return _RegistryGetValueUsingWinReg(key, value) 183 except ImportError: 184 raise Exception('The python library _winreg not found.') 185 186 187def GetVisualStudioVersion(): 188 """Return best available version of Visual Studio. 189 """ 190 supported_versions = list(MSVS_VERSIONS.keys()) 191 192 # VS installed in depot_tools for Googlers 193 if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))): 194 return supported_versions[0] 195 196 # VS installed in system for external developers 197 supported_versions_str = ', '.join('{} ({})'.format(v,k) 198 for k,v in MSVS_VERSIONS.items()) 199 available_versions = [] 200 for version in supported_versions: 201 # Checking vs%s_install environment variables. 202 # For example, vs2019_install could have the value 203 # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community". 204 # Only vs2017_install, vs2019_install and vs2022_install are supported. 205 path = os.environ.get('vs%s_install' % version) 206 if path and os.path.exists(path): 207 available_versions.append(version) 208 break 209 # Detecting VS under possible paths. 210 if version >= '2022': 211 program_files_path_variable = '%ProgramFiles%' 212 else: 213 program_files_path_variable = '%ProgramFiles(x86)%' 214 path = os.path.expandvars(program_files_path_variable + 215 '/Microsoft Visual Studio/%s' % version) 216 if path and any( 217 os.path.exists(os.path.join(path, edition)) 218 for edition in ('Enterprise', 'Professional', 'Community', 'Preview', 219 'BuildTools')): 220 available_versions.append(version) 221 break 222 223 if not available_versions: 224 raise Exception('No supported Visual Studio can be found.' 225 ' Supported versions are: %s.' % supported_versions_str) 226 return available_versions[0] 227 228 229def DetectVisualStudioPath(): 230 """Return path to the installed Visual Studio. 231 """ 232 233 # Note that this code is used from 234 # build/toolchain/win/setup_toolchain.py as well. 235 version_as_year = GetVisualStudioVersion() 236 237 # The VC++ >=2017 install location needs to be located using COM instead of 238 # the registry. For details see: 239 # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/ 240 # For now we use a hardcoded default with an environment variable override. 241 if version_as_year >= '2022': 242 program_files_path_variable = '%ProgramFiles%' 243 else: 244 program_files_path_variable = '%ProgramFiles(x86)%' 245 for path in (os.environ.get('vs%s_install' % version_as_year), 246 os.path.expandvars(program_files_path_variable + 247 '/Microsoft Visual Studio/%s/Enterprise' % 248 version_as_year), 249 os.path.expandvars(program_files_path_variable + 250 '/Microsoft Visual Studio/%s/Professional' % 251 version_as_year), 252 os.path.expandvars(program_files_path_variable + 253 '/Microsoft Visual Studio/%s/Community' % 254 version_as_year), 255 os.path.expandvars(program_files_path_variable + 256 '/Microsoft Visual Studio/%s/Preview' % 257 version_as_year), 258 os.path.expandvars(program_files_path_variable + 259 '/Microsoft Visual Studio/%s/BuildTools' % 260 version_as_year)): 261 if path and os.path.exists(path): 262 return path 263 264 raise Exception('Visual Studio Version %s not found.' % version_as_year) 265 266 267def _CopyRuntimeImpl(target, source, verbose=True): 268 """Copy |source| to |target| if it doesn't already exist or if it needs to be 269 updated (comparing last modified time as an approximate float match as for 270 some reason the values tend to differ by ~1e-07 despite being copies of the 271 same file... https://crbug.com/603603). 272 """ 273 if (os.path.isdir(os.path.dirname(target)) and 274 (not os.path.isfile(target) or 275 abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)): 276 if verbose: 277 print('Copying %s to %s...' % (source, target)) 278 if os.path.exists(target): 279 # Make the file writable so that we can delete it now, and keep it 280 # readable. 281 os.chmod(target, stat.S_IWRITE | stat.S_IREAD) 282 os.unlink(target) 283 shutil.copy2(source, target) 284 # Make the file writable so that we can overwrite or delete it later, 285 # keep it readable. 286 os.chmod(target, stat.S_IWRITE | stat.S_IREAD) 287 288def _SortByHighestVersionNumberFirst(list_of_str_versions): 289 """This sorts |list_of_str_versions| according to version number rules 290 so that version "1.12" is higher than version "1.9". Does not work 291 with non-numeric versions like 1.4.a8 which will be higher than 292 1.4.a12. It does handle the versions being embedded in file paths. 293 """ 294 def to_int_if_int(x): 295 try: 296 return int(x) 297 except ValueError: 298 return x 299 300 def to_number_sequence(x): 301 part_sequence = re.split(r'[\\/\.]', x) 302 return [to_int_if_int(x) for x in part_sequence] 303 304 list_of_str_versions.sort(key=to_number_sequence, reverse=True) 305 306 307def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix): 308 """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't 309 exist, but the target directory does exist.""" 310 if target_cpu == 'arm64': 311 # Windows ARM64 VCRuntime is located at {toolchain_root}/VC/Redist/MSVC/ 312 # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/. 313 # Select VC toolset directory based on Visual Studio version 314 vc_redist_root = FindVCRedistRoot() 315 if suffix.startswith('.'): 316 vc_toolset_dir = 'Microsoft.{}.CRT' \ 317 .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) 318 source_dir = os.path.join(vc_redist_root, 319 'arm64', vc_toolset_dir) 320 else: 321 vc_toolset_dir = 'Microsoft.{}.DebugCRT' \ 322 .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) 323 source_dir = os.path.join(vc_redist_root, 'debug_nonredist', 324 'arm64', vc_toolset_dir) 325 file_parts = ('msvcp140', 'vccorlib140', 'vcruntime140') 326 if target_cpu == 'x64' and GetVisualStudioVersion() != '2017': 327 file_parts = file_parts + ('vcruntime140_1', ) 328 for file_part in file_parts: 329 dll = file_part + suffix 330 target = os.path.join(target_dir, dll) 331 source = os.path.join(source_dir, dll) 332 _CopyRuntimeImpl(target, source) 333 # We must copy ucrtbased.dll for all CPU types. The rest of the Universal CRT 334 # is installed as part of the OS in Windows 10 and beyond. 335 if not suffix.startswith('.'): 336 win_sdk_dir = os.path.normpath( 337 os.environ.get( 338 'WINDOWSSDKDIR', 339 os.path.expandvars('%ProgramFiles(x86)%' 340 '\\Windows Kits\\10'))) 341 # ucrtbased.dll is located at {win_sdk_dir}/bin/{a.b.c.d}/{target_cpu}/ 342 # ucrt/. 343 sdk_bin_root = os.path.join(win_sdk_dir, 'bin') 344 sdk_bin_sub_dirs = glob.glob(os.path.join(sdk_bin_root, '10.*')) 345 # Select the most recent SDK if there are multiple versions installed. 346 _SortByHighestVersionNumberFirst(sdk_bin_sub_dirs) 347 for directory in sdk_bin_sub_dirs: 348 sdk_redist_root_version = os.path.join(sdk_bin_root, directory) 349 if not os.path.isdir(sdk_redist_root_version): 350 continue 351 source_dir = os.path.join(sdk_redist_root_version, target_cpu, 'ucrt') 352 if not os.path.isdir(source_dir): 353 continue 354 break 355 _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix), 356 os.path.join(source_dir, 'ucrtbase' + suffix)) 357 358 359def FindVCComponentRoot(component): 360 """Find the most recent Tools or Redist or other directory in an MSVC install. 361 Typical results are {toolchain_root}/VC/{component}/MSVC/{x.y.z}. The {x.y.z} 362 version number part changes frequently so the highest version number found is 363 used. 364 """ 365 366 SetEnvironmentAndGetRuntimeDllDirs() 367 assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ) 368 vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'], 369 'VC', component, 'MSVC') 370 vc_component_msvc_contents = glob.glob( 371 os.path.join(vc_component_msvc_root, '14.*')) 372 # Select the most recent toolchain if there are several. 373 _SortByHighestVersionNumberFirst(vc_component_msvc_contents) 374 for directory in vc_component_msvc_contents: 375 if os.path.isdir(directory): 376 return directory 377 raise Exception('Unable to find the VC %s directory.' % component) 378 379 380def FindVCRedistRoot(): 381 """In >=VS2017, Redist binaries are located in 382 {toolchain_root}/VC/Redist/MSVC/{x.y.z}/{target_cpu}/. 383 384 This returns the '{toolchain_root}/VC/Redist/MSVC/{x.y.z}/' path. 385 """ 386 return FindVCComponentRoot('Redist') 387 388 389def _CopyRuntime(target_dir, source_dir, target_cpu, debug): 390 """Copy the VS runtime DLLs, only if the target doesn't exist, but the target 391 directory does exist. Handles VS 2015, 2017 and 2019.""" 392 suffix = 'd.dll' if debug else '.dll' 393 # VS 2015, 2017 and 2019 use the same CRT DLLs. 394 _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix) 395 396 397def CopyDlls(target_dir, configuration, target_cpu): 398 """Copy the VS runtime DLLs into the requested directory as needed. 399 400 configuration is one of 'Debug' or 'Release'. 401 target_cpu is one of 'x86', 'x64' or 'arm64'. 402 403 The debug configuration gets both the debug and release DLLs; the 404 release config only the latter. 405 """ 406 vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() 407 if not vs_runtime_dll_dirs: 408 return 409 410 x64_runtime, x86_runtime, arm64_runtime = vs_runtime_dll_dirs 411 if target_cpu == 'x64': 412 runtime_dir = x64_runtime 413 elif target_cpu == 'x86': 414 runtime_dir = x86_runtime 415 elif target_cpu == 'arm64': 416 runtime_dir = arm64_runtime 417 else: 418 raise Exception('Unknown target_cpu: ' + target_cpu) 419 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False) 420 if configuration == 'Debug': 421 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True) 422 _CopyDebugger(target_dir, target_cpu) 423 if target_cpu == 'arm64': 424 target_dir = os.path.join(target_dir, 'win_clang_x64') 425 target_cpu = 'x64' 426 runtime_dir = x64_runtime 427 os.makedirs(target_dir, exist_ok=True) 428 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False) 429 if configuration == 'Debug': 430 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True) 431 _CopyDebugger(target_dir, target_cpu) 432 433 434def _CopyDebugger(target_dir, target_cpu): 435 """Copy dbghelp.dll, dbgcore.dll, and msdia140.dll into the requested 436 directory. 437 438 target_cpu is one of 'x86', 'x64' or 'arm64'. 439 440 dbghelp.dll is used when Chrome needs to symbolize stacks. Copying this file 441 from the SDK directory avoids using the system copy of dbghelp.dll which then 442 ensures compatibility with recent debug information formats, such as 443 large-page PDBs. Note that for these DLLs to be deployed to swarming bots they 444 also need to be listed in group("runtime_libs"). 445 446 dbgcore.dll is needed when using some functions from dbghelp.dll (like 447 MinidumpWriteDump). 448 449 msdia140.dll is needed for tools like symupload.exe and dump_syms.exe. 450 """ 451 win_sdk_dir = SetEnvironmentAndGetSDKDir() 452 if not win_sdk_dir: 453 return 454 455 # List of debug files that should be copied, the first element of the tuple is 456 # the name of the file and the second indicates if it's optional. 457 debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)] 458 for debug_file, is_optional in debug_files: 459 full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file) 460 if not os.path.exists(full_path): 461 if is_optional: 462 continue 463 else: 464 raise Exception('%s not found in "%s"\r\nYou must install ' 465 'Windows 10 SDK version %s including the ' 466 '"Debugging Tools for Windows" feature.' % 467 (debug_file, full_path, SDK_VERSION)) 468 target_path = os.path.join(target_dir, debug_file) 469 _CopyRuntimeImpl(target_path, full_path) 470 471 # The x64 version of msdia140.dll is always used because symupload and 472 # dump_syms are always built as x64 binaries. 473 dia_path = os.path.join(NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH']), 474 'DIA SDK', 'bin', 'amd64', 'msdia140.dll') 475 _CopyRuntimeImpl(os.path.join(target_dir, 'msdia140.dll'), dia_path) 476 477 478def _GetDesiredVsToolchainHashes(): 479 """Load a list of SHA1s corresponding to the toolchains that we want installed 480 to build with.""" 481 # Third parties that do not have access to the canonical toolchain can map 482 # canonical toolchain version to their own toolchain versions. 483 toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % TOOLCHAIN_HASH 484 return [os.environ.get(toolchain_hash_mapping_key, TOOLCHAIN_HASH)] 485 486 487def ShouldUpdateToolchain(): 488 """Check if the toolchain should be upgraded.""" 489 if not os.path.exists(json_data_file): 490 return True 491 with open(json_data_file, 'r') as tempf: 492 toolchain_data = json.load(tempf) 493 version = toolchain_data['version'] 494 env_version = GetVisualStudioVersion() 495 # If there's a mismatch between the version set in the environment and the one 496 # in the json file then the toolchain should be updated. 497 return version != env_version 498 499 500def Update(force=False, no_download=False): 501 """Requests an update of the toolchain to the specific hashes we have at 502 this revision. The update outputs a .json of the various configuration 503 information required to pass to gyp which we use in |GetToolchainDir()|. 504 If no_download is true then the toolchain will be configured if present but 505 will not be downloaded. 506 """ 507 if force != False and force != '--force': 508 print('Unknown parameter "%s"' % force, file=sys.stderr) 509 return 1 510 if force == '--force' or os.path.exists(json_data_file): 511 force = True 512 513 depot_tools_win_toolchain = \ 514 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) 515 if (_HostIsWindows() or force) and depot_tools_win_toolchain: 516 import find_depot_tools 517 depot_tools_path = find_depot_tools.add_depot_tools_to_path() 518 519 # On Linux, the file system is usually case-sensitive while the Windows 520 # SDK only works on case-insensitive file systems. If it doesn't already 521 # exist, set up a ciopfs fuse mount to put the SDK in a case-insensitive 522 # part of the file system. 523 toolchain_dir = os.path.join(depot_tools_path, 'win_toolchain', 'vs_files') 524 # For testing this block, unmount existing mounts with 525 # fusermount -u third_party/depot_tools/win_toolchain/vs_files 526 if sys.platform.startswith('linux') and not os.path.ismount(toolchain_dir): 527 ciopfs = shutil.which('ciopfs') 528 if not ciopfs: 529 # ciopfs not found in PATH; try the one downloaded from the DEPS hook. 530 ciopfs = os.path.join(script_dir, 'ciopfs') 531 if not os.path.isdir(toolchain_dir): 532 os.mkdir(toolchain_dir) 533 if not os.path.isdir(toolchain_dir + '.ciopfs'): 534 os.mkdir(toolchain_dir + '.ciopfs') 535 # Without use_ino, clang's #pragma once and Wnonportable-include-path 536 # both don't work right, see https://llvm.org/PR34931 537 # use_ino doesn't slow down builds, so it seems there's no drawback to 538 # just using it always. 539 subprocess.check_call([ 540 ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs', toolchain_dir]) 541 542 get_toolchain_args = [ 543 sys.executable, 544 os.path.join(depot_tools_path, 545 'win_toolchain', 546 'get_toolchain_if_necessary.py'), 547 '--output-json', json_data_file, 548 ] + _GetDesiredVsToolchainHashes() 549 if force: 550 get_toolchain_args.append('--force') 551 if no_download: 552 get_toolchain_args.append('--no-download') 553 subprocess.check_call(get_toolchain_args) 554 555 return 0 556 557 558def NormalizePath(path): 559 while path.endswith('\\'): 560 path = path[:-1] 561 return path 562 563 564def SetEnvironmentAndGetSDKDir(): 565 """Gets location information about the current sdk (must have been 566 previously updated by 'update'). This is used for the GN build.""" 567 SetEnvironmentAndGetRuntimeDllDirs() 568 569 # If WINDOWSSDKDIR is not set, search the default SDK path and set it. 570 if not 'WINDOWSSDKDIR' in os.environ: 571 default_sdk_path = os.path.expandvars('%ProgramFiles(x86)%' 572 '\\Windows Kits\\10') 573 if os.path.isdir(default_sdk_path): 574 os.environ['WINDOWSSDKDIR'] = default_sdk_path 575 576 return NormalizePath(os.environ['WINDOWSSDKDIR']) 577 578 579def SDKIncludesIDCompositionDevice4(): 580 """Returns true if the selected Windows SDK includes the declaration for the 581 IDCompositionDevice4 interface. This is essentially the equivalent checking 582 if a (non-preview) SDK version >=10.0.22621.2428. 583 584 We cannot check for this SDK version directly since it installs to a folder 585 with the minor version set to 0 (i.e. 10.0.22621.0) and the 586 IDCompositionDevice4 interface was added in a servicing release which did 587 not increment the major version. 588 589 There doesn't seem to be a straightforward and cross-platform way to get the 590 minor version of an installed SDK directory. To work around this, we look 591 for the GUID declaring the interface which implies the SDK version and 592 ensures the interface itself is present.""" 593 win_sdk_dir = SetEnvironmentAndGetSDKDir() 594 if not win_sdk_dir: 595 return False 596 597 # Skip this check if we know the major version definitely includes 598 # IDCompositionDevice4. 599 if int(SDK_VERSION.split('.')[2]) > 22621: 600 return True 601 602 dcomp_header_path = os.path.join(win_sdk_dir, 'Include', SDK_VERSION, 'um', 603 'dcomp.h') 604 DECLARE_DEVICE4_LINE = ('DECLARE_INTERFACE_IID_(' 605 'IDCompositionDevice4, IDCompositionDevice3, ' 606 '"85FC5CCA-2DA6-494C-86B6-4A775C049B8A")') 607 with open(dcomp_header_path) as f: 608 for line in f.readlines(): 609 if line.rstrip() == DECLARE_DEVICE4_LINE: 610 return True 611 612 return False 613 614 615def GetToolchainDir(): 616 """Gets location information about the current toolchain (must have been 617 previously updated by 'update'). This is used for the GN build.""" 618 runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() 619 win_sdk_dir = SetEnvironmentAndGetSDKDir() 620 version_as_year = GetVisualStudioVersion() 621 622 if not SDKIncludesIDCompositionDevice4(): 623 print( 624 'Windows SDK >= 10.0.22621.2428 required. You can get it by updating ' 625 f'Visual Studio {version_as_year} using the Visual Studio Installer.', 626 file=sys.stderr, 627 ) 628 return 1 629 630 print('''vs_path = %s 631sdk_version = %s 632sdk_path = %s 633vs_version = %s 634wdk_dir = %s 635runtime_dirs = %s 636''' % (ToGNString(NormalizePath( 637 os.environ['GYP_MSVS_OVERRIDE_PATH'])), ToGNString(SDK_VERSION), 638 ToGNString(win_sdk_dir), ToGNString(version_as_year), 639 ToGNString(NormalizePath(os.environ.get('WDK_DIR', ''))), 640 ToGNString(os.path.pathsep.join(runtime_dll_dirs or ['None'])))) 641 642 643def main(): 644 commands = { 645 'update': Update, 646 'get_toolchain_dir': GetToolchainDir, 647 'copy_dlls': CopyDlls, 648 } 649 if len(sys.argv) < 2 or sys.argv[1] not in commands: 650 print('Expected one of: %s' % ', '.join(commands), file=sys.stderr) 651 return 1 652 return commands[sys.argv[1]](*sys.argv[2:]) 653 654 655if __name__ == '__main__': 656 sys.exit(main()) 657