blob: 5ac59ba070a19dd7efd0352a2f6b0c0be42ae168 [file] [log] [blame]
Alexander Thomasb5c63ce2021-04-15 10:10:20 +00001#!/usr/bin/env python3
John McCutchan36af9942016-07-01 11:09:28 -07002# 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
Zach Anderson381c5062017-09-28 09:25:37 +00006# For Dart developers:
7# This file keeps the MSVC toolchain up-to-date for Google developers.
8# It is copied from Chromium:
9# https://cs.chromium.org/chromium/src/build/vs_toolchain.py
Alexander Aprelev521a1062019-07-31 20:01:01 +000010# with modifications that update paths and remove dependencies on gyp.
Zach Anderson381c5062017-09-28 09:25:37 +000011# To update to a new MSVC toolchain, copy the updated script from the Chromium
Alexander Aprelev521a1062019-07-31 20:01:01 +000012# tree, edit to make it work in the Dart tree by updating paths in the original script.
Zach Anderson381c5062017-09-28 09:25:37 +000013
Alexander Aprelev521a1062019-07-31 20:01:01 +000014from __future__ import print_function
15
16import collections
Zach Anderson9a6797e2016-10-04 09:19:07 -070017import glob
John McCutchan36af9942016-07-01 11:09:28 -070018import json
19import os
20import pipes
Zach Anderson9a6797e2016-10-04 09:19:07 -070021import platform
Zach Anderson381c5062017-09-28 09:25:37 +000022import re
John McCutchan36af9942016-07-01 11:09:28 -070023import shutil
Zach Anderson9a6797e2016-10-04 09:19:07 -070024import stat
John McCutchan36af9942016-07-01 11:09:28 -070025import subprocess
26import sys
Alexander Thomasfd7b86e2018-11-18 22:06:49 +000027
Alexander Apreleve7d71be2018-09-26 17:09:36 +000028from gn_helpers import ToGNString
John McCutchan36af9942016-07-01 11:09:28 -070029
Alexander Thomasb5c63ce2021-04-15 10:10:20 +000030# VS 2019 16.61 with 10.0.19041 SDK, and 10.0.17134 version of
31# d3dcompiler_47.dll, with ARM64 libraries and UWP support.
32# See go/chromium-msvc-toolchain for instructions about how to update the
33# toolchain.
34#
35# When updating the toolchain, consider the following areas impacted by the
36# toolchain version:
37#
38# * //base/win/windows_version.cc NTDDI preprocessor check
39# Triggers a compiler error if the available SDK is older than the minimum.
40# * //build/config/win/BUILD.gn NTDDI_VERSION value
41# Affects the availability of APIs in the toolchain headers.
42# * //docs/windows_build_instructions.md mentions of VS or Windows SDK.
43# Keeps the document consistent with the toolchain version.
44TOOLCHAIN_HASH = '20d5f2553f'
45
John McCutchan36af9942016-07-01 11:09:28 -070046script_dir = os.path.dirname(os.path.realpath(__file__))
Alexander Thomasb5c63ce2021-04-15 10:10:20 +000047dart_src = os.path.abspath(os.path.join(script_dir, os.pardir))
48sys.path.insert(0, os.path.join(dart_src, 'tools'))
John McCutchan36af9942016-07-01 11:09:28 -070049json_data_file = os.path.join(script_dir, 'win_toolchain.json')
50
Alexander Aprelev521a1062019-07-31 20:01:01 +000051# VS versions are listed in descending order of priority (highest first).
52MSVS_VERSIONS = collections.OrderedDict([
Nate Bosch55f81f22019-08-05 20:34:31 +000053 ('2019', '16.0'),
Alexander Thomasb5c63ce2021-04-15 10:10:20 +000054 ('2017', '15.0'),
Alexander Aprelev521a1062019-07-31 20:01:01 +000055])
Zach Anderson9a6797e2016-10-04 09:19:07 -070056
Alexander Thomasb5c63ce2021-04-15 10:10:20 +000057# List of preferred VC toolset version based on MSVS
58MSVC_TOOLSET_VERSION = {
59 '2019': 'VC142',
60 '2017': 'VC141',
61}
62
63
64def _HostIsWindows():
65 """Returns True if running on a Windows host (including under cygwin)."""
66 return sys.platform in ('win32', 'cygwin')
67
Zach Anderson9a6797e2016-10-04 09:19:07 -070068
John McCutchan36af9942016-07-01 11:09:28 -070069def SetEnvironmentAndGetRuntimeDllDirs():
Nate Bosch55f81f22019-08-05 20:34:31 +000070 """Sets up os.environ to use the depot_tools VS toolchain with gyp, and
Alexander Aprelev521a1062019-07-31 20:01:01 +000071 returns the location of the VC runtime DLLs so they can be copied into
John McCutchan36af9942016-07-01 11:09:28 -070072 the output directory after gyp generation.
Zach Anderson9a6797e2016-10-04 09:19:07 -070073
Alexander Aprelev521a1062019-07-31 20:01:01 +000074 Return value is [x64path, x86path, 'Arm64Unused'] or None. arm64path is
75 generated separately because there are multiple folders for the arm64 VC
76 runtime.
John McCutchan36af9942016-07-01 11:09:28 -070077 """
Nate Bosch55f81f22019-08-05 20:34:31 +000078 vs_runtime_dll_dirs = None
79 depot_tools_win_toolchain = \
80 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
81 # When running on a non-Windows host, only do this if the SDK has explicitly
82 # been downloaded before (in which case json_data_file will exist).
Alexander Thomasb5c63ce2021-04-15 10:10:20 +000083 if ((_HostIsWindows() or os.path.exists(json_data_file)) and
84 depot_tools_win_toolchain):
Nate Bosch55f81f22019-08-05 20:34:31 +000085 if ShouldUpdateToolchain():
86 if len(sys.argv) > 1 and sys.argv[1] == 'update':
87 update_result = Update()
88 else:
89 update_result = Update(no_download=True)
90 if update_result != 0:
Alexander Thomasb5c63ce2021-04-15 10:10:20 +000091 raise Exception('Failed to update, error code %d.' %
92 update_result)
Nate Bosch55f81f22019-08-05 20:34:31 +000093 with open(json_data_file, 'r') as tempf:
94 toolchain_data = json.load(tempf)
John McCutchan36af9942016-07-01 11:09:28 -070095
Nate Bosch55f81f22019-08-05 20:34:31 +000096 toolchain = toolchain_data['path']
97 version = toolchain_data['version']
98 win_sdk = toolchain_data.get('win_sdk')
Nate Bosch55f81f22019-08-05 20:34:31 +000099 wdk = toolchain_data['wdk']
100 # TODO(scottmg): The order unfortunately matters in these. They should be
101 # split into separate keys for x64/x86/arm64. (See CopyDlls call below).
102 # http://crbug.com/345992
103 vs_runtime_dll_dirs = toolchain_data['runtime_dirs']
104 # The number of runtime_dirs in the toolchain_data was two (x64/x86) but
105 # changed to three (x64/x86/arm64) and this code needs to handle both
106 # possibilities, which can change independently from this code.
107 if len(vs_runtime_dll_dirs) == 2:
108 vs_runtime_dll_dirs.append('Arm64Unused')
John McCutchan36af9942016-07-01 11:09:28 -0700109
Nate Bosch55f81f22019-08-05 20:34:31 +0000110 os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
Zach Anderson381c5062017-09-28 09:25:37 +0000111
Nate Bosch55f81f22019-08-05 20:34:31 +0000112 os.environ['WINDOWSSDKDIR'] = win_sdk
113 os.environ['WDK_DIR'] = wdk
114 # Include the VS runtime in the PATH in case it's not machine-installed.
115 runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs)
116 os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH']
117 elif sys.platform == 'win32' and not depot_tools_win_toolchain:
118 if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ:
119 os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath()
Zach Anderson9a6797e2016-10-04 09:19:07 -0700120
Nate Bosch55f81f22019-08-05 20:34:31 +0000121 # When using an installed toolchain these files aren't needed in the output
122 # directory in order to run binaries locally, but they are needed in order
123 # to create isolates or the mini_installer. Copying them to the output
124 # directory ensures that they are available when needed.
125 bitness = platform.architecture()[0]
126 # When running 64-bit python the x64 DLLs will be in System32
127 # ARM64 binaries will not be available in the system directories because we
128 # don't build on ARM64 machines.
129 x64_path = 'System32' if bitness == '64bit' else 'Sysnative'
130 x64_path = os.path.join(os.path.expandvars('%windir%'), x64_path)
131 vs_runtime_dll_dirs = [
132 x64_path,
133 os.path.join(os.path.expandvars('%windir%'), 'SysWOW64'),
134 'Arm64Unused'
135 ]
Zach Anderson9a6797e2016-10-04 09:19:07 -0700136
Nate Bosch55f81f22019-08-05 20:34:31 +0000137 return vs_runtime_dll_dirs
Zach Anderson9a6797e2016-10-04 09:19:07 -0700138
139
140def _RegistryGetValueUsingWinReg(key, value):
Nate Bosch55f81f22019-08-05 20:34:31 +0000141 """Use the _winreg module to obtain the value of a registry key.
Zach Anderson9a6797e2016-10-04 09:19:07 -0700142
143 Args:
144 key: The registry key.
145 value: The particular registry value to read.
146 Return:
147 contents of the registry key's value, or None on failure. Throws
148 ImportError if _winreg is unavailable.
149 """
Nate Bosch55f81f22019-08-05 20:34:31 +0000150 import _winreg
151 try:
152 root, subkey = key.split('\\', 1)
153 assert root == 'HKLM' # Only need HKLM for now.
154 with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
155 return _winreg.QueryValueEx(hkey, value)[0]
156 except WindowsError:
157 return None
Zach Anderson9a6797e2016-10-04 09:19:07 -0700158
159
160def _RegistryGetValue(key, value):
Nate Bosch55f81f22019-08-05 20:34:31 +0000161 try:
162 return _RegistryGetValueUsingWinReg(key, value)
163 except ImportError:
164 raise Exception('The python library _winreg not found.')
Zach Anderson9a6797e2016-10-04 09:19:07 -0700165
166
167def GetVisualStudioVersion():
Nate Bosch55f81f22019-08-05 20:34:31 +0000168 """Return best available version of Visual Studio.
Zach Anderson9a6797e2016-10-04 09:19:07 -0700169 """
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000170 supported_versions = list(MSVS_VERSIONS.keys())
Alexander Aprelev521a1062019-07-31 20:01:01 +0000171
Nate Bosch55f81f22019-08-05 20:34:31 +0000172 # VS installed in depot_tools for Googlers
173 if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))):
174 return supported_versions[0]
Alexander Aprelev521a1062019-07-31 20:01:01 +0000175
Nate Bosch55f81f22019-08-05 20:34:31 +0000176 # VS installed in system for external developers
177 supported_versions_str = ', '.join(
178 '{} ({})'.format(v, k) for k, v in MSVS_VERSIONS.items())
179 available_versions = []
180 for version in supported_versions:
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000181 # Checking vs%s_install environment variables.
182 # For example, vs2019_install could have the value
183 # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community".
184 # Only vs2017_install and vs2019_install are supported.
185 path = os.environ.get('vs%s_install' % version)
186 if path and os.path.exists(path):
187 available_versions.append(version)
188 break
189 # Detecting VS under possible paths.
190 path = os.path.expandvars('%ProgramFiles(x86)%' +
191 '/Microsoft Visual Studio/%s' % version)
192 if path and any(
193 os.path.exists(os.path.join(path, edition))
194 for edition in ('Enterprise', 'Professional', 'Community',
195 'Preview', 'BuildTools')):
196 available_versions.append(version)
197 break
Alexander Aprelev521a1062019-07-31 20:01:01 +0000198
Nate Bosch55f81f22019-08-05 20:34:31 +0000199 if not available_versions:
200 raise Exception('No supported Visual Studio can be found.'
201 ' Supported versions are: %s.' % supported_versions_str)
202 return available_versions[0]
Zach Anderson9a6797e2016-10-04 09:19:07 -0700203
204
205def DetectVisualStudioPath():
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000206 """Return path to the installed Visual Studio.
Zach Anderson9a6797e2016-10-04 09:19:07 -0700207 """
208
Nate Bosch55f81f22019-08-05 20:34:31 +0000209 # Note that this code is used from
210 # build/toolchain/win/setup_toolchain.py as well.
211 version_as_year = GetVisualStudioVersion()
Zach Anderson9a6797e2016-10-04 09:19:07 -0700212
Nate Bosch55f81f22019-08-05 20:34:31 +0000213 # The VC++ >=2017 install location needs to be located using COM instead of
214 # the registry. For details see:
215 # https://blogs.msdn.microsoft.com/heaths/2016/09/15/changes-to-visual-studio-15-setup/
216 # For now we use a hardcoded default with an environment variable override.
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000217 for path in (os.environ.get('vs%s_install' % version_as_year),
218 os.path.expandvars('%ProgramFiles(x86)%' +
219 '/Microsoft Visual Studio/%s/Enterprise' %
220 version_as_year),
221 os.path.expandvars('%ProgramFiles(x86)%' +
222 '/Microsoft Visual Studio/%s/Professional' %
223 version_as_year),
224 os.path.expandvars('%ProgramFiles(x86)%' +
225 '/Microsoft Visual Studio/%s/Community' %
226 version_as_year),
227 os.path.expandvars('%ProgramFiles(x86)%' +
228 '/Microsoft Visual Studio/%s/Preview' %
229 version_as_year),
230 os.path.expandvars('%ProgramFiles(x86)%' +
231 '/Microsoft Visual Studio/%s/BuildTools' %
232 version_as_year)):
Nate Bosch55f81f22019-08-05 20:34:31 +0000233 if path and os.path.exists(path):
234 return path
Alexander Aprelev521a1062019-07-31 20:01:01 +0000235
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000236 raise Exception('Visual Studio Version %s not found.' % version_as_year)
John McCutchan36af9942016-07-01 11:09:28 -0700237
238
Zach Anderson9a6797e2016-10-04 09:19:07 -0700239def _CopyRuntimeImpl(target, source, verbose=True):
Nate Bosch55f81f22019-08-05 20:34:31 +0000240 """Copy |source| to |target| if it doesn't already exist or if it needs to be
Zach Anderson9a6797e2016-10-04 09:19:07 -0700241 updated (comparing last modified time as an approximate float match as for
242 some reason the values tend to differ by ~1e-07 despite being copies of the
243 same file... https://crbug.com/603603).
John McCutchan36af9942016-07-01 11:09:28 -0700244 """
Nate Bosch55f81f22019-08-05 20:34:31 +0000245 if (os.path.isdir(os.path.dirname(target)) and
246 (not os.path.isfile(target) or
247 abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)):
248 if verbose:
249 print('Copying %s to %s...' % (source, target))
250 if os.path.exists(target):
251 # Make the file writable so that we can delete it now, and keep it
252 # readable.
253 os.chmod(target, stat.S_IWRITE | stat.S_IREAD)
254 os.unlink(target)
255 shutil.copy2(source, target)
256 # Make the file writable so that we can overwrite or delete it later,
257 # keep it readable.
258 os.chmod(target, stat.S_IWRITE | stat.S_IREAD)
259
John McCutchan36af9942016-07-01 11:09:28 -0700260
Alexander Aprelev521a1062019-07-31 20:01:01 +0000261def _SortByHighestVersionNumberFirst(list_of_str_versions):
Nate Bosch55f81f22019-08-05 20:34:31 +0000262 """This sorts |list_of_str_versions| according to version number rules
Alexander Aprelev521a1062019-07-31 20:01:01 +0000263 so that version "1.12" is higher than version "1.9". Does not work
264 with non-numeric versions like 1.4.a8 which will be higher than
265 1.4.a12. It does handle the versions being embedded in file paths.
266 """
Alexander Aprelev521a1062019-07-31 20:01:01 +0000267
Nate Bosch55f81f22019-08-05 20:34:31 +0000268 def to_int_if_int(x):
269 try:
270 return int(x)
271 except ValueError:
272 return x
Alexander Aprelev521a1062019-07-31 20:01:01 +0000273
Nate Bosch55f81f22019-08-05 20:34:31 +0000274 def to_number_sequence(x):
275 part_sequence = re.split(r'[\\/\.]', x)
276 return [to_int_if_int(x) for x in part_sequence]
277
278 list_of_str_versions.sort(key=to_number_sequence, reverse=True)
279
John McCutchan36af9942016-07-01 11:09:28 -0700280
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000281def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix):
Nate Bosch55f81f22019-08-05 20:34:31 +0000282 """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
John McCutchan36af9942016-07-01 11:09:28 -0700283 exist, but the target directory does exist."""
Nate Bosch55f81f22019-08-05 20:34:31 +0000284 if target_cpu == 'arm64':
285 # Windows ARM64 VCRuntime is located at {toolchain_root}/VC/Redist/MSVC/
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000286 # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/.
287 # Select VC toolset directory based on Visual Studio version
Nate Bosch55f81f22019-08-05 20:34:31 +0000288 vc_redist_root = FindVCRedistRoot()
289 if suffix.startswith('.'):
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000290 vc_toolset_dir = 'Microsoft.{}.CRT' \
291 .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()])
292 source_dir = os.path.join(vc_redist_root, 'arm64', vc_toolset_dir)
Nate Bosch55f81f22019-08-05 20:34:31 +0000293 else:
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000294 vc_toolset_dir = 'Microsoft.{}.DebugCRT' \
295 .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()])
Nate Bosch55f81f22019-08-05 20:34:31 +0000296 source_dir = os.path.join(vc_redist_root, 'debug_nonredist',
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000297 'arm64', vc_toolset_dir)
298 file_parts = ('msvcp140', 'vccorlib140', 'vcruntime140')
299 if target_cpu == 'x64' and GetVisualStudioVersion() != '2017':
300 file_parts = file_parts + ('vcruntime140_1',)
301 for file_part in file_parts:
302 dll = file_part + suffix
Nate Bosch55f81f22019-08-05 20:34:31 +0000303 target = os.path.join(target_dir, dll)
304 source = os.path.join(source_dir, dll)
305 _CopyRuntimeImpl(target, source)
306 # Copy the UCRT files from the Windows SDK. This location includes the
307 # api-ms-win-crt-*.dll files that are not found in the Windows directory.
308 # These files are needed for component builds. If WINDOWSSDKDIR is not set
309 # use the default SDK path. This will be the case when
310 # DEPOT_TOOLS_WIN_TOOLCHAIN=0 and vcvarsall.bat has not been run.
311 win_sdk_dir = os.path.normpath(
312 os.environ.get(
313 'WINDOWSSDKDIR',
314 os.path.expandvars('%ProgramFiles(x86)%'
315 '\\Windows Kits\\10')))
316 # ARM64 doesn't have a redist for the ucrt DLLs because they are always
317 # present in the OS.
318 if target_cpu != 'arm64':
319 # Starting with the 10.0.17763 SDK the ucrt files are in a version-named
320 # directory - this handles both cases.
321 redist_dir = os.path.join(win_sdk_dir, 'Redist')
322 version_dirs = glob.glob(os.path.join(redist_dir, '10.*'))
323 if len(version_dirs) > 0:
324 _SortByHighestVersionNumberFirst(version_dirs)
325 redist_dir = version_dirs[0]
326 ucrt_dll_dirs = os.path.join(redist_dir, 'ucrt', 'DLLs', target_cpu)
327 ucrt_files = glob.glob(os.path.join(ucrt_dll_dirs, 'api-ms-win-*.dll'))
328 assert len(ucrt_files) > 0
329 for ucrt_src_file in ucrt_files:
330 file_part = os.path.basename(ucrt_src_file)
331 ucrt_dst_file = os.path.join(target_dir, file_part)
332 _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False)
333 # We must copy ucrtbase.dll for x64/x86, and ucrtbased.dll for all CPU types.
334 if target_cpu != 'arm64' or not suffix.startswith('.'):
335 if not suffix.startswith('.'):
336 # ucrtbased.dll is located at {win_sdk_dir}/bin/{a.b.c.d}/{target_cpu}/
337 # ucrt/.
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000338 sdk_bin_root = os.path.join(win_sdk_dir, 'bin')
339 sdk_bin_sub_dirs = glob.glob(os.path.join(sdk_bin_root, '10.*'))
Nate Bosch55f81f22019-08-05 20:34:31 +0000340 # Select the most recent SDK if there are multiple versions installed.
341 _SortByHighestVersionNumberFirst(sdk_bin_sub_dirs)
342 for directory in sdk_bin_sub_dirs:
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000343 sdk_redist_root_version = os.path.join(sdk_bin_root, directory)
Nate Bosch55f81f22019-08-05 20:34:31 +0000344 if not os.path.isdir(sdk_redist_root_version):
345 continue
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000346 source_dir = os.path.join(sdk_redist_root_version, target_cpu,
347 'ucrt')
348 break
349 _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix),
350 os.path.join(source_dir, 'ucrtbase' + suffix))
Zach Anderson9a6797e2016-10-04 09:19:07 -0700351
352
Alexander Aprelev521a1062019-07-31 20:01:01 +0000353def FindVCComponentRoot(component):
Nate Bosch55f81f22019-08-05 20:34:31 +0000354 """Find the most recent Tools or Redist or other directory in an MSVC install.
Alexander Aprelev521a1062019-07-31 20:01:01 +0000355 Typical results are {toolchain_root}/VC/{component}/MSVC/{x.y.z}. The {x.y.z}
356 version number part changes frequently so the highest version number found is
357 used.
Zach Anderson381c5062017-09-28 09:25:37 +0000358 """
Alexander Aprelev521a1062019-07-31 20:01:01 +0000359
Nate Bosch55f81f22019-08-05 20:34:31 +0000360 SetEnvironmentAndGetRuntimeDllDirs()
361 assert ('GYP_MSVS_OVERRIDE_PATH' in os.environ)
362 vc_component_msvc_root = os.path.join(os.environ['GYP_MSVS_OVERRIDE_PATH'],
363 'VC', component, 'MSVC')
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000364 vc_component_msvc_contents = glob.glob(
365 os.path.join(vc_component_msvc_root, '14.*'))
Nate Bosch55f81f22019-08-05 20:34:31 +0000366 # Select the most recent toolchain if there are several.
367 _SortByHighestVersionNumberFirst(vc_component_msvc_contents)
368 for directory in vc_component_msvc_contents:
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000369 if os.path.isdir(directory):
370 return directory
Nate Bosch55f81f22019-08-05 20:34:31 +0000371 raise Exception('Unable to find the VC %s directory.' % component)
Zach Anderson381c5062017-09-28 09:25:37 +0000372
373
Alexander Aprelev521a1062019-07-31 20:01:01 +0000374def FindVCRedistRoot():
Nate Bosch55f81f22019-08-05 20:34:31 +0000375 """In >=VS2017, Redist binaries are located in
Alexander Aprelev521a1062019-07-31 20:01:01 +0000376 {toolchain_root}/VC/Redist/MSVC/{x.y.z}/{target_cpu}/.
377
378 This returns the '{toolchain_root}/VC/Redist/MSVC/{x.y.z}/' path.
Zach Anderson381c5062017-09-28 09:25:37 +0000379 """
Nate Bosch55f81f22019-08-05 20:34:31 +0000380 return FindVCComponentRoot('Redist')
Zach Anderson381c5062017-09-28 09:25:37 +0000381
382
Zach Anderson9a6797e2016-10-04 09:19:07 -0700383def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
Nate Bosch55f81f22019-08-05 20:34:31 +0000384 """Copy the VS runtime DLLs, only if the target doesn't exist, but the target
Alexander Aprelev521a1062019-07-31 20:01:01 +0000385 directory does exist. Handles VS 2015, 2017 and 2019."""
Nate Bosch55f81f22019-08-05 20:34:31 +0000386 suffix = 'd.dll' if debug else '.dll'
387 # VS 2015, 2017 and 2019 use the same CRT DLLs.
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000388 _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix)
John McCutchan36af9942016-07-01 11:09:28 -0700389
390
391def CopyDlls(target_dir, configuration, target_cpu):
Nate Bosch55f81f22019-08-05 20:34:31 +0000392 """Copy the VS runtime DLLs into the requested directory as needed.
John McCutchan36af9942016-07-01 11:09:28 -0700393
394 configuration is one of 'Debug' or 'Release'.
Alexander Aprelev521a1062019-07-31 20:01:01 +0000395 target_cpu is one of 'x86', 'x64' or 'arm64'.
John McCutchan36af9942016-07-01 11:09:28 -0700396
397 The debug configuration gets both the debug and release DLLs; the
398 release config only the latter.
399 """
Nate Bosch55f81f22019-08-05 20:34:31 +0000400 vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
401 if not vs_runtime_dll_dirs:
402 return
John McCutchan36af9942016-07-01 11:09:28 -0700403
Nate Bosch55f81f22019-08-05 20:34:31 +0000404 x64_runtime, x86_runtime, arm64_runtime = vs_runtime_dll_dirs
405 if target_cpu == 'x64':
406 runtime_dir = x64_runtime
407 elif target_cpu == 'x86':
408 runtime_dir = x86_runtime
409 elif target_cpu == 'arm64':
410 runtime_dir = arm64_runtime
411 else:
412 raise Exception('Unknown target_cpu: ' + target_cpu)
413 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False)
414 if configuration == 'Debug':
415 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True)
416 _CopyDebugger(target_dir, target_cpu)
Zach Anderson381c5062017-09-28 09:25:37 +0000417
418
419def _CopyDebugger(target_dir, target_cpu):
Nate Bosch55f81f22019-08-05 20:34:31 +0000420 """Copy dbghelp.dll and dbgcore.dll into the requested directory as needed.
Zach Anderson381c5062017-09-28 09:25:37 +0000421
Alexander Aprelev521a1062019-07-31 20:01:01 +0000422 target_cpu is one of 'x86', 'x64' or 'arm64'.
Zach Anderson381c5062017-09-28 09:25:37 +0000423
424 dbghelp.dll is used when Chrome needs to symbolize stacks. Copying this file
425 from the SDK directory avoids using the system copy of dbghelp.dll which then
426 ensures compatibility with recent debug information formats, such as VS
427 2017 /debug:fastlink PDBs.
428
429 dbgcore.dll is needed when using some functions from dbghelp.dll (like
430 MinidumpWriteDump).
431 """
Nate Bosch55f81f22019-08-05 20:34:31 +0000432 win_sdk_dir = SetEnvironmentAndGetSDKDir()
433 if not win_sdk_dir:
434 return
Zach Anderson381c5062017-09-28 09:25:37 +0000435
Nate Bosch55f81f22019-08-05 20:34:31 +0000436 # List of debug files that should be copied, the first element of the tuple is
437 # the name of the file and the second indicates if it's optional.
438 debug_files = [('dbghelp.dll', False), ('dbgcore.dll', True)]
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000439 # The UCRT is not a redistributable component on arm64.
440 if target_cpu != 'arm64':
441 debug_files.extend([('api-ms-win-downlevel-kernel32-l2-1-0.dll', False),
442 ('api-ms-win-eventing-provider-l1-1-0.dll', False)])
Nate Bosch55f81f22019-08-05 20:34:31 +0000443 for debug_file, is_optional in debug_files:
444 full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu,
445 debug_file)
446 if not os.path.exists(full_path):
447 if is_optional:
448 continue
449 else:
Nate Bosch55f81f22019-08-05 20:34:31 +0000450 raise Exception(
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000451 '%s not found in "%s"\r\nYou must install'
452 'Windows 10 SDK version 10.0.19041.0 including the '
453 '"Debugging Tools for Windows" feature.' %
454 (debug_file, full_path))
Nate Bosch55f81f22019-08-05 20:34:31 +0000455 target_path = os.path.join(target_dir, debug_file)
456 _CopyRuntimeImpl(target_path, full_path)
John McCutchan36af9942016-07-01 11:09:28 -0700457
458
459def _GetDesiredVsToolchainHashes():
Nate Bosch55f81f22019-08-05 20:34:31 +0000460 """Load a list of SHA1s corresponding to the toolchains that we want installed
John McCutchan36af9942016-07-01 11:09:28 -0700461 to build with."""
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000462 # Third parties that do not have access to the canonical toolchain can map
463 # canonical toolchain version to their own toolchain versions.
464 toolchain_hash_mapping_key = 'GYP_MSVS_HASH_%s' % TOOLCHAIN_HASH
465 return [os.environ.get(toolchain_hash_mapping_key, TOOLCHAIN_HASH)]
John McCutchan36af9942016-07-01 11:09:28 -0700466
467
Zach Anderson9a6797e2016-10-04 09:19:07 -0700468def ShouldUpdateToolchain():
Nate Bosch55f81f22019-08-05 20:34:31 +0000469 """Check if the toolchain should be upgraded."""
470 if not os.path.exists(json_data_file):
471 return True
472 with open(json_data_file, 'r') as tempf:
473 toolchain_data = json.load(tempf)
474 version = toolchain_data['version']
475 env_version = GetVisualStudioVersion()
476 # If there's a mismatch between the version set in the environment and the one
477 # in the json file then the toolchain should be updated.
478 return version != env_version
Zach Anderson9a6797e2016-10-04 09:19:07 -0700479
480
Alexander Aprelev521a1062019-07-31 20:01:01 +0000481def Update(force=False, no_download=False):
Nate Bosch55f81f22019-08-05 20:34:31 +0000482 """Requests an update of the toolchain to the specific hashes we have at
John McCutchan36af9942016-07-01 11:09:28 -0700483 this revision. The update outputs a .json of the various configuration
484 information required to pass to gyp which we use in |GetToolchainDir()|.
Alexander Aprelev521a1062019-07-31 20:01:01 +0000485 If no_download is true then the toolchain will be configured if present but
486 will not be downloaded.
John McCutchan36af9942016-07-01 11:09:28 -0700487 """
Nate Bosch55f81f22019-08-05 20:34:31 +0000488 if force != False and force != '--force':
489 print('Unknown parameter "%s"' % force, file=sys.stderr)
490 return 1
491 if force == '--force' or os.path.exists(json_data_file):
492 force = True
Zach Anderson9a6797e2016-10-04 09:19:07 -0700493
Nate Bosch55f81f22019-08-05 20:34:31 +0000494 depot_tools_win_toolchain = \
495 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000496 if (_HostIsWindows() or force) and depot_tools_win_toolchain:
Nate Bosch55f81f22019-08-05 20:34:31 +0000497 import find_depot_tools
498 depot_tools_path = find_depot_tools.add_depot_tools_to_path()
Alexander Apreleve7d71be2018-09-26 17:09:36 +0000499
Nate Bosch55f81f22019-08-05 20:34:31 +0000500 # On Linux, the file system is usually case-sensitive while the Windows
501 # SDK only works on case-insensitive file systems. If it doesn't already
502 # exist, set up a ciopfs fuse mount to put the SDK in a case-insensitive
503 # part of the file system.
504 toolchain_dir = os.path.join(depot_tools_path, 'win_toolchain',
505 'vs_files')
506 # For testing this block, unmount existing mounts with
507 # fusermount -u third_party/depot_tools/win_toolchain/vs_files
508 if sys.platform.startswith(
509 'linux') and not os.path.ismount(toolchain_dir):
510 import distutils.spawn
511 ciopfs = distutils.spawn.find_executable('ciopfs')
512 if not ciopfs:
513 # ciopfs not found in PATH; try the one downloaded from the DEPS hook.
514 ciopfs = os.path.join(script_dir, 'ciopfs')
515 if not os.path.isdir(toolchain_dir):
516 os.mkdir(toolchain_dir)
517 if not os.path.isdir(toolchain_dir + '.ciopfs'):
518 os.mkdir(toolchain_dir + '.ciopfs')
519 # Without use_ino, clang's #pragma once and Wnonportable-include-path
520 # both don't work right, see https://llvm.org/PR34931
521 # use_ino doesn't slow down builds, so it seems there's no drawback to
522 # just using it always.
523 subprocess.check_call([
524 ciopfs, '-o', 'use_ino', toolchain_dir + '.ciopfs',
525 toolchain_dir
526 ])
Alexander Apreleve7d71be2018-09-26 17:09:36 +0000527
Nate Bosch55f81f22019-08-05 20:34:31 +0000528 get_toolchain_args = [
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000529 # TODO(athom): use sys.executable (python3).
Vyacheslav Egorovf364c8b2021-05-21 11:04:22 +0000530 # Note: depot_tools contains python.bat not python.exe
531 # so for python to land on the first python in the PATH
532 # irrespective of its extension we pass shell=True below.
Alexander Thomasb5c63ce2021-04-15 10:10:20 +0000533 'python',
Nate Bosch55f81f22019-08-05 20:34:31 +0000534 os.path.join(depot_tools_path, 'win_toolchain',
535 'get_toolchain_if_necessary.py'),
536 '--output-json',
537 json_data_file,
538 ] + _GetDesiredVsToolchainHashes()
539 if force:
540 get_toolchain_args.append('--force')
541 if no_download:
542 get_toolchain_args.append('--no-download')
Vyacheslav Egorovf364c8b2021-05-21 11:04:22 +0000543 subprocess.check_call(get_toolchain_args, shell=True)
John McCutchan36af9942016-07-01 11:09:28 -0700544
Nate Bosch55f81f22019-08-05 20:34:31 +0000545 return 0
John McCutchan36af9942016-07-01 11:09:28 -0700546
547
Zach Anderson9a6797e2016-10-04 09:19:07 -0700548def NormalizePath(path):
Nate Bosch55f81f22019-08-05 20:34:31 +0000549 while path.endswith('\\'):
550 path = path[:-1]
551 return path
Zach Anderson9a6797e2016-10-04 09:19:07 -0700552
553
Zach Anderson381c5062017-09-28 09:25:37 +0000554def SetEnvironmentAndGetSDKDir():
Nate Bosch55f81f22019-08-05 20:34:31 +0000555 """Gets location information about the current sdk (must have been
John McCutchan36af9942016-07-01 11:09:28 -0700556 previously updated by 'update'). This is used for the GN build."""
Nate Bosch55f81f22019-08-05 20:34:31 +0000557 SetEnvironmentAndGetRuntimeDllDirs()
John McCutchan36af9942016-07-01 11:09:28 -0700558
Nate Bosch55f81f22019-08-05 20:34:31 +0000559 # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
560 if not 'WINDOWSSDKDIR' in os.environ:
561 default_sdk_path = os.path.expandvars('%ProgramFiles(x86)%'
562 '\\Windows Kits\\10')
563 if os.path.isdir(default_sdk_path):
564 os.environ['WINDOWSSDKDIR'] = default_sdk_path
John McCutchan36af9942016-07-01 11:09:28 -0700565
Nate Bosch55f81f22019-08-05 20:34:31 +0000566 return NormalizePath(os.environ['WINDOWSSDKDIR'])
Zach Anderson381c5062017-09-28 09:25:37 +0000567
568
569def GetToolchainDir():
Nate Bosch55f81f22019-08-05 20:34:31 +0000570 """Gets location information about the current toolchain (must have been
Zach Anderson381c5062017-09-28 09:25:37 +0000571 previously updated by 'update'). This is used for the GN build."""
Nate Bosch55f81f22019-08-05 20:34:31 +0000572 runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
573 win_sdk_dir = SetEnvironmentAndGetSDKDir()
Zach Anderson381c5062017-09-28 09:25:37 +0000574
Nate Bosch55f81f22019-08-05 20:34:31 +0000575 print('''vs_path = %s
Alexander Apreleve7d71be2018-09-26 17:09:36 +0000576sdk_path = %s
577vs_version = %s
578wdk_dir = %s
579runtime_dirs = %s
Alexander Aprelev521a1062019-07-31 20:01:01 +0000580''' % (ToGNString(NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH'])),
581 ToGNString(win_sdk_dir), ToGNString(GetVisualStudioVersion()),
582 ToGNString(NormalizePath(os.environ.get('WDK_DIR', ''))),
583 ToGNString(os.path.pathsep.join(runtime_dll_dirs or ['None']))))
John McCutchan36af9942016-07-01 11:09:28 -0700584
585
586def main():
Nate Bosch55f81f22019-08-05 20:34:31 +0000587 commands = {
588 'update': Update,
589 'get_toolchain_dir': GetToolchainDir,
590 'copy_dlls': CopyDlls,
591 }
592 if len(sys.argv) < 2 or sys.argv[1] not in commands:
593 print('Expected one of: %s' % ', '.join(commands), file=sys.stderr)
594 return 1
595 return commands[sys.argv[1]](*sys.argv[2:])
John McCutchan36af9942016-07-01 11:09:28 -0700596
597
598if __name__ == '__main__':
Nate Bosch55f81f22019-08-05 20:34:31 +0000599 sys.exit(main())