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