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    # Checking vs%s_install environment variables.
155    # For example, vs2019_install could have the value
156    # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community".
157    # Only vs2017_install and vs2019_install are supported.
158    path = os.environ.get('vs%s_install' % version)
159    if path and os.path.exists(path):
160      available_versions.append(version)
161      break
162    # Detecting VS under possible paths.
163    path = os.path.expandvars('%ProgramFiles(x86)%' +
164                              '/Microsoft Visual Studio/%s' % version)
165    if path and any(
166        os.path.exists(os.path.join(path, edition))
167        for edition in ('Enterprise', 'Professional', 'Community', 'Preview',
168                        'BuildTools')):
169      available_versions.append(version)
170      break
171
172  if not available_versions:
173    raise Exception('No supported Visual Studio can be found.'
174                    ' Supported versions are: %s.' % supported_versions_str)
175  return available_versions[0]
176
177
178def DetectVisualStudioPath():
179  """Return path to the installed Visual Studio.
180  """
181
182  # Note that this code is used from
183  # build/toolchain/win/setup_toolchain.py as well.
184  version_as_year = GetVisualStudioVersion()
185
186  # The VC++ >=2017 install location needs to be located using COM instead of
187  # the registry. For details see:
188  # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/
189  # For now we use a hardcoded default with an environment variable override.
190  for path in (
191      os.environ.get('vs%s_install' % version_as_year),
192      os.path.expandvars('%ProgramFiles(x86)%' +
193                         '/Microsoft Visual Studio/%s/Enterprise' %
194                         version_as_year),
195      os.path.expandvars('%ProgramFiles(x86)%' +
196                         '/Microsoft Visual Studio/%s/Professional' %
197                         version_as_year),
198      os.path.expandvars('%ProgramFiles(x86)%' +
199                         '/Microsoft Visual Studio/%s/Community' %
200                         version_as_year),
201      os.path.expandvars('%ProgramFiles(x86)%' +
202                         '/Microsoft Visual Studio/%s/Preview' %
203                         version_as_year),
204      os.path.expandvars('%ProgramFiles(x86)%' +
205                         '/Microsoft Visual Studio/%s/BuildTools' %
206                         version_as_year)):
207    if path and os.path.exists(path):
208      return path
209
210  raise Exception('Visual Studio Version %s not found.' % version_as_year)
211
212
213def _CopyRuntimeImpl(target, source, verbose=True):
214  """Copy |source| to |target| if it doesn't already exist or if it needs to be
215  updated (comparing last modified time as an approximate float match as for
216  some reason the values tend to differ by ~1e-07 despite being copies of the
217  same file... https://crbug.com/603603).
218  """
219  if (os.path.isdir(os.path.dirname(target)) and
220      (not os.path.isfile(target) or
221       abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)):
222    if verbose:
223      print('Copying %s to %s...' % (source, target))
224    if os.path.exists(target):
225      # Make the file writable so that we can delete it now, and keep it
226      # readable.
227      os.chmod(target, stat.S_IWRITE | stat.S_IREAD)
228      os.unlink(target)
229    shutil.copy2(source, target)
230    # Make the file writable so that we can overwrite or delete it later,
231    # keep it readable.
232    os.chmod(target, stat.S_IWRITE | stat.S_IREAD)
233
234def _SortByHighestVersionNumberFirst(list_of_str_versions):
235  """This sorts |list_of_str_versions| according to version number rules
236  so that version "1.12" is higher than version "1.9". Does not work
237  with non-numeric versions like 1.4.a8 which will be higher than
238  1.4.a12. It does handle the versions being embedded in file paths.
239  """
240  def to_int_if_int(x):
241    try:
242      return int(x)
243    except ValueError:
244      return x
245
246  def to_number_sequence(x):
247    part_sequence = re.split(r'[\\/\.]', x)
248    return [to_int_if_int(x) for x in part_sequence]
249
250  list_of_str_versions.sort(key=to_number_sequence, reverse=True)
251
252
253def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix):
254  """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
255  exist, but the target directory does exist."""
256  if target_cpu == 'arm64':
257    # Windows ARM64 VCRuntime is located at {toolchain_root}/VC/Redist/MSVC/
258    # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/.
259    # Select VC toolset directory based on Visual Studio version
260    vc_redist_root = FindVCRedistRoot()
261    if suffix.startswith('.'):
262      vc_toolset_dir = 'Microsoft.{}.CRT' \
263         .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()])
264      source_dir = os.path.join(vc_redist_root,
265                                'arm64', vc_toolset_dir)
266    else:
267      vc_toolset_dir = 'Microsoft.{}.DebugCRT' \
268         .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()])
269      source_dir = os.path.join(vc_redist_root, 'debug_nonredist',
270                                'arm64', vc_toolset_dir)
271  file_parts = ('msvcp140', 'vccorlib140', 'vcruntime140')
272  if target_cpu == 'x64' and GetVisualStudioVersion() != '2017':
273    file_parts = file_parts + ('vcruntime140_1', )
274  for file_part in file_parts:
275    dll = file_part + suffix
276    target = os.path.join(target_dir, dll)
277    source = os.path.join(source_dir, dll)
278    _CopyRuntimeImpl(target, source)
279  # Copy the UCRT files from the Windows SDK. This location includes the
280  # api-ms-win-crt-*.dll files that are not found in the Windows directory.
281  # These files are needed for component builds. If WINDOWSSDKDIR is not set
282  # use the default SDK path. This will be the case when
283  # DEPOT_TOOLS_WIN_TOOLCHAIN=0 and vcvarsall.bat has not been run.
284  win_sdk_dir = os.path.normpath(
285      os.environ.get('WINDOWSSDKDIR',
286                     os.path.expandvars('%ProgramFiles(x86)%'
287                                        '\\Windows Kits\\10')))
288  # ARM64 doesn't have a redist for the ucrt DLLs because they are always
289  # present in the OS.
290  if target_cpu != 'arm64':
291    # Starting with the 10.0.17763 SDK the ucrt files are in a version-named
292    # directory - this handles both cases.
293    redist_dir = os.path.join(win_sdk_dir, 'Redist')
294    version_dirs = glob.glob(os.path.join(redist_dir, '10.*'))
295    if len(version_dirs) > 0:
296      _SortByHighestVersionNumberFirst(version_dirs)
297      redist_dir = version_dirs[0]
298    ucrt_dll_dirs = os.path.join(redist_dir, 'ucrt', 'DLLs', target_cpu)
299    ucrt_files = glob.glob(os.path.join(ucrt_dll_dirs, 'api-ms-win-*.dll'))
300    assert len(ucrt_files) > 0
301    for ucrt_src_file in ucrt_files:
302      file_part = os.path.basename(ucrt_src_file)
303      ucrt_dst_file = os.path.join(target_dir, file_part)
304      _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False)
305  # We must copy ucrtbase.dll for x64/x86, and ucrtbased.dll for all CPU types.
306  if target_cpu != 'arm64' or not suffix.startswith('.'):
307    if not suffix.startswith('.'):
308      # ucrtbased.dll is located at {win_sdk_dir}/bin/{a.b.c.d}/{target_cpu}/
309      # ucrt/.
310      sdk_bin_root = os.path.join(win_sdk_dir, 'bin')
311      sdk_bin_sub_dirs = glob.glob(os.path.join(sdk_bin_root, '10.*'))
312      # Select the most recent SDK if there are multiple versions installed.
313      _SortByHighestVersionNumberFirst(sdk_bin_sub_dirs)
314      for directory in sdk_bin_sub_dirs:
315        sdk_redist_root_version = os.path.join(sdk_bin_root, directory)
316        if not os.path.isdir(sdk_redist_root_version):
317          continue
318        source_dir = os.path.join(sdk_redist_root_version, target_cpu, 'ucrt')
319        break
320    _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix),
321                     os.path.join(source_dir, 'ucrtbase' + suffix))
322
323
324def FindVCComponentRoot(component):
325  """Find the most recent Tools or Redist or other directory in an MSVC install.
326  Typical results are {toolchain_root}/VC/{component}/MSVC/{x.y.z}. The {x.y.z}
327  version number part changes frequently so the highest version number found is
328  used.
329  """
330
331  SetEnvironmentAndGetRuntimeDllDirs()
332  assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ)
333  vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'],
334      'VC', component, 'MSVC')
335  vc_component_msvc_contents = os.listdir(vc_component_msvc_root)
336  # Select the most recent toolchain if there are several.
337  _SortByHighestVersionNumberFirst(vc_component_msvc_contents)
338  for directory in vc_component_msvc_contents:
339    if not os.path.isdir(os.path.join(vc_component_msvc_root, directory)):
340      continue
341    if re.match(r'14\.\d+\.\d+', directory):
342      return os.path.join(vc_component_msvc_root, directory)
343  raise Exception('Unable to find the VC %s directory.' % component)
344
345
346def FindVCRedistRoot():
347  """In >=VS2017, Redist binaries are located in
348  {toolchain_root}/VC/Redist/MSVC/{x.y.z}/{target_cpu}/.
349
350  This returns the '{toolchain_root}/VC/Redist/MSVC/{x.y.z}/' path.
351  """
352  return FindVCComponentRoot('Redist')
353
354
355def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
356  """Copy the VS runtime DLLs, only if the target doesn't exist, but the target
357  directory does exist. Handles VS 2015, 2017 and 2019."""
358  suffix = 'd.dll' if debug else '.dll'
359  # VS 2015, 2017 and 2019 use the same CRT DLLs.
360  _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix)
361
362
363def CopyDlls(target_dir, configuration, target_cpu):
364  """Copy the VS runtime DLLs into the requested directory as needed.
365
366  configuration is one of 'Debug' or 'Release'.
367  target_cpu is one of 'x86', 'x64' or 'arm64'.
368
369  The debug configuration gets both the debug and release DLLs; the
370  release config only the latter.
371  """
372  vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
373  if not vs_runtime_dll_dirs:
374    return
375
376  x64_runtime, x86_runtime, arm64_runtime = vs_runtime_dll_dirs
377  if target_cpu == 'x64':
378    runtime_dir = x64_runtime
379  elif target_cpu == 'x86':
380    runtime_dir = x86_runtime
381  elif target_cpu == 'arm64':
382    runtime_dir = arm64_runtime
383  else:
384    raise Exception('Unknown target_cpu: ' + target_cpu)
385  _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False)
386  if configuration == 'Debug':
387    _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True)
388  _CopyDebugger(target_dir, target_cpu)
389
390
391def _CopyDebugger(target_dir, target_cpu):
392  """Copy dbghelp.dll and dbgcore.dll into the requested directory as needed.
393
394  target_cpu is one of 'x86', 'x64' or 'arm64'.
395
396  dbghelp.dll is used when Chrome needs to symbolize stacks. Copying this file
397  from the SDK directory avoids using the system copy of dbghelp.dll which then
398  ensures compatibility with recent debug information formats, such as VS
399  2017 /debug:fastlink PDBs.
400
401  dbgcore.dll is needed when using some functions from dbghelp.dll (like
402  MinidumpWriteDump).
403  """
404  win_sdk_dir = SetEnvironmentAndGetSDKDir()
405  if not win_sdk_dir:
406    return
407
408  # List of debug files that should be copied, the first element of the tuple is
409  # the name of the file and the second indicates if it's optional.
410  debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)]
411  # The UCRT is not a redistributable component on arm64.
412  if target_cpu != 'arm64':
413    debug_files.extend([('api-ms-win-downlevel-kernel32-l2-1-0.dll', False),
414                        ('api-ms-win-eventing-provider-l1-1-0.dll', False)])
415  for debug_file, is_optional in debug_files:
416    full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file)
417    if not os.path.exists(full_path):
418      if is_optional:
419        continue
420      else:
421        raise Exception('%s not found in "%s"\r\nYou must install'
422                        'Windows 10 SDK version 10.0.19041.0 including the '
423                        '"Debugging Tools for Windows" feature.' %
424                        (debug_file, full_path))
425    target_path = os.path.join(target_dir, debug_file)
426    _CopyRuntimeImpl(target_path, full_path)
427
428
429def _GetDesiredVsToolchainHashes():
430  """Load a list of SHA1s corresponding to the toolchains that we want installed
431  to build with.
432
433  When updating the toolchain, consider the following areas impacted by the
434  toolchain version:
435
436  * //base/win/windows_version.cc NTDDI preprocessor check
437    Triggers a compiler error if the available SDK is older than the minimum.
438  * //build/config/win/BUILD.gn NTDDI_VERSION value
439    Affects the availability of APIs in the toolchain headers.
440  * //docs/windows_build_instructions.md mentions of VS or Windows SDK.
441    Keeps the document consistent with the toolchain version.
442  """
443  # VS 2019 16.61 with 10.0.19041 SDK, and 10.0.17134 version of
444  # d3dcompiler_47.dll, with ARM64 libraries and UWP support.
445  # See go/chromium-msvc-toolchain for instructions about how to update the
446  # toolchain.
447  toolchain_hash = 'a687d8e2e4114d9015eb550e1b156af21381faac'
448  # Third parties that do not have access to the canonical toolchain can map
449  # canonical toolchain version to their own toolchain versions.
450  toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % toolchain_hash
451  return [os.environ.get(toolchain_hash_mapping_key, toolchain_hash)]
452
453
454def ShouldUpdateToolchain():
455  """Check if the toolchain should be upgraded."""
456  if not os.path.exists(json_data_file):
457    return True
458  with open(json_data_file, 'r') as tempf:
459    toolchain_data = json.load(tempf)
460  version = toolchain_data['version']
461  env_version = GetVisualStudioVersion()
462  # If there's a mismatch between the version set in the environment and the one
463  # in the json file then the toolchain should be updated.
464  return version != env_version
465
466
467def Update(force=False, no_download=False):
468  """Requests an update of the toolchain to the specific hashes we have at
469  this revision. The update outputs a .json of the various configuration
470  information required to pass to gyp which we use in |GetToolchainDir()|.
471  If no_download is true then the toolchain will be configured if present but
472  will not be downloaded.
473  """
474  if force != False and force != '--force':
475    print('Unknown parameter "%s"' % force, file=sys.stderr)
476    return 1
477  if force == '--force' or os.path.exists(json_data_file):
478    force = True
479
480  depot_tools_win_toolchain = \
481      bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
482  if (_HostIsWindows() or force) and depot_tools_win_toolchain:
483    import find_depot_tools
484    depot_tools_path = find_depot_tools.add_depot_tools_to_path()
485
486    # On Linux, the file system is usually case-sensitive while the Windows
487    # SDK only works on case-insensitive file systems.  If it doesn't already
488    # exist, set up a ciopfs fuse mount to put the SDK in a case-insensitive
489    # part of the file system.
490    toolchain_dir = os.path.join(depot_tools_path, 'win_toolchain', 'vs_files')
491    # For testing this block, unmount existing mounts with
492    # fusermount -u third_party/depot_tools/win_toolchain/vs_files
493    if sys.platform.startswith('linux') and not os.path.ismount(toolchain_dir):
494      import distutils.spawn
495      ciopfs = distutils.spawn.find_executable('ciopfs')
496      if not ciopfs:
497        # ciopfs not found in PATH; try the one downloaded from the DEPS hook.
498        ciopfs = os.path.join(script_dir, 'ciopfs')
499      if not os.path.isdir(toolchain_dir):
500        os.mkdir(toolchain_dir)
501      if not os.path.isdir(toolchain_dir + '.ciopfs'):
502        os.mkdir(toolchain_dir + '.ciopfs')
503      # Without use_ino, clang's #pragma once and Wnonportable-include-path
504      # both don't work right, see https://llvm.org/PR34931
505      # use_ino doesn't slow down builds, so it seems there's no drawback to
506      # just using it always.
507      subprocess.check_call([
508          ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs', toolchain_dir])
509
510    get_toolchain_args = [
511        sys.executable,
512        os.path.join(depot_tools_path,
513                    'win_toolchain',
514                    'get_toolchain_if_necessary.py'),
515        '--output-json', json_data_file,
516      ] + _GetDesiredVsToolchainHashes()
517    if force:
518      get_toolchain_args.append('--force')
519    if no_download:
520      get_toolchain_args.append('--no-download')
521    subprocess.check_call(get_toolchain_args)
522
523  return 0
524
525
526def NormalizePath(path):
527  while path.endswith('\\'):
528    path = path[:-1]
529  return path
530
531
532def SetEnvironmentAndGetSDKDir():
533  """Gets location information about the current sdk (must have been
534  previously updated by 'update'). This is used for the GN build."""
535  SetEnvironmentAndGetRuntimeDllDirs()
536
537  # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
538  if not 'WINDOWSSDKDIR' in os.environ:
539    default_sdk_path = os.path.expandvars('%ProgramFiles(x86)%'
540                                          '\\Windows Kits\\10')
541    if os.path.isdir(default_sdk_path):
542      os.environ['WINDOWSSDKDIR'] = default_sdk_path
543
544  return NormalizePath(os.environ['WINDOWSSDKDIR'])
545
546
547def GetToolchainDir():
548  """Gets location information about the current toolchain (must have been
549  previously updated by 'update'). This is used for the GN build."""
550  runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
551  win_sdk_dir = SetEnvironmentAndGetSDKDir()
552
553  print('''vs_path = %s
554sdk_path = %s
555vs_version = %s
556wdk_dir = %s
557runtime_dirs = %s
558''' % (ToGNString(NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH'])),
559       ToGNString(win_sdk_dir), ToGNString(GetVisualStudioVersion()),
560       ToGNString(NormalizePath(os.environ.get('WDK_DIR', ''))),
561       ToGNString(os.path.pathsep.join(runtime_dll_dirs or ['None']))))
562
563
564def main():
565  commands = {
566      'update': Update,
567      'get_toolchain_dir': GetToolchainDir,
568      'copy_dlls': CopyDlls,
569  }
570  if len(sys.argv) < 2 or sys.argv[1] not in commands:
571    print('Expected one of: %s' % ', '.join(commands), file=sys.stderr)
572    return 1
573  if sys.argv[1] == 'copy_dlls':
574    return 0
575  return commands[sys.argv[1]](*sys.argv[2:])
576
577
578if __name__ == '__main__':
579  sys.exit(main())
580