1 #!/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 7 import glob 8 import json 9 import os 10 import pipes 11 import shutil 12 import subprocess 13 import sys 14 15 16 script_dir = os.path.dirname(os.path.realpath(__file__)) 17 chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir)) 18 SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 sys.path.insert(1, os.path.join(chrome_src, 'tools')) 20 sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib')) 21 json_data_file = os.path.join(script_dir, 'win_toolchain.json') 22 23 24 import gyp 25 26 27 # Use MSVS2013 as the default toolchain. 28 CURRENT_DEFAULT_TOOLCHAIN_VERSION = '2013' 29 30 31 def 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 83 def _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 103 def _RegistryGetValue(key, value): 104 try: 105 return _RegistryGetValueUsingWinReg(key, value) 106 except ImportError: 107 raise Exception('The python library _winreg not found.') 108 109 110 def GetVisualStudioVersion(): 111 """Return GYP_MSVS_VERSION of Visual Studio. 112 """ 113 return os.environ.get('GYP_MSVS_VERSION', CURRENT_DEFAULT_TOOLCHAIN_VERSION) 114 115 116 def 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 145 def _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 157 def _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 171 def _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 181 def _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 199 def _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 227 def 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_release = os.path.join(output_dir, 'Release') 240 out_debug_x64 = os.path.join(output_dir, 'Debug_x64') 241 out_release_x64 = os.path.join(output_dir, 'Release_x64') 242 243 _CopyRuntime(out_debug, x86, "x86", debug=True) 244 _CopyRuntime(out_release, x86, "x86", debug=False) 245 _CopyRuntime(out_debug_x64, x64, "x64", debug=True) 246 _CopyRuntime(out_release_x64, x64, "x64", debug=False) 247 248 249 def CopyDlls(target_dir, configuration, target_cpu): 250 """Copy the VS runtime DLLs into the requested directory as needed. 251 252 configuration is one of 'Debug' or 'Release'. 253 target_cpu is one of 'x86' or 'x64'. 254 255 The debug configuration gets both the debug and release DLLs; the 256 release config only the latter. 257 258 This is used for the GN build. 259 """ 260 vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() 261 if not vs_runtime_dll_dirs: 262 return 263 264 x64_runtime, x86_runtime = vs_runtime_dll_dirs 265 runtime_dir = x64_runtime if target_cpu == 'x64' else x86_runtime 266 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False) 267 if configuration == 'Debug': 268 _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True) 269 270 271 def _GetDesiredVsToolchainHashes(): 272 """Load a list of SHA1s corresponding to the toolchains that we want installed 273 to build with.""" 274 if GetVisualStudioVersion() == '2015': 275 # Update 2. 276 return ['95ddda401ec5678f15eeed01d2bee08fcbc5ee97'] 277 else: 278 return ['03a4e939cd325d6bc5216af41b92d02dda1366a6'] 279 280 281 def ShouldUpdateToolchain(): 282 """Check if the toolchain should be upgraded.""" 283 if not os.path.exists(json_data_file): 284 return True 285 with open(json_data_file, 'r') as tempf: 286 toolchain_data = json.load(tempf) 287 version = toolchain_data['version'] 288 env_version = GetVisualStudioVersion() 289 # If there's a mismatch between the version set in the environment and the one 290 # in the json file then the toolchain should be updated. 291 return version != env_version 292 293 294 def Update(force=False): 295 """Requests an update of the toolchain to the specific hashes we have at 296 this revision. The update outputs a .json of the various configuration 297 information required to pass to gyp which we use in |GetToolchainDir()|. 298 """ 299 if force != False and force != '--force': 300 print >>sys.stderr, 'Unknown parameter "%s"' % force 301 return 1 302 if force == '--force' or os.path.exists(json_data_file): 303 force = True 304 305 depot_tools_win_toolchain = \ 306 bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1'))) 307 if ((sys.platform in ('win32', 'cygwin') or force) and 308 depot_tools_win_toolchain): 309 import find_depot_tools 310 depot_tools_path = find_depot_tools.add_depot_tools_to_path() 311 # Necessary so that get_toolchain_if_necessary.py will put the VS toolkit 312 # in the correct directory. 313 os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion() 314 get_toolchain_args = [ 315 sys.executable, 316 os.path.join(depot_tools_path, 317 'win_toolchain', 318 'get_toolchain_if_necessary.py'), 319 '--output-json', json_data_file, 320 ] + _GetDesiredVsToolchainHashes() 321 if force: 322 get_toolchain_args.append('--force') 323 subprocess.check_call(get_toolchain_args) 324 325 return 0 326 327 328 def NormalizePath(path): 329 while path.endswith("\\"): 330 path = path[:-1] 331 return path 332 333 334 def GetToolchainDir(): 335 """Gets location information about the current toolchain (must have been 336 previously updated by 'update'). This is used for the GN build.""" 337 runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs() 338 339 # If WINDOWSSDKDIR is not set, search the default SDK path and set it. 340 if not 'WINDOWSSDKDIR' in os.environ: 341 default_sdk_path = 'C:\\Program Files (x86)\\Windows Kits\\10' 342 if os.path.isdir(default_sdk_path): 343 os.environ['WINDOWSSDKDIR'] = default_sdk_path 344 345 print '''vs_path = "%s" 346 sdk_path = "%s" 347 vs_version = "%s" 348 wdk_dir = "%s" 349 runtime_dirs = "%s" 350 ''' % ( 351 NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH']), 352 NormalizePath(os.environ['WINDOWSSDKDIR']), 353 GetVisualStudioVersion(), 354 NormalizePath(os.environ.get('WDK_DIR', '')), 355 os.path.pathsep.join(runtime_dll_dirs or ['None'])) 356 357 358 def main(): 359 commands = { 360 'update': Update, 361 'get_toolchain_dir': GetToolchainDir, 362 'copy_dlls': CopyDlls, 363 } 364 if len(sys.argv) < 2 or sys.argv[1] not in commands: 365 print >>sys.stderr, 'Expected one of: %s' % ', '.join(commands) 366 return 1 367 return commands[sys.argv[1]](*sys.argv[2:]) 368 369 370 if __name__ == '__main__': 371 sys.exit(main()) 372