1# Copyright (c) 2013 Google Inc. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Handle version information related to Visual Stuio.""" 6 7import errno 8import os 9import re 10import subprocess 11import sys 12import gyp 13 14 15class VisualStudioVersion(object): 16 """Information regarding a version of Visual Studio.""" 17 18 def __init__(self, short_name, description, 19 solution_version, project_version, flat_sln, uses_vcxproj, 20 path, sdk_based, default_toolset=None): 21 self.short_name = short_name 22 self.description = description 23 self.solution_version = solution_version 24 self.project_version = project_version 25 self.flat_sln = flat_sln 26 self.uses_vcxproj = uses_vcxproj 27 self.path = path 28 self.sdk_based = sdk_based 29 self.default_toolset = default_toolset 30 31 def ShortName(self): 32 return self.short_name 33 34 def Description(self): 35 """Get the full description of the version.""" 36 return self.description 37 38 def SolutionVersion(self): 39 """Get the version number of the sln files.""" 40 return self.solution_version 41 42 def ProjectVersion(self): 43 """Get the version number of the vcproj or vcxproj files.""" 44 return self.project_version 45 46 def FlatSolution(self): 47 return self.flat_sln 48 49 def UsesVcxproj(self): 50 """Returns true if this version uses a vcxproj file.""" 51 return self.uses_vcxproj 52 53 def ProjectExtension(self): 54 """Returns the file extension for the project.""" 55 return self.uses_vcxproj and '.vcxproj' or '.vcproj' 56 57 def Path(self): 58 """Returns the path to Visual Studio installation.""" 59 return self.path 60 61 def ToolPath(self, tool): 62 """Returns the path to a given compiler tool. """ 63 return os.path.normpath(os.path.join(self.path, "VC/bin", tool)) 64 65 def DefaultToolset(self): 66 """Returns the msbuild toolset version that will be used in the absence 67 of a user override.""" 68 return self.default_toolset 69 70 def SetupScript(self, target_arch): 71 """Returns a command (with arguments) to be used to set up the 72 environment.""" 73 # Check if we are running in the SDK command line environment and use 74 # the setup script from the SDK if so. |target_arch| should be either 75 # 'x86' or 'x64'. 76 assert target_arch in ('x86', 'x64') 77 sdk_dir = os.environ.get('WindowsSDKDir') 78 if self.sdk_based and sdk_dir: 79 return [os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')), 80 '/' + target_arch] 81 else: 82 # We don't use VC/vcvarsall.bat for x86 because vcvarsall calls 83 # vcvars32, which it can only find if VS??COMNTOOLS is set, which it 84 # isn't always. 85 if target_arch == 'x86': 86 return [os.path.normpath( 87 os.path.join(self.path, 'Common7/Tools/vsvars32.bat'))] 88 else: 89 assert target_arch == 'x64' 90 arg = 'x86_amd64' 91 if (os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or 92 os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64'): 93 # Use the 64-on-64 compiler if we can. 94 arg = 'amd64' 95 return [os.path.normpath( 96 os.path.join(self.path, 'VC/vcvarsall.bat')), arg] 97 98 99def _RegistryQueryBase(sysdir, key, value): 100 """Use reg.exe to read a particular key. 101 102 While ideally we might use the win32 module, we would like gyp to be 103 python neutral, so for instance cygwin python lacks this module. 104 105 Arguments: 106 sysdir: The system subdirectory to attempt to launch reg.exe from. 107 key: The registry key to read from. 108 value: The particular value to read. 109 Return: 110 stdout from reg.exe, or None for failure. 111 """ 112 # Skip if not on Windows or Python Win32 setup issue 113 if sys.platform not in ('win32', 'cygwin'): 114 return None 115 # Setup params to pass to and attempt to launch reg.exe 116 cmd = [os.path.join(os.environ.get('WINDIR', ''), sysdir, 'reg.exe'), 117 'query', key] 118 if value: 119 cmd.extend(['/v', value]) 120 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 121 # Obtain the stdout from reg.exe, reading to the end so p.returncode is valid 122 # Note that the error text may be in [1] in some cases 123 text = p.communicate()[0] 124 # Check return code from reg.exe; officially 0==success and 1==error 125 if p.returncode: 126 return None 127 return text 128 129 130def _RegistryQuery(key, value=None): 131 """Use reg.exe to read a particular key through _RegistryQueryBase. 132 133 First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If 134 that fails, it falls back to System32. Sysnative is available on Vista and 135 up and available on Windows Server 2003 and XP through KB patch 942589. Note 136 that Sysnative will always fail if using 64-bit python due to it being a 137 virtual directory and System32 will work correctly in the first place. 138 139 KB 942589 - http://support.microsoft.com/kb/942589/en-us. 140 141 Arguments: 142 key: The registry key. 143 value: The particular registry value to read (optional). 144 Return: 145 stdout from reg.exe, or None for failure. 146 """ 147 text = None 148 try: 149 text = _RegistryQueryBase('Sysnative', key, value) 150 except OSError, e: 151 if e.errno == errno.ENOENT: 152 text = _RegistryQueryBase('System32', key, value) 153 else: 154 raise 155 return text 156 157 158def _RegistryGetValue(key, value): 159 """Use reg.exe to obtain the value of a registry key. 160 161 Args: 162 key: The registry key. 163 value: The particular registry value to read. 164 Return: 165 contents of the registry key's value, or None on failure. 166 """ 167 text = _RegistryQuery(key, value) 168 if not text: 169 return None 170 # Extract value. 171 match = re.search(r'REG_\w+\s+([^\r]+)\r\n', text) 172 if not match: 173 return None 174 return match.group(1) 175 176 177def _RegistryKeyExists(key): 178 """Use reg.exe to see if a key exists. 179 180 Args: 181 key: The registry key to check. 182 Return: 183 True if the key exists 184 """ 185 if not _RegistryQuery(key): 186 return False 187 return True 188 189 190def _CreateVersion(name, path, sdk_based=False): 191 """Sets up MSVS project generation. 192 193 Setup is based off the GYP_MSVS_VERSION environment variable or whatever is 194 autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is 195 passed in that doesn't match a value in versions python will throw a error. 196 """ 197 if path: 198 path = os.path.normpath(path) 199 versions = { 200 '2013': VisualStudioVersion('2013', 201 'Visual Studio 2013', 202 solution_version='13.00', 203 project_version='12.0', 204 flat_sln=False, 205 uses_vcxproj=True, 206 path=path, 207 sdk_based=sdk_based, 208 default_toolset='v120'), 209 '2013e': VisualStudioVersion('2013e', 210 'Visual Studio 2013', 211 solution_version='13.00', 212 project_version='12.0', 213 flat_sln=True, 214 uses_vcxproj=True, 215 path=path, 216 sdk_based=sdk_based, 217 default_toolset='v120'), 218 '2012': VisualStudioVersion('2012', 219 'Visual Studio 2012', 220 solution_version='12.00', 221 project_version='4.0', 222 flat_sln=False, 223 uses_vcxproj=True, 224 path=path, 225 sdk_based=sdk_based, 226 default_toolset='v110'), 227 '2012e': VisualStudioVersion('2012e', 228 'Visual Studio 2012', 229 solution_version='12.00', 230 project_version='4.0', 231 flat_sln=True, 232 uses_vcxproj=True, 233 path=path, 234 sdk_based=sdk_based, 235 default_toolset='v110'), 236 '2010': VisualStudioVersion('2010', 237 'Visual Studio 2010', 238 solution_version='11.00', 239 project_version='4.0', 240 flat_sln=False, 241 uses_vcxproj=True, 242 path=path, 243 sdk_based=sdk_based), 244 '2010e': VisualStudioVersion('2010e', 245 'Visual Studio 2010', 246 solution_version='11.00', 247 project_version='4.0', 248 flat_sln=True, 249 uses_vcxproj=True, 250 path=path, 251 sdk_based=sdk_based), 252 '2008': VisualStudioVersion('2008', 253 'Visual Studio 2008', 254 solution_version='10.00', 255 project_version='9.00', 256 flat_sln=False, 257 uses_vcxproj=False, 258 path=path, 259 sdk_based=sdk_based), 260 '2008e': VisualStudioVersion('2008e', 261 'Visual Studio 2008', 262 solution_version='10.00', 263 project_version='9.00', 264 flat_sln=True, 265 uses_vcxproj=False, 266 path=path, 267 sdk_based=sdk_based), 268 '2005': VisualStudioVersion('2005', 269 'Visual Studio 2005', 270 solution_version='9.00', 271 project_version='8.00', 272 flat_sln=False, 273 uses_vcxproj=False, 274 path=path, 275 sdk_based=sdk_based), 276 '2005e': VisualStudioVersion('2005e', 277 'Visual Studio 2005', 278 solution_version='9.00', 279 project_version='8.00', 280 flat_sln=True, 281 uses_vcxproj=False, 282 path=path, 283 sdk_based=sdk_based), 284 } 285 return versions[str(name)] 286 287 288def _ConvertToCygpath(path): 289 """Convert to cygwin path if we are using cygwin.""" 290 if sys.platform == 'cygwin': 291 p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE) 292 path = p.communicate()[0].strip() 293 return path 294 295 296def _DetectVisualStudioVersions(versions_to_check, force_express): 297 """Collect the list of installed visual studio versions. 298 299 Returns: 300 A list of visual studio versions installed in descending order of 301 usage preference. 302 Base this on the registry and a quick check if devenv.exe exists. 303 Only versions 8-10 are considered. 304 Possibilities are: 305 2005(e) - Visual Studio 2005 (8) 306 2008(e) - Visual Studio 2008 (9) 307 2010(e) - Visual Studio 2010 (10) 308 2012(e) - Visual Studio 2012 (11) 309 2013(e) - Visual Studio 2013 (11) 310 Where (e) is e for express editions of MSVS and blank otherwise. 311 """ 312 version_to_year = { 313 '8.0': '2005', 314 '9.0': '2008', 315 '10.0': '2010', 316 '11.0': '2012', 317 '12.0': '2013', 318 } 319 versions = [] 320 for version in versions_to_check: 321 # Old method of searching for which VS version is installed 322 # We don't use the 2010-encouraged-way because we also want to get the 323 # path to the binaries, which it doesn't offer. 324 keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version, 325 r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version, 326 r'HKLM\Software\Microsoft\VCExpress\%s' % version, 327 r'HKLM\Software\Wow6432Node\Microsoft\VCExpress\%s' % version] 328 for index in range(len(keys)): 329 path = _RegistryGetValue(keys[index], 'InstallDir') 330 if not path: 331 continue 332 path = _ConvertToCygpath(path) 333 # Check for full. 334 full_path = os.path.join(path, 'devenv.exe') 335 express_path = os.path.join(path, 'vcexpress.exe') 336 if not force_express and os.path.exists(full_path): 337 # Add this one. 338 versions.append(_CreateVersion(version_to_year[version], 339 os.path.join(path, '..', '..'))) 340 # Check for express. 341 elif os.path.exists(express_path): 342 # Add this one. 343 versions.append(_CreateVersion(version_to_year[version] + 'e', 344 os.path.join(path, '..', '..'))) 345 346 # The old method above does not work when only SDK is installed. 347 keys = [r'HKLM\Software\Microsoft\VisualStudio\SxS\VC7', 348 r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VC7'] 349 for index in range(len(keys)): 350 path = _RegistryGetValue(keys[index], version) 351 if not path: 352 continue 353 path = _ConvertToCygpath(path) 354 versions.append(_CreateVersion(version_to_year[version] + 'e', 355 os.path.join(path, '..'), sdk_based=True)) 356 357 return versions 358 359 360def SelectVisualStudioVersion(version='auto'): 361 """Select which version of Visual Studio projects to generate. 362 363 Arguments: 364 version: Hook to allow caller to force a particular version (vs auto). 365 Returns: 366 An object representing a visual studio project format version. 367 """ 368 # In auto mode, check environment variable for override. 369 if version == 'auto': 370 version = os.environ.get('GYP_MSVS_VERSION', 'auto') 371 version_map = { 372 'auto': ('10.0', '9.0', '8.0', '11.0'), 373 '2005': ('8.0',), 374 '2005e': ('8.0',), 375 '2008': ('9.0',), 376 '2008e': ('9.0',), 377 '2010': ('10.0',), 378 '2010e': ('10.0',), 379 '2012': ('11.0',), 380 '2012e': ('11.0',), 381 '2013': ('12.0',), 382 '2013e': ('12.0',), 383 } 384 override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH') 385 if override_path: 386 msvs_version = os.environ.get('GYP_MSVS_VERSION') 387 if not msvs_version or 'e' not in msvs_version: 388 raise ValueError('GYP_MSVS_OVERRIDE_PATH requires GYP_MSVS_VERSION to be ' 389 'set to an "e" version (e.g. 2010e)') 390 return _CreateVersion(msvs_version, override_path, sdk_based=True) 391 version = str(version) 392 versions = _DetectVisualStudioVersions(version_map[version], 'e' in version) 393 if not versions: 394 if version == 'auto': 395 # Default to 2005 if we couldn't find anything 396 return _CreateVersion('2005', None) 397 else: 398 return _CreateVersion(version, None) 399 return versions[0] 400