blob: ddff24a43882a3f35eaffeb637a1be2b2a0b3785 [file] [log] [blame]
Ben Murdoch61f157c2016-09-16 13:49:30 +01001#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import glob
7import json
8import os
9import pipes
10import shutil
11import subprocess
12import sys
13
14
15script_dir = os.path.dirname(os.path.realpath(__file__))
16chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
17SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
19json_data_file = os.path.join(script_dir, 'win_toolchain.json')
20
21
22import gyp
23
24
25# Use MSVS2015 as the default toolchain.
26CURRENT_DEFAULT_TOOLCHAIN_VERSION = '2015'
27
28
29def SetEnvironmentAndGetRuntimeDllDirs():
30 """Sets up os.environ to use the depot_tools VS toolchain with gyp, and
31 returns the location of the VS runtime DLLs so they can be copied into
32 the output directory after gyp generation.
33 """
34 vs_runtime_dll_dirs = None
35 depot_tools_win_toolchain = \
36 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
37 # When running on a non-Windows host, only do this if the SDK has explicitly
38 # been downloaded before (in which case json_data_file will exist).
39 if ((sys.platform in ('win32', 'cygwin') or os.path.exists(json_data_file))
40 and depot_tools_win_toolchain):
41 if ShouldUpdateToolchain():
42 Update()
43 with open(json_data_file, 'r') as tempf:
44 toolchain_data = json.load(tempf)
45
46 toolchain = toolchain_data['path']
47 version = toolchain_data['version']
48 win_sdk = toolchain_data.get('win_sdk')
49 if not win_sdk:
50 win_sdk = toolchain_data['win8sdk']
51 wdk = toolchain_data['wdk']
52 # TODO(scottmg): The order unfortunately matters in these. They should be
53 # split into separate keys for x86 and x64. (See CopyVsRuntimeDlls call
54 # below). http://crbug.com/345992
55 vs_runtime_dll_dirs = toolchain_data['runtime_dirs']
56
57 os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
58 os.environ['GYP_MSVS_VERSION'] = version
59 # We need to make sure windows_sdk_path is set to the automated
60 # toolchain values in GYP_DEFINES, but don't want to override any
61 # otheroptions.express
62 # values there.
63 gyp_defines_dict = gyp.NameValueListToDict(gyp.ShlexEnv('GYP_DEFINES'))
64 gyp_defines_dict['windows_sdk_path'] = win_sdk
65 os.environ['GYP_DEFINES'] = ' '.join('%s=%s' % (k, pipes.quote(str(v)))
66 for k, v in gyp_defines_dict.iteritems())
67 os.environ['WINDOWSSDKDIR'] = win_sdk
68 os.environ['WDK_DIR'] = wdk
69 # Include the VS runtime in the PATH in case it's not machine-installed.
70 runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs)
71 os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH']
72 elif sys.platform == 'win32' and not depot_tools_win_toolchain:
73 if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ:
74 os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath()
75 if not 'GYP_MSVS_VERSION' in os.environ:
76 os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
77
78 return vs_runtime_dll_dirs
79
80
81def _RegistryGetValueUsingWinReg(key, value):
82 """Use the _winreg module to obtain the value of a registry key.
83
84 Args:
85 key: The registry key.
86 value: The particular registry value to read.
87 Return:
88 contents of the registry key's value, or None on failure. Throws
89 ImportError if _winreg is unavailable.
90 """
91 import _winreg
92 try:
93 root, subkey = key.split('\\', 1)
94 assert root == 'HKLM' # Only need HKLM for now.
95 with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
96 return _winreg.QueryValueEx(hkey, value)[0]
97 except WindowsError:
98 return None
99
100
101def _RegistryGetValue(key, value):
102 try:
103 return _RegistryGetValueUsingWinReg(key, value)
104 except ImportError:
105 raise Exception('The python library _winreg not found.')
106
107
108def GetVisualStudioVersion():
109 """Return GYP_MSVS_VERSION of Visual Studio.
110 """
111 return os.environ.get('GYP_MSVS_VERSION', CURRENT_DEFAULT_TOOLCHAIN_VERSION)
112
113
114def DetectVisualStudioPath():
115 """Return path to the GYP_MSVS_VERSION of Visual Studio.
116 """
117
118 # Note that this code is used from
119 # build/toolchain/win/setup_toolchain.py as well.
120 version_as_year = GetVisualStudioVersion()
121 year_to_version = {
122 '2013': '12.0',
123 '2015': '14.0',
124 }
125 if version_as_year not in year_to_version:
126 raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
127 ' not supported. Supported versions are: %s') % (
128 version_as_year, ', '.join(year_to_version.keys())))
129 version = year_to_version[version_as_year]
130 keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version,
131 r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version]
132 for key in keys:
133 path = _RegistryGetValue(key, 'InstallDir')
134 if not path:
135 continue
136 path = os.path.normpath(os.path.join(path, '..', '..'))
137 return path
138
139 raise Exception(('Visual Studio Version %s (from GYP_MSVS_VERSION)'
140 ' not found.') % (version_as_year))
141
142
143def _VersionNumber():
144 """Gets the standard version number ('120', '140', etc.) based on
145 GYP_MSVS_VERSION."""
146 vs_version = GetVisualStudioVersion()
147 if vs_version == '2013':
148 return '120'
149 elif vs_version == '2015':
150 return '140'
151 else:
152 raise ValueError('Unexpected GYP_MSVS_VERSION')
153
154
155def _CopyRuntimeImpl(target, source, verbose=True):
156 """Copy |source| to |target| if it doesn't already exist or if it needs to be
157 updated (comparing last modified time as an approximate float match as for
158 some reason the values tend to differ by ~1e-07 despite being copies of the
159 same file... https://crbug.com/603603).
160 """
161 if (os.path.isdir(os.path.dirname(target)) and
162 (not os.path.isfile(target) or
163 abs(os.stat(target).st_mtime - os.stat(source).st_mtime) >= 0.01)):
164 if verbose:
165 print 'Copying %s to %s...' % (source, target)
166 if os.path.exists(target):
167 os.unlink(target)
168 shutil.copy2(source, target)
169
170
171def _CopyRuntime2013(target_dir, source_dir, dll_pattern):
172 """Copy both the msvcr and msvcp runtime DLLs, only if the target doesn't
173 exist, but the target directory does exist."""
174 for file_part in ('p', 'r'):
175 dll = dll_pattern % file_part
176 target = os.path.join(target_dir, dll)
177 source = os.path.join(source_dir, dll)
178 _CopyRuntimeImpl(target, source)
179
180
181def _CopyRuntime2015(target_dir, source_dir, dll_pattern, suffix):
182 """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
183 exist, but the target directory does exist."""
184 for file_part in ('msvcp', 'vccorlib', 'vcruntime'):
185 dll = dll_pattern % file_part
186 target = os.path.join(target_dir, dll)
187 source = os.path.join(source_dir, dll)
188 _CopyRuntimeImpl(target, source)
189 ucrt_src_dir = os.path.join(source_dir, 'api-ms-win-*.dll')
190 for ucrt_src_file in glob.glob(ucrt_src_dir):
191 file_part = os.path.basename(ucrt_src_file)
192 ucrt_dst_file = os.path.join(target_dir, file_part)
193 _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False)
194 _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix),
195 os.path.join(source_dir, 'ucrtbase' + suffix))
196
197
198def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
199 """Copy the VS runtime DLLs, only if the target doesn't exist, but the target
200 directory does exist. Handles VS 2013 and VS 2015."""
201 suffix = "d.dll" if debug else ".dll"
202 if GetVisualStudioVersion() == '2015':
203 _CopyRuntime2015(target_dir, source_dir, '%s140' + suffix, suffix)
204 else:
205 _CopyRuntime2013(target_dir, source_dir, 'msvc%s120' + suffix)
206
207 # Copy the PGO runtime library to the release directories.
208 if not debug and os.environ.get('GYP_MSVS_OVERRIDE_PATH'):
209 pgo_x86_runtime_dir = os.path.join(os.environ.get('GYP_MSVS_OVERRIDE_PATH'),
210 'VC', 'bin')
211 pgo_x64_runtime_dir = os.path.join(pgo_x86_runtime_dir, 'amd64')
212 pgo_runtime_dll = 'pgort' + _VersionNumber() + '.dll'
213 if target_cpu == "x86":
214 source_x86 = os.path.join(pgo_x86_runtime_dir, pgo_runtime_dll)
215 if os.path.exists(source_x86):
216 _CopyRuntimeImpl(os.path.join(target_dir, pgo_runtime_dll), source_x86)
217 elif target_cpu == "x64":
218 source_x64 = os.path.join(pgo_x64_runtime_dir, pgo_runtime_dll)
219 if os.path.exists(source_x64):
220 _CopyRuntimeImpl(os.path.join(target_dir, pgo_runtime_dll),
221 source_x64)
222 else:
223 raise NotImplementedError("Unexpected target_cpu value:" + target_cpu)
224
225
226def CopyVsRuntimeDlls(output_dir, runtime_dirs):
227 """Copies the VS runtime DLLs from the given |runtime_dirs| to the output
228 directory so that even if not system-installed, built binaries are likely to
229 be able to run.
230
231 This needs to be run after gyp has been run so that the expected target
232 output directories are already created.
233
234 This is used for the GYP build and gclient runhooks.
235 """
236 x86, x64 = runtime_dirs
237 out_debug = os.path.join(output_dir, 'Debug')
238 out_debug_nacl64 = os.path.join(output_dir, 'Debug', 'x64')
239 out_release = os.path.join(output_dir, 'Release')
240 out_release_nacl64 = os.path.join(output_dir, 'Release', 'x64')
241 out_debug_x64 = os.path.join(output_dir, 'Debug_x64')
242 out_release_x64 = os.path.join(output_dir, 'Release_x64')
243
244 if os.path.exists(out_debug) and not os.path.exists(out_debug_nacl64):
245 os.makedirs(out_debug_nacl64)
246 if os.path.exists(out_release) and not os.path.exists(out_release_nacl64):
247 os.makedirs(out_release_nacl64)
248 _CopyRuntime(out_debug, x86, "x86", debug=True)
249 _CopyRuntime(out_release, x86, "x86", debug=False)
250 _CopyRuntime(out_debug_x64, x64, "x64", debug=True)
251 _CopyRuntime(out_release_x64, x64, "x64", debug=False)
252 _CopyRuntime(out_debug_nacl64, x64, "x64", debug=True)
253 _CopyRuntime(out_release_nacl64, x64, "x64", debug=False)
254
255
256def CopyDlls(target_dir, configuration, target_cpu):
257 """Copy the VS runtime DLLs into the requested directory as needed.
258
259 configuration is one of 'Debug' or 'Release'.
260 target_cpu is one of 'x86' or 'x64'.
261
262 The debug configuration gets both the debug and release DLLs; the
263 release config only the latter.
264
265 This is used for the GN build.
266 """
267 vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
268 if not vs_runtime_dll_dirs:
269 return
270
271 x64_runtime, x86_runtime = vs_runtime_dll_dirs
272 runtime_dir = x64_runtime if target_cpu == 'x64' else x86_runtime
273 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False)
274 if configuration == 'Debug':
275 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True)
276
277
278def _GetDesiredVsToolchainHashes():
279 """Load a list of SHA1s corresponding to the toolchains that we want installed
280 to build with."""
281 if GetVisualStudioVersion() == '2015':
282 if bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN_PRERELEASE', '0'))):
283 # Update 3 pre-release, May 16th.
284 return ['283cc362f57dbe240e0d21f48ae45f9d834a425a']
285 else:
286 # Update 2.
287 return ['95ddda401ec5678f15eeed01d2bee08fcbc5ee97']
288 else:
289 return ['03a4e939cd325d6bc5216af41b92d02dda1366a6']
290
291
292def ShouldUpdateToolchain():
293 """Check if the toolchain should be upgraded."""
294 if not os.path.exists(json_data_file):
295 return True
296 with open(json_data_file, 'r') as tempf:
297 toolchain_data = json.load(tempf)
298 version = toolchain_data['version']
299 env_version = GetVisualStudioVersion()
300 # If there's a mismatch between the version set in the environment and the one
301 # in the json file then the toolchain should be updated.
302 return version != env_version
303
304
305def Update(force=False):
306 """Requests an update of the toolchain to the specific hashes we have at
307 this revision. The update outputs a .json of the various configuration
308 information required to pass to gyp which we use in |GetToolchainDir()|.
309 """
310 if force != False and force != '--force':
311 print >>sys.stderr, 'Unknown parameter "%s"' % force
312 return 1
313 if force == '--force' or os.path.exists(json_data_file):
314 force = True
315
316 depot_tools_win_toolchain = \
317 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
318 if ((sys.platform in ('win32', 'cygwin') or force) and
319 depot_tools_win_toolchain):
320 import find_depot_tools
321 depot_tools_path = find_depot_tools.add_depot_tools_to_path()
322 # Necessary so that get_toolchain_if_necessary.py will put the VS toolkit
323 # in the correct directory.
324 os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
325 get_toolchain_args = [
326 sys.executable,
327 os.path.join(depot_tools_path,
328 'win_toolchain',
329 'get_toolchain_if_necessary.py'),
330 '--output-json', json_data_file,
331 ] + _GetDesiredVsToolchainHashes()
332 if force:
333 get_toolchain_args.append('--force')
334 subprocess.check_call(get_toolchain_args)
335
336 return 0
337
338
339def NormalizePath(path):
340 while path.endswith("\\"):
341 path = path[:-1]
342 return path
343
344
345def GetToolchainDir():
346 """Gets location information about the current toolchain (must have been
347 previously updated by 'update'). This is used for the GN build."""
348 runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
349
350 # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
351 if not 'WINDOWSSDKDIR' in os.environ:
352 default_sdk_path = 'C:\\Program Files (x86)\\Windows Kits\\10'
353 if os.path.isdir(default_sdk_path):
354 os.environ['WINDOWSSDKDIR'] = default_sdk_path
355
356 print '''vs_path = "%s"
357sdk_path = "%s"
358vs_version = "%s"
359wdk_dir = "%s"
360runtime_dirs = "%s"
361''' % (
362 NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH']),
363 NormalizePath(os.environ['WINDOWSSDKDIR']),
364 GetVisualStudioVersion(),
365 NormalizePath(os.environ.get('WDK_DIR', '')),
366 os.path.pathsep.join(runtime_dll_dirs or ['None']))
367
368
369def main():
370 commands = {
371 'update': Update,
372 'get_toolchain_dir': GetToolchainDir,
373 'copy_dlls': CopyDlls,
374 }
375 if len(sys.argv) < 2 or sys.argv[1] not in commands:
376 print >>sys.stderr, 'Expected one of: %s' % ', '.join(commands)
377 return 1
378 return commands[sys.argv[1]](*sys.argv[2:])
379
380
381if __name__ == '__main__':
382 sys.exit(main())