[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>
This commit is contained in:
Alexander Aprelev 2022-08-03 16:30:56 +00:00 committed by Commit Bot
parent 63e170a242
commit a6f56aec88
4 changed files with 440 additions and 162 deletions

81
build/find_depot_tools.py Normal file
View file

@ -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())

View file

@ -146,9 +146,10 @@ template("msvc_toolchain") {
}
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 @@ template("msvc_toolchain") {
}
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 @@ template("msvc_toolchain") {
}
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 @@ template("win_toolchains") {
visual_studio_path,
windows_sdk_path,
visual_studio_runtime_dirs,
"win",
toolchain_arch,
"environment." + toolchain_arch,
],
"scope")

View file

@ -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 @@ def _FormatAsEnvironmentBlock(envvar_dict):
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__':

View file

@ -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 stat
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 @@ def SetEnvironmentAndGetRuntimeDllDirs():
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 @@ def SetEnvironmentAndGetRuntimeDllDirs():
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 @@ def SetEnvironmentAndGetRuntimeDllDirs():
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 @@ def SetEnvironmentAndGetRuntimeDllDirs():
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 @@ def _RegistryGetValueUsingWinReg(key, value):
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 _RegistryGetValue(key, value):
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 GetVisualStudioVersion():
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 @@ def DetectVisualStudioPath():
# 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 @@ def _SortByHighestVersionNumberFirst(list_of_str_versions):
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 @@ def FindVCComponentRoot(component):
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 @@ def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
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 @@ def _CopyDebugger(target_dir, target_cpu):
# 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 _CopyDebugger(target_dir, target_cpu):
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 @@ def Update(force=False, no_download=False):
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 @@ def Update(force=False, no_download=False):
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,