tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

vs_toolchain.py (26491B)


      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 
      7 import collections
      8 import glob
      9 import json
     10 import os
     11 import platform
     12 import re
     13 import shutil
     14 import stat
     15 import subprocess
     16 import sys
     17 
     18 from gn_helpers import ToGNString
     19 
     20 # VS 2022 17.13.4 with 10.0.26100.3323 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 #
     62 TOOLCHAIN_HASH = '076960eda6'
     63 SDK_VERSION = '10.0.26100.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
     69 MSVS_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.
     77 MSVC_TOOLSET_VERSION = {
     78    '2022': 'VC143',
     79    '2019': 'VC142',
     80    '2017': 'VC141',
     81 }
     82 
     83 script_dir = os.path.dirname(os.path.realpath(__file__))
     84 json_data_file = os.path.join(script_dir, 'win_toolchain.json')
     85 
     86 
     87 def _HostIsWindows():
     88  """Returns True if running on a Windows host (including under cygwin)."""
     89  return sys.platform in ('win32', 'cygwin')
     90 
     91 def 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 
    160 def _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 
    180 def _RegistryGetValue(key, value):
    181  try:
    182    return _RegistryGetValueUsingWinReg(key, value)
    183  except ImportError:
    184    raise Exception('The python library _winreg not found.')
    185 
    186 
    187 def 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 
    229 def 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 
    267 def _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 
    288 def _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 
    307 def _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 
    359 def 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 
    380 def 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 
    389 def _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 
    397 def 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 
    434 def _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 
    478 def _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 
    487 def 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 
    500 def 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 
    558 def NormalizePath(path):
    559  while path.endswith('\\'):
    560    path = path[:-1]
    561  return path
    562 
    563 
    564 def 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 
    579 def 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 
    615 def 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
    631 sdk_version = %s
    632 sdk_path = %s
    633 vs_version = %s
    634 wdk_dir = %s
    635 runtime_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 
    643 def 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  if sys.argv[1] == 'copy_dlls':
    653    return 0
    654  return commands[sys.argv[1]](*sys.argv[2:])
    655 
    656 
    657 if __name__ == '__main__':
    658  sys.exit(main())