[infra] Update windows build and toolchain setup python scripts.
The scripts originate from Chromium, this CL syncs Dart's copy with Chromium tip of the tree at 6f8f710079b3e363f4fd7ffe3d848384e4b7c816.
Format toolchain/win/BUILD.gn
Change-Id: Ice7ba48bdd102ffe0e25c6ae6068f83cb14169ba
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/253500
Reviewed-by: Alexander Thomas <athom@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
diff --git a/build/find_depot_tools.py b/build/find_depot_tools.py
new file mode 100644
index 0000000..903fc25
--- /dev/null
+++ b/build/find_depot_tools.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python3
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# For Dart/Flutter developers:
+# This file is copied from Chromium:
+# https://cs.chromium.org/chromium/src/build/find_depot_tools.py
+# When updating replace reference to python on the first line with python3.
+#
+"""Small utility function to find depot_tools and add it to the python path.
+
+Will throw an ImportError exception if depot_tools can't be found since it
+imports breakpad.
+
+This can also be used as a standalone script to print out the depot_tools
+directory location.
+"""
+
+from __future__ import print_function
+
+import os
+import sys
+
+
+# Path to //src
+SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
+
+
+def IsRealDepotTools(path):
+ expanded_path = os.path.expanduser(path)
+ return os.path.isfile(os.path.join(expanded_path, 'gclient.py'))
+
+
+def add_depot_tools_to_path():
+ """Search for depot_tools and add it to sys.path."""
+ # First, check if we have a DEPS'd in "depot_tools".
+ deps_depot_tools = os.path.join(SRC, 'third_party', 'depot_tools')
+ if IsRealDepotTools(deps_depot_tools):
+ # Put the pinned version at the start of the sys.path, in case there
+ # are other non-pinned versions already on the sys.path.
+ sys.path.insert(0, deps_depot_tools)
+ return deps_depot_tools
+
+ # Then look if depot_tools is already in PYTHONPATH.
+ for i in sys.path:
+ if i.rstrip(os.sep).endswith('depot_tools') and IsRealDepotTools(i):
+ return i
+ # Then look if depot_tools is in PATH, common case.
+ for i in os.environ['PATH'].split(os.pathsep):
+ if IsRealDepotTools(i):
+ sys.path.append(i.rstrip(os.sep))
+ return i
+ # Rare case, it's not even in PATH, look upward up to root.
+ root_dir = os.path.dirname(os.path.abspath(__file__))
+ previous_dir = os.path.abspath(__file__)
+ while root_dir and root_dir != previous_dir:
+ i = os.path.join(root_dir, 'depot_tools')
+ if IsRealDepotTools(i):
+ sys.path.append(i)
+ return i
+ previous_dir = root_dir
+ root_dir = os.path.dirname(root_dir)
+ print('Failed to find depot_tools', file=sys.stderr)
+ return None
+
+DEPOT_TOOLS_PATH = add_depot_tools_to_path()
+
+# pylint: disable=W0611
+import breakpad
+
+
+def main():
+ if DEPOT_TOOLS_PATH is None:
+ return 1
+ print(DEPOT_TOOLS_PATH)
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/build/toolchain/win/BUILD.gn b/build/toolchain/win/BUILD.gn
index d70d49e..bc54953 100644
--- a/build/toolchain/win/BUILD.gn
+++ b/build/toolchain/win/BUILD.gn
@@ -146,9 +146,10 @@
}
tool("solink") {
- dllname = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" # e.g. foo.dll
- libname =
- "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.lib" # e.g. foo.dll.lib
+ dllname = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}" # e.g.
+ # foo.dll
+ libname = "{{root_out_dir}}/{{target_output_name}}{{output_extension}}.lib" # e.g.
+ # foo.dll.lib
rspfile = "${dllname}.rsp"
link_command = "$python_path $tool_wrapper_path link-wrapper $env False link.exe /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:${dllname}.pdb @$rspfile"
@@ -175,7 +176,9 @@
}
tool("solink_module") {
- dllname = "{{output_dir}}/{{target_output_name}}{{output_extension}}" # e.g. foo.dll
+ dllname =
+ "{{output_dir}}/{{target_output_name}}{{output_extension}}" # e.g.
+ # foo.dll
pdbname = "${dllname}.pdb"
rspfile = "${dllname}.rsp"
@@ -230,8 +233,7 @@
}
tool("copy") {
- command =
- "$python_path $tool_wrapper_path recursive-mirror {{source}} {{output}}"
+ command = "$python_path $tool_wrapper_path recursive-mirror {{source}} {{output}}"
description = "COPY {{source}} {{output}}"
}
}
@@ -246,7 +248,9 @@
visual_studio_path,
windows_sdk_path,
visual_studio_runtime_dirs,
+ "win",
toolchain_arch,
+ "environment." + toolchain_arch,
],
"scope")
diff --git a/build/toolchain/win/setup_toolchain.py b/build/toolchain/win/setup_toolchain.py
index 10b878a..e680ba0 100644
--- a/build/toolchain/win/setup_toolchain.py
+++ b/build/toolchain/win/setup_toolchain.py
@@ -10,63 +10,183 @@
# win tool. The script assumes that the root build directory is the current dir
# and the files will be written to the current directory.
+from __future__ import print_function
+
import errno
+import json
import os
import re
import subprocess
import sys
+sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+import gn_helpers
+
+SCRIPT_DIR = os.path.dirname(__file__)
def _ExtractImportantEnvironment(output_of_set):
"""Extracts environment variables required for the toolchain to run from
a textual dump output by the cmd.exe 'set' command."""
envvars_to_save = (
+ 'cipd_cache_dir', # needed by vpython
+ 'homedrive', # needed by vpython
+ 'homepath', # needed by vpython
'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
'include',
'lib',
'libpath',
+ 'luci_context', # needed by vpython
'path',
'pathext',
'systemroot',
'temp',
'tmp',
+ 'userprofile', # needed by vpython
+ 'vpython_virtualenv_root' # needed by vpython
)
env = {}
+ # This occasionally happens and leads to misleading SYSTEMROOT error messages
+ # if not caught here.
+ if output_of_set.count('=') == 0:
+ raise Exception('Invalid output_of_set. Value is:\n%s' % output_of_set)
for line in output_of_set.splitlines():
for envvar in envvars_to_save:
- if re.match(envvar + '=', line.decode().lower()):
- var, setting = line.decode().split('=', 1)
+ if re.match(envvar + '=', line.lower()):
+ var, setting = line.split('=', 1)
if envvar == 'path':
- # Our own rules (for running gyp-win-tool) and other actions in
- # Chromium rely on python being in the path. Add the path to this
- # python here so that if it's not in the path when ninja is run
- # later, python will still be found.
+ # Our own rules and actions in Chromium rely on python being in the
+ # path. Add the path to this python here so that if it's not in the
+ # path when ninja is run later, python will still be found.
setting = os.path.dirname(sys.executable) + os.pathsep + setting
+ if envvar in ['include', 'lib']:
+ # Make sure that the include and lib paths point to directories that
+ # exist. This ensures a (relatively) clear error message if the
+ # required SDK is not installed.
+ for part in setting.split(';'):
+ if not os.path.exists(part) and len(part) != 0:
+ raise Exception(
+ 'Path "%s" from environment variable "%s" does not exist. '
+ 'Make sure the necessary SDK is installed.' % (part, envvar))
env[var.upper()] = setting
break
- for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
- if required not in env:
- raise Exception('Environment variable "%s" '
- 'required to be set to valid path' % required)
+ if sys.platform in ('win32', 'cygwin'):
+ for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
+ if required not in env:
+ raise Exception('Environment variable "%s" '
+ 'required to be set to valid path' % required)
return env
-def _SetupScript(target_cpu, sdk_dir):
- """Returns a command (with arguments) to be used to set up the
- environment."""
+def _DetectVisualStudioPath():
+ """Return path to the installed Visual Studio.
+ """
+
+ # Use the code in build/vs_toolchain.py to avoid duplicating code.
+ chromium_dir = os.path.abspath(os.path.join(SCRIPT_DIR, '..', '..', '..'))
+ sys.path.append(os.path.join(chromium_dir, 'build'))
+ import vs_toolchain
+ return vs_toolchain.DetectVisualStudioPath()
+
+
+def _LoadEnvFromBat(args):
+ """Given a bat command, runs it and returns env vars set by it."""
+ args = args[:]
+ args.extend(('&&', 'set'))
+ popen = subprocess.Popen(
+ args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ variables, _ = popen.communicate()
+ if popen.returncode != 0:
+ raise Exception('"%s" failed with error %d' % (args, popen.returncode))
+ return variables.decode(errors='ignore')
+
+
+def _LoadToolchainEnv(cpu, toolchain_root, sdk_dir, target_store):
+ """Returns a dictionary with environment variables that must be set while
+ running binaries from the toolchain (e.g. INCLUDE and PATH for cl.exe)."""
# Check if we are running in the SDK command line environment and use
- # the setup script from the SDK if so.
- assert target_cpu in ('x86', 'x64', 'arm64')
+ # the setup script from the SDK if so. |cpu| should be either
+ # 'x86' or 'x64' or 'arm' or 'arm64'.
+ assert cpu in ('x86', 'x64', 'arm', 'arm64')
if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', 1))) and sdk_dir:
- return [os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')),
- '/' + target_cpu]
+ # Load environment from json file.
+ env = os.path.normpath(os.path.join(sdk_dir, 'bin/SetEnv.%s.json' % cpu))
+ env = json.load(open(env))['env']
+ if env['VSINSTALLDIR'] == [["..", "..\\"]]:
+ # Old-style paths were relative to the win_sdk\bin directory.
+ json_relative_dir = os.path.join(sdk_dir, 'bin')
+ else:
+ # New-style paths are relative to the toolchain directory.
+ json_relative_dir = toolchain_root
+ for k in env:
+ entries = [os.path.join(*([json_relative_dir] + e)) for e in env[k]]
+ # clang-cl wants INCLUDE to be ;-separated even on non-Windows,
+ # lld-link wants LIB to be ;-separated even on non-Windows. Path gets :.
+ # The separator for INCLUDE here must match the one used in main() below.
+ sep = os.pathsep if k == 'PATH' else ';'
+ env[k] = sep.join(entries)
+ # PATH is a bit of a special case, it's in addition to the current PATH.
+ env['PATH'] = env['PATH'] + os.pathsep + os.environ['PATH']
+ # Augment with the current env to pick up TEMP and friends.
+ for k in os.environ:
+ if k not in env:
+ env[k] = os.environ[k]
+
+ varlines = []
+ for k in sorted(env.keys()):
+ varlines.append('%s=%s' % (str(k), str(env[k])))
+ variables = '\n'.join(varlines)
+
+ # Check that the json file contained the same environment as the .cmd file.
+ if sys.platform in ('win32', 'cygwin'):
+ script = os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.cmd'))
+ arg = '/' + cpu
+ json_env = _ExtractImportantEnvironment(variables)
+ cmd_env = _ExtractImportantEnvironment(_LoadEnvFromBat([script, arg]))
+ assert _LowercaseDict(json_env) == _LowercaseDict(cmd_env)
else:
+ if 'GYP_MSVS_OVERRIDE_PATH' not in os.environ:
+ os.environ['GYP_MSVS_OVERRIDE_PATH'] = _DetectVisualStudioPath()
# We only support x64-hosted tools.
- # TODO(scottmg|dpranke): Non-depot_tools toolchain: need to get Visual
- # Studio install location from registry.
- return [os.path.normpath(os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'],
- 'VC/Auxiliary/Build/vcvarsall.bat')),
- 'amd64_x86' if target_cpu == 'x86' else 'amd64']
+ script_path = os.path.normpath(os.path.join(
+ os.environ['GYP_MSVS_OVERRIDE_PATH'],
+ 'VC/vcvarsall.bat'))
+ if not os.path.exists(script_path):
+ # vcvarsall.bat for VS 2017 fails if run after running vcvarsall.bat from
+ # VS 2013 or VS 2015. Fix this by clearing the vsinstalldir environment
+ # variable. Since vcvarsall.bat appends to the INCLUDE, LIB, and LIBPATH
+ # environment variables we need to clear those to avoid getting double
+ # entries when vcvarsall.bat has been run before gn gen. vcvarsall.bat
+ # also adds to PATH, but there is no clean way of clearing that and it
+ # doesn't seem to cause problems.
+ if 'VSINSTALLDIR' in os.environ:
+ del os.environ['VSINSTALLDIR']
+ if 'INCLUDE' in os.environ:
+ del os.environ['INCLUDE']
+ if 'LIB' in os.environ:
+ del os.environ['LIB']
+ if 'LIBPATH' in os.environ:
+ del os.environ['LIBPATH']
+ other_path = os.path.normpath(os.path.join(
+ os.environ['GYP_MSVS_OVERRIDE_PATH'],
+ 'VC/Auxiliary/Build/vcvarsall.bat'))
+ if not os.path.exists(other_path):
+ raise Exception('%s is missing - make sure VC++ tools are installed.' %
+ script_path)
+ script_path = other_path
+ cpu_arg = "amd64"
+ if (cpu != 'x64'):
+ # x64 is default target CPU thus any other CPU requires a target set
+ cpu_arg += '_' + cpu
+ args = [script_path, cpu_arg, ]
+ # Store target must come before any SDK version declaration
+ if (target_store):
+ args.append('store')
+ # Explicitly specifying the SDK version to build with to avoid accidentally
+ # building with a new and untested SDK. This should stay in sync with the
+ # packaged toolchain in build/vs_toolchain.py.
+ args.append('10.0.20348.0')
+ variables = _LoadEnvFromBat(args)
+ return _ExtractImportantEnvironment(variables)
def _FormatAsEnvironmentBlock(envvar_dict):
@@ -81,76 +201,114 @@
return block
-def _CopyTool(source_path):
- """Copies the given tool to the current directory, including a warning not
- to edit it."""
- with open(source_path) as source_file:
- tool_source = source_file.readlines()
+def _LowercaseDict(d):
+ """Returns a copy of `d` with both key and values lowercased.
- # Add header and write it out to the current directory (which should be the
- # root build dir).
- with open("gyp-win-tool", 'w') as tool_file:
- tool_file.write(''.join([tool_source[0],
- '# Generated by setup_toolchain.py do not edit.\n']
- + tool_source[1:]))
+ Args:
+ d: dict to lowercase (e.g. {'A': 'BcD'}).
+
+ Returns:
+ A dict with both keys and values lowercased (e.g.: {'a': 'bcd'}).
+ """
+ return {k.lower(): d[k].lower() for k in d}
+
+
+def FindFileInEnvList(env, env_name, separator, file_name, optional=False):
+ parts = env[env_name].split(separator)
+ for path in parts:
+ if os.path.exists(os.path.join(path, file_name)):
+ return os.path.realpath(path)
+ assert optional, "%s is not found in %s:\n%s\nCheck if it is installed." % (
+ file_name, env_name, '\n'.join(parts))
+ return ''
def main():
- if len(sys.argv) != 5:
+ if len(sys.argv) != 7:
print('Usage setup_toolchain.py '
'<visual studio path> <win sdk path> '
- '<runtime dirs> <target_cpu>')
+ '<runtime dirs> <target_os> <target_cpu> '
+ '<environment block name|none>')
sys.exit(2)
+ # toolchain_root and win_sdk_path are only read if the hermetic Windows
+ # toolchain is set, that is if DEPOT_TOOLS_WIN_TOOLCHAIN is not set to 0.
+ # With the hermetic Windows toolchain, the visual studio path in argv[1]
+ # is the root of the Windows toolchain directory.
+ toolchain_root = sys.argv[1]
win_sdk_path = sys.argv[2]
- runtime_dirs = sys.argv[3]
- target_cpu = sys.argv[4]
- cpus = ('x86', 'x64', 'arm64')
+ runtime_dirs = sys.argv[3]
+ target_os = sys.argv[4]
+ target_cpu = sys.argv[5]
+ environment_block_name = sys.argv[6]
+ if (environment_block_name == 'none'):
+ environment_block_name = ''
+
+ if (target_os == 'winuwp'):
+ target_store = True
+ else:
+ target_store = False
+
+ cpus = ('x86', 'x64', 'arm', 'arm64')
assert target_cpu in cpus
vc_bin_dir = ''
+ include = ''
+ lib = ''
# TODO(scottmg|goma): Do we need an equivalent of
# ninja_use_custom_environment_files?
+ def relflag(s): # Make s relative to builddir when cwd and sdk on same drive.
+ try:
+ return os.path.relpath(s).replace('\\', '/')
+ except ValueError:
+ return s
+
+ def q(s): # Quote s if it contains spaces or other weird characters.
+ return s if re.match(r'^[a-zA-Z0-9._/\\:-]*$', s) else '"' + s + '"'
+
for cpu in cpus:
- # Extract environment variables for subprocesses.
- args = _SetupScript(cpu, win_sdk_path)
- args.extend(('&&', 'set'))
- popen = subprocess.Popen(
- args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- stdout_data, stderr_data = popen.communicate()
- if popen.returncode != 0:
- print('Error, got returncode:', popen.returncode)
- print('## stdout:')
- print(stdout_data)
- print('## stderr:')
- print(stderr_data)
- sys.exit(2)
- env = _ExtractImportantEnvironment(stdout_data)
- env['PATH'] = runtime_dirs + ';' + env['PATH']
-
if cpu == target_cpu:
- for path in env['PATH'].split(os.pathsep):
- if os.path.exists(os.path.join(path, 'cl.exe')):
- vc_bin_dir = os.path.realpath(path)
- break
+ # Extract environment variables for subprocesses.
+ env = _LoadToolchainEnv(cpu, toolchain_root, win_sdk_path, target_store)
+ env['PATH'] = runtime_dirs + os.pathsep + env['PATH']
- # The Windows SDK include directories must be first. They both have a sal.h,
- # and the SDK one is newer and the SDK uses some newer features from it not
- # present in the Visual Studio one.
+ vc_bin_dir = FindFileInEnvList(env, 'PATH', os.pathsep, 'cl.exe')
- if win_sdk_path:
- additional_includes = ('{sdk_dir}\\Include\\shared;' +
- '{sdk_dir}\\Include\\um;' +
- '{sdk_dir}\\Include\\winrt;').format(
- sdk_dir=win_sdk_path)
- env['INCLUDE'] = additional_includes + env['INCLUDE']
- env_block = _FormatAsEnvironmentBlock(env)
- with open('environment.' + cpu, 'wb') as f:
- f.write(env_block.encode())
+ # The separator for INCLUDE here must match the one used in
+ # _LoadToolchainEnv() above.
+ include = [p.replace('"', r'\"') for p in env['INCLUDE'].split(';') if p]
+ include = list(map(relflag, include))
- assert vc_bin_dir
- print('vc_bin_dir = "%s"' % vc_bin_dir)
+ lib = [p.replace('"', r'\"') for p in env['LIB'].split(';') if p]
+ lib = list(map(relflag, lib))
+
+ include_I = ' '.join([q('/I' + i) for i in include])
+ include_imsvc = ' '.join([q('-imsvc' + i) for i in include])
+ libpath_flags = ' '.join([q('-libpath:' + i) for i in lib])
+
+ if (environment_block_name != ''):
+ env_block = _FormatAsEnvironmentBlock(env)
+ with open(environment_block_name, 'w') as f:
+ f.write(env_block)
+
+ print('vc_bin_dir = ' + gn_helpers.ToGNString(vc_bin_dir))
+ assert include_I
+ print('include_flags_I = ' + gn_helpers.ToGNString(include_I))
+ assert include_imsvc
+ if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', 1))) and win_sdk_path:
+ print('include_flags_imsvc = ' +
+ gn_helpers.ToGNString(q('/winsysroot' + relflag(toolchain_root))))
+ else:
+ print('include_flags_imsvc = ' + gn_helpers.ToGNString(include_imsvc))
+ print('paths = ' + gn_helpers.ToGNString(env['PATH']))
+ assert libpath_flags
+ print('libpath_flags = ' + gn_helpers.ToGNString(libpath_flags))
+ if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', 1))) and win_sdk_path:
+ print('libpath_lldlink_flags = ' +
+ gn_helpers.ToGNString(q('/winsysroot:' + relflag(toolchain_root))))
+ else:
+ print('libpath_lldlink_flags = ' + gn_helpers.ToGNString(libpath_flags))
if __name__ == '__main__':
diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py
index 6314e5e..9b0a7c8 100644
--- a/build/vs_toolchain.py
+++ b/build/vs_toolchain.py
@@ -1,18 +1,14 @@
#!/usr/bin/env python3
-#
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
+#
# For Dart/Flutter developers:
-# This file keeps the MSVC toolchain up-to-date for Google developers.
-# It is copied from Chromium:
+# This file is copied from Chromium:
# https://cs.chromium.org/chromium/src/build/vs_toolchain.py
-# with modifications that update paths, and remove dependencies on gyp.
-# To update to a new MSVC toolchain, copy the updated script from the Chromium
-# tree, and edit to make it work in the Dart tree by updating paths in the original script.
-
-
+# When updating replace reference to python on the first line with python3.
+#
+from __future__ import print_function
import collections
import glob
@@ -26,21 +22,47 @@
import subprocess
import sys
-
from gn_helpers import ToGNString
+# VS 2019 16.61 with 10.0.20348.0 SDK, 10.0.19041 version of Debuggers
+# with ARM64 libraries and UWP support.
+# See go/chromium-msvc-toolchain for instructions about how to update the
+# toolchain.
+#
+# When updating the toolchain, consider the following areas impacted by the
+# toolchain version:
+#
+# * //base/win/windows_version.cc NTDDI preprocessor check
+# Triggers a compiler error if the available SDK is older than the minimum.
+# * //build/config/win/BUILD.gn NTDDI_VERSION value
+# Affects the availability of APIs in the toolchain headers.
+# * //docs/windows_build_instructions.md mentions of VS or Windows SDK.
+# Keeps the document consistent with the toolchain version.
+TOOLCHAIN_HASH = '1023ce2e82'
+
script_dir = os.path.dirname(os.path.realpath(__file__))
json_data_file = os.path.join(script_dir, 'win_toolchain.json')
-sys.path.insert(0, os.path.join(script_dir, '..', 'tools'))
-
# VS versions are listed in descending order of priority (highest first).
+# The first version is assumed by this script to be the one that is packaged,
+# which makes a difference for the arm64 runtime.
MSVS_VERSIONS = collections.OrderedDict([
- ('2017', '15.0'),
- ('2019', '16.0'),
- ('2022', '17.0'),
+ ('2019', '16.0'), # Default and packaged version of Visual Studio.
+ ('2022', '17.0'),
+ ('2017', '15.0'),
])
+# List of preferred VC toolset version based on MSVS
+# Order is not relevant for this dictionary.
+MSVC_TOOLSET_VERSION = {
+ '2022': 'VC143',
+ '2019': 'VC142',
+ '2017': 'VC141',
+}
+
+def _HostIsWindows():
+ """Returns True if running on a Windows host (including under cygwin)."""
+ return sys.platform in ('win32', 'cygwin')
def SetEnvironmentAndGetRuntimeDllDirs():
"""Sets up os.environ to use the depot_tools VS toolchain with gyp, and
@@ -56,7 +78,7 @@
bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
# When running on a non-Windows host, only do this if the SDK has explicitly
# been downloaded before (in which case json_data_file will exist).
- if ((sys.platform in ('win32', 'cygwin') or os.path.exists(json_data_file))
+ if ((_HostIsWindows() or os.path.exists(json_data_file))
and depot_tools_win_toolchain):
if ShouldUpdateToolchain():
if len(sys.argv) > 1 and sys.argv[1] == 'update':
@@ -71,8 +93,6 @@
toolchain = toolchain_data['path']
version = toolchain_data['version']
win_sdk = toolchain_data.get('win_sdk')
- if not win_sdk:
- win_sdk = toolchain_data['win8sdk']
wdk = toolchain_data['wdk']
# TODO(scottmg): The order unfortunately matters in these. They should be
# split into separate keys for x64/x86/arm64. (See CopyDlls call below).
@@ -85,7 +105,6 @@
vs_runtime_dll_dirs.append('Arm64Unused')
os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
- os.environ['GYP_MSVS_VERSION'] = version
os.environ['WINDOWSSDKDIR'] = win_sdk
os.environ['WDK_DIR'] = wdk
@@ -95,8 +114,6 @@
elif sys.platform == 'win32' and not depot_tools_win_toolchain:
if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ:
os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath()
- if not 'GYP_MSVS_VERSION' in os.environ:
- os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
# When using an installed toolchain these files aren't needed in the output
# directory in order to run binaries locally, but they are needed in order
@@ -126,12 +143,12 @@
contents of the registry key's value, or None on failure. Throws
ImportError if _winreg is unavailable.
"""
- import winreg
+ import _winreg
try:
root, subkey = key.split('\\', 1)
assert root == 'HKLM' # Only need HKLM for now.
- with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
- return winreg.QueryValueEx(hkey, value)[0]
+ with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
+ return _winreg.QueryValueEx(hkey, value)[0]
except WindowsError:
return None
@@ -146,31 +163,38 @@
def GetVisualStudioVersion():
"""Return best available version of Visual Studio.
"""
-
- env_version = os.environ.get('GYP_MSVS_VERSION')
- if env_version:
- return env_version
-
supported_versions = list(MSVS_VERSIONS.keys())
# VS installed in depot_tools for Googlers
if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))):
- return list(supported_versions)[0]
+ return supported_versions[0]
# VS installed in system for external developers
supported_versions_str = ', '.join('{} ({})'.format(v,k)
- for k,v in list(MSVS_VERSIONS.items()))
+ for k,v in MSVS_VERSIONS.items())
available_versions = []
for version in supported_versions:
- for path in (
- os.environ.get('vs%s_install' % version),
- os.path.expandvars('%ProgramFiles(x86)%' +
- '/Microsoft Visual Studio/%s' % version),
- os.path.expandvars('%ProgramFiles%' +
- '/Microsoft Visual Studio/%s' % version)):
- if path and os.path.exists(path):
- available_versions.append(version)
- break
+ # Checking vs%s_install environment variables.
+ # For example, vs2019_install could have the value
+ # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community".
+ # Only vs2017_install, vs2019_install and vs2022_install are supported.
+ path = os.environ.get('vs%s_install' % version)
+ if path and os.path.exists(path):
+ available_versions.append(version)
+ break
+ # Detecting VS under possible paths.
+ if version >= '2022':
+ program_files_path_variable = '%ProgramFiles%'
+ else:
+ program_files_path_variable = '%ProgramFiles(x86)%'
+ path = os.path.expandvars(program_files_path_variable +
+ '/Microsoft Visual Studio/%s' % version)
+ if path and any(
+ os.path.exists(os.path.join(path, edition))
+ for edition in ('Enterprise', 'Professional', 'Community', 'Preview',
+ 'BuildTools')):
+ available_versions.append(version)
+ break
if not available_versions:
raise Exception('No supported Visual Studio can be found.'
@@ -179,7 +203,7 @@
def DetectVisualStudioPath():
- """Return path to the GYP_MSVS_VERSION of Visual Studio.
+ """Return path to the installed Visual Studio.
"""
# Note that this code is used from
@@ -190,18 +214,30 @@
# the registry. For details see:
# https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/
# For now we use a hardcoded default with an environment variable override.
- possible_install_paths = (
- os.path.expandvars('%s/Microsoft Visual Studio/%s/%s' %
- (program_path_var, version_as_year, product))
- for program_path_var in ('%ProgramFiles%', '%ProgramFiles(x86)%')
- for product in ('Enterprise', 'Professional', 'Community', 'Preview'))
- for path in (
- os.environ.get('vs%s_install' % version_as_year), *possible_install_paths):
+ if version_as_year >= '2022':
+ program_files_path_variable = '%ProgramFiles%'
+ else:
+ program_files_path_variable = '%ProgramFiles(x86)%'
+ for path in (os.environ.get('vs%s_install' % version_as_year),
+ os.path.expandvars(program_files_path_variable +
+ '/Microsoft Visual Studio/%s/Enterprise' %
+ version_as_year),
+ os.path.expandvars(program_files_path_variable +
+ '/Microsoft Visual Studio/%s/Professional' %
+ version_as_year),
+ os.path.expandvars(program_files_path_variable +
+ '/Microsoft Visual Studio/%s/Community' %
+ version_as_year),
+ os.path.expandvars(program_files_path_variable +
+ '/Microsoft Visual Studio/%s/Preview' %
+ version_as_year),
+ os.path.expandvars(program_files_path_variable +
+ '/Microsoft Visual Studio/%s/BuildTools' %
+ version_as_year)):
if path and os.path.exists(path):
return path
- raise Exception('Visual Studio Version %s (from GYP_MSVS_VERSION)'
- ' not found.' % version_as_year)
+ raise Exception('Visual Studio Version %s not found.' % version_as_year)
def _CopyRuntimeImpl(target, source, verbose=True):
@@ -243,21 +279,30 @@
list_of_str_versions.sort(key=to_number_sequence, reverse=True)
-def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, dll_pattern, suffix):
+
+def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix):
"""Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
exist, but the target directory does exist."""
if target_cpu == 'arm64':
# Windows ARM64 VCRuntime is located at {toolchain_root}/VC/Redist/MSVC/
- # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC141.CRT/.
+ # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/.
+ # Select VC toolset directory based on Visual Studio version
vc_redist_root = FindVCRedistRoot()
if suffix.startswith('.'):
+ vc_toolset_dir = 'Microsoft.{}.CRT' \
+ .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()])
source_dir = os.path.join(vc_redist_root,
- 'arm64', 'Microsoft.VC141.CRT')
+ 'arm64', vc_toolset_dir)
else:
+ vc_toolset_dir = 'Microsoft.{}.DebugCRT' \
+ .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()])
source_dir = os.path.join(vc_redist_root, 'debug_nonredist',
- 'arm64', 'Microsoft.VC141.DebugCRT')
- for file_part in ('msvcp', 'vccorlib', 'vcruntime'):
- dll = dll_pattern % file_part
+ 'arm64', vc_toolset_dir)
+ file_parts = ('msvcp140', 'vccorlib140', 'vcruntime140')
+ if target_cpu == 'x64' and GetVisualStudioVersion() != '2017':
+ file_parts = file_parts + ('vcruntime140_1', )
+ for file_part in file_parts:
+ dll = file_part + suffix
target = os.path.join(target_dir, dll)
source = os.path.join(source_dir, dll)
_CopyRuntimeImpl(target, source)
@@ -317,14 +362,13 @@
assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ)
vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'],
'VC', component, 'MSVC')
- vc_component_msvc_contents = os.listdir(vc_component_msvc_root)
+ vc_component_msvc_contents = glob.glob(
+ os.path.join(vc_component_msvc_root, '14.*'))
# Select the most recent toolchain if there are several.
_SortByHighestVersionNumberFirst(vc_component_msvc_contents)
for directory in vc_component_msvc_contents:
- if not os.path.isdir(os.path.join(vc_component_msvc_root, directory)):
- continue
- if re.match(r'14\.\d+\.\d+', directory):
- return os.path.join(vc_component_msvc_root, directory)
+ if os.path.isdir(directory):
+ return directory
raise Exception('Unable to find the VC %s directory.' % component)
@@ -342,8 +386,7 @@
directory does exist. Handles VS 2015, 2017 and 2019."""
suffix = 'd.dll' if debug else '.dll'
# VS 2015, 2017 and 2019 use the same CRT DLLs.
- _CopyUCRTRuntime(target_dir, source_dir, target_cpu, '%s140' + suffix,
- suffix)
+ _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix)
def CopyDlls(target_dir, configuration, target_cpu):
@@ -394,17 +437,20 @@
# List of debug files that should be copied, the first element of the tuple is
# the name of the file and the second indicates if it's optional.
debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)]
+ # The UCRT is not a redistributable component on arm64.
+ if target_cpu != 'arm64':
+ debug_files.extend([('api-ms-win-downlevel-kernel32-l2-1-0.dll', False),
+ ('api-ms-win-eventing-provider-l1-1-0.dll', False)])
for debug_file, is_optional in debug_files:
full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file)
if not os.path.exists(full_path):
if is_optional:
continue
else:
- # TODO(crbug.com/773476): remove version requirement.
- raise Exception('%s not found in "%s"\r\nYou must install the '
- '"Debugging Tools for Windows" feature from the Windows'
- ' 10 SDK.'
- % (debug_file, full_path))
+ raise Exception('%s not found in "%s"\r\nYou must install '
+ 'Windows 10 SDK version 10.0.20348.0 including the '
+ '"Debugging Tools for Windows" feature.' %
+ (debug_file, full_path))
target_path = os.path.join(target_dir, debug_file)
_CopyRuntimeImpl(target_path, full_path)
@@ -412,17 +458,10 @@
def _GetDesiredVsToolchainHashes():
"""Load a list of SHA1s corresponding to the toolchains that we want installed
to build with."""
- env_version = GetVisualStudioVersion()
- if env_version == '2017':
- # VS 2017 Update 9 (15.9.12) with 10.0.18362 SDK, 10.0.17763 version of
- # Debuggers, and 10.0.17134 version of d3dcompiler_47.dll, with ARM64
- # libraries.
- toolchain_hash = '418b3076791776573a815eb298c8aa590307af63'
- # Third parties that do not have access to the canonical toolchain can map
- # canonical toolchain version to their own toolchain versions.
- toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % toolchain_hash
- return [os.environ.get(toolchain_hash_mapping_key, toolchain_hash)]
- raise Exception('Unsupported VS version %s' % env_version)
+ # Third parties that do not have access to the canonical toolchain can map
+ # canonical toolchain version to their own toolchain versions.
+ toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % TOOLCHAIN_HASH
+ return [os.environ.get(toolchain_hash_mapping_key, TOOLCHAIN_HASH)]
def ShouldUpdateToolchain():
@@ -453,8 +492,7 @@
depot_tools_win_toolchain = \
bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
- if ((sys.platform in ('win32', 'cygwin') or force) and
- depot_tools_win_toolchain):
+ if (_HostIsWindows() or force) and depot_tools_win_toolchain:
import find_depot_tools
depot_tools_path = find_depot_tools.add_depot_tools_to_path()
@@ -482,9 +520,6 @@
subprocess.check_call([
ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs', toolchain_dir])
- # Necessary so that get_toolchain_if_necessary.py will put the VS toolkit
- # in the correct directory.
- os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
get_toolchain_args = [
sys.executable,
os.path.join(depot_tools_path,