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