1#!/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()) 383