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 glob 13 14PY3 = bytes != str 15 16 17def JoinPath(*args): 18 return os.path.normpath(os.path.join(*args)) 19 20 21class VisualStudioVersion(object): 22 """Information regarding a version of Visual Studio.""" 23 24 def __init__( 25 self, 26 short_name, 27 description, 28 solution_version, 29 project_version, 30 flat_sln, 31 uses_vcxproj, 32 path, 33 sdk_based, 34 default_toolset=None, 35 compatible_sdks=None, 36 ): 37 self.short_name = short_name 38 self.description = description 39 self.solution_version = solution_version 40 self.project_version = project_version 41 self.flat_sln = flat_sln 42 self.uses_vcxproj = uses_vcxproj 43 self.path = path 44 self.sdk_based = sdk_based 45 self.default_toolset = default_toolset 46 compatible_sdks = compatible_sdks or [] 47 compatible_sdks.sort(key=lambda v: float(v.replace("v", "")), reverse=True) 48 self.compatible_sdks = compatible_sdks 49 50 def ShortName(self): 51 return self.short_name 52 53 def Description(self): 54 """Get the full description of the version.""" 55 return self.description 56 57 def SolutionVersion(self): 58 """Get the version number of the sln files.""" 59 return self.solution_version 60 61 def ProjectVersion(self): 62 """Get the version number of the vcproj or vcxproj files.""" 63 return self.project_version 64 65 def FlatSolution(self): 66 return self.flat_sln 67 68 def UsesVcxproj(self): 69 """Returns true if this version uses a vcxproj file.""" 70 return self.uses_vcxproj 71 72 def ProjectExtension(self): 73 """Returns the file extension for the project.""" 74 return self.uses_vcxproj and ".vcxproj" or ".vcproj" 75 76 def Path(self): 77 """Returns the path to Visual Studio installation.""" 78 return self.path 79 80 def ToolPath(self, tool): 81 """Returns the path to a given compiler tool. """ 82 return os.path.normpath(os.path.join(self.path, "VC/bin", tool)) 83 84 def DefaultToolset(self): 85 """Returns the msbuild toolset version that will be used in the absence 86 of a user override.""" 87 return self.default_toolset 88 89 def _SetupScriptInternal(self, target_arch): 90 """Returns a command (with arguments) to be used to set up the 91 environment.""" 92 assert target_arch in ("x86", "x64"), "target_arch not supported" 93 # If WindowsSDKDir is set and SetEnv.Cmd exists then we are using the 94 # depot_tools build tools and should run SetEnv.Cmd to set up the 95 # environment. The check for WindowsSDKDir alone is not sufficient because 96 # this is set by running vcvarsall.bat. 97 sdk_dir = os.environ.get("WindowsSDKDir", "") 98 setup_path = JoinPath(sdk_dir, "Bin", "SetEnv.Cmd") 99 if self.sdk_based and sdk_dir and os.path.exists(setup_path): 100 return [setup_path, "/" + target_arch] 101 102 is_host_arch_x64 = ( 103 os.environ.get("PROCESSOR_ARCHITECTURE") == "AMD64" 104 or os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64" 105 ) 106 107 # For VS2017 (and newer) it's fairly easy 108 if self.short_name >= "2017": 109 script_path = JoinPath( 110 self.path, "VC", "Auxiliary", "Build", "vcvarsall.bat" 111 ) 112 113 # Always use a native executable, cross-compiling if necessary. 114 host_arch = "amd64" if is_host_arch_x64 else "x86" 115 msvc_target_arch = "amd64" if target_arch == "x64" else "x86" 116 arg = host_arch 117 if host_arch != msvc_target_arch: 118 arg += "_" + msvc_target_arch 119 120 return [script_path, arg] 121 122 # We try to find the best version of the env setup batch. 123 vcvarsall = JoinPath(self.path, "VC", "vcvarsall.bat") 124 if target_arch == "x86": 125 if ( 126 self.short_name >= "2013" 127 and self.short_name[-1] != "e" 128 and is_host_arch_x64 129 ): 130 # VS2013 and later, non-Express have a x64-x86 cross that we want 131 # to prefer. 132 return [vcvarsall, "amd64_x86"] 133 else: 134 # Otherwise, the standard x86 compiler. We don't use VC/vcvarsall.bat 135 # for x86 because vcvarsall calls vcvars32, which it can only find if 136 # VS??COMNTOOLS is set, which isn't guaranteed. 137 return [JoinPath(self.path, "Common7", "Tools", "vsvars32.bat")] 138 elif target_arch == "x64": 139 arg = "x86_amd64" 140 # Use the 64-on-64 compiler if we're not using an express edition and 141 # we're running on a 64bit OS. 142 if self.short_name[-1] != "e" and is_host_arch_x64: 143 arg = "amd64" 144 return [vcvarsall, arg] 145 146 def SetupScript(self, target_arch): 147 script_data = self._SetupScriptInternal(target_arch) 148 script_path = script_data[0] 149 if not os.path.exists(script_path): 150 raise Exception( 151 "%s is missing - make sure VC++ tools are installed." % script_path 152 ) 153 return script_data 154 155 156def _RegistryQueryBase(sysdir, key, value): 157 """Use reg.exe to read a particular key. 158 159 While ideally we might use the win32 module, we would like gyp to be 160 python neutral, so for instance cygwin python lacks this module. 161 162 Arguments: 163 sysdir: The system subdirectory to attempt to launch reg.exe from. 164 key: The registry key to read from. 165 value: The particular value to read. 166 Return: 167 stdout from reg.exe, or None for failure. 168 """ 169 # Skip if not on Windows or Python Win32 setup issue 170 if sys.platform not in ("win32", "cygwin"): 171 return None 172 # Setup params to pass to and attempt to launch reg.exe 173 cmd = [os.path.join(os.environ.get("WINDIR", ""), sysdir, "reg.exe"), "query", key] 174 if value: 175 cmd.extend(["/v", value]) 176 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 177 # Obtain the stdout from reg.exe, reading to the end so p.returncode is valid 178 # Note that the error text may be in [1] in some cases 179 text = p.communicate()[0] 180 if PY3: 181 text = text.decode("utf-8") 182 # Check return code from reg.exe; officially 0==success and 1==error 183 if p.returncode: 184 return None 185 return text 186 187 188def _RegistryQuery(key, value=None): 189 r"""Use reg.exe to read a particular key through _RegistryQueryBase. 190 191 First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If 192 that fails, it falls back to System32. Sysnative is available on Vista and 193 up and available on Windows Server 2003 and XP through KB patch 942589. Note 194 that Sysnative will always fail if using 64-bit python due to it being a 195 virtual directory and System32 will work correctly in the first place. 196 197 KB 942589 - http://support.microsoft.com/kb/942589/en-us. 198 199 Arguments: 200 key: The registry key. 201 value: The particular registry value to read (optional). 202 Return: 203 stdout from reg.exe, or None for failure. 204 """ 205 text = None 206 try: 207 text = _RegistryQueryBase("Sysnative", key, value) 208 except OSError as e: 209 if e.errno == errno.ENOENT: 210 text = _RegistryQueryBase("System32", key, value) 211 else: 212 raise 213 return text 214 215 216def _RegistryGetValueUsingWinReg(key, value): 217 """Use the _winreg module to obtain the value of a registry key. 218 219 Args: 220 key: The registry key. 221 value: The particular registry value to read. 222 Return: 223 contents of the registry key's value, or None on failure. Throws 224 ImportError if _winreg is unavailable. 225 """ 226 try: 227 # Python 2 228 from _winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx 229 except ImportError: 230 # Python 3 231 from winreg import HKEY_LOCAL_MACHINE, OpenKey, QueryValueEx 232 233 try: 234 root, subkey = key.split("\\", 1) 235 assert root == "HKLM" # Only need HKLM for now. 236 with OpenKey(HKEY_LOCAL_MACHINE, subkey) as hkey: 237 return QueryValueEx(hkey, value)[0] 238 except WindowsError: 239 return None 240 241 242def _RegistryGetValue(key, value): 243 """Use _winreg or reg.exe to obtain the value of a registry key. 244 245 Using _winreg is preferable because it solves an issue on some corporate 246 environments where access to reg.exe is locked down. However, we still need 247 to fallback to reg.exe for the case where the _winreg module is not available 248 (for example in cygwin python). 249 250 Args: 251 key: The registry key. 252 value: The particular registry value to read. 253 Return: 254 contents of the registry key's value, or None on failure. 255 """ 256 try: 257 return _RegistryGetValueUsingWinReg(key, value) 258 except ImportError: 259 pass 260 261 # Fallback to reg.exe if we fail to import _winreg. 262 text = _RegistryQuery(key, value) 263 if not text: 264 return None 265 # Extract value. 266 match = re.search(r"REG_\w+\s+([^\r]+)\r\n", text) 267 if not match: 268 return None 269 return match.group(1) 270 271 272def _CreateVersion(name, path, sdk_based=False): 273 """Sets up MSVS project generation. 274 275 Setup is based off the GYP_MSVS_VERSION environment variable or whatever is 276 autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is 277 passed in that doesn't match a value in versions python will throw a error. 278 """ 279 if path: 280 path = os.path.normpath(path) 281 versions = { 282 "2019": VisualStudioVersion( 283 "2019", 284 "Visual Studio 2019", 285 solution_version="12.00", 286 project_version="16.0", 287 flat_sln=False, 288 uses_vcxproj=True, 289 path=path, 290 sdk_based=sdk_based, 291 default_toolset="v142", 292 compatible_sdks=["v8.1", "v10.0"], 293 ), 294 "2017": VisualStudioVersion( 295 "2017", 296 "Visual Studio 2017", 297 solution_version="12.00", 298 project_version="15.0", 299 flat_sln=False, 300 uses_vcxproj=True, 301 path=path, 302 sdk_based=sdk_based, 303 default_toolset="v141", 304 compatible_sdks=["v8.1", "v10.0"], 305 ), 306 "2015": VisualStudioVersion( 307 "2015", 308 "Visual Studio 2015", 309 solution_version="12.00", 310 project_version="14.0", 311 flat_sln=False, 312 uses_vcxproj=True, 313 path=path, 314 sdk_based=sdk_based, 315 default_toolset="v140", 316 ), 317 "2013": VisualStudioVersion( 318 "2013", 319 "Visual Studio 2013", 320 solution_version="13.00", 321 project_version="12.0", 322 flat_sln=False, 323 uses_vcxproj=True, 324 path=path, 325 sdk_based=sdk_based, 326 default_toolset="v120", 327 ), 328 "2013e": VisualStudioVersion( 329 "2013e", 330 "Visual Studio 2013", 331 solution_version="13.00", 332 project_version="12.0", 333 flat_sln=True, 334 uses_vcxproj=True, 335 path=path, 336 sdk_based=sdk_based, 337 default_toolset="v120", 338 ), 339 "2012": VisualStudioVersion( 340 "2012", 341 "Visual Studio 2012", 342 solution_version="12.00", 343 project_version="4.0", 344 flat_sln=False, 345 uses_vcxproj=True, 346 path=path, 347 sdk_based=sdk_based, 348 default_toolset="v110", 349 ), 350 "2012e": VisualStudioVersion( 351 "2012e", 352 "Visual Studio 2012", 353 solution_version="12.00", 354 project_version="4.0", 355 flat_sln=True, 356 uses_vcxproj=True, 357 path=path, 358 sdk_based=sdk_based, 359 default_toolset="v110", 360 ), 361 "2010": VisualStudioVersion( 362 "2010", 363 "Visual Studio 2010", 364 solution_version="11.00", 365 project_version="4.0", 366 flat_sln=False, 367 uses_vcxproj=True, 368 path=path, 369 sdk_based=sdk_based, 370 ), 371 "2010e": VisualStudioVersion( 372 "2010e", 373 "Visual C++ Express 2010", 374 solution_version="11.00", 375 project_version="4.0", 376 flat_sln=True, 377 uses_vcxproj=True, 378 path=path, 379 sdk_based=sdk_based, 380 ), 381 "2008": VisualStudioVersion( 382 "2008", 383 "Visual Studio 2008", 384 solution_version="10.00", 385 project_version="9.00", 386 flat_sln=False, 387 uses_vcxproj=False, 388 path=path, 389 sdk_based=sdk_based, 390 ), 391 "2008e": VisualStudioVersion( 392 "2008e", 393 "Visual Studio 2008", 394 solution_version="10.00", 395 project_version="9.00", 396 flat_sln=True, 397 uses_vcxproj=False, 398 path=path, 399 sdk_based=sdk_based, 400 ), 401 "2005": VisualStudioVersion( 402 "2005", 403 "Visual Studio 2005", 404 solution_version="9.00", 405 project_version="8.00", 406 flat_sln=False, 407 uses_vcxproj=False, 408 path=path, 409 sdk_based=sdk_based, 410 ), 411 "2005e": VisualStudioVersion( 412 "2005e", 413 "Visual Studio 2005", 414 solution_version="9.00", 415 project_version="8.00", 416 flat_sln=True, 417 uses_vcxproj=False, 418 path=path, 419 sdk_based=sdk_based, 420 ), 421 } 422 return versions[str(name)] 423 424 425def _ConvertToCygpath(path): 426 """Convert to cygwin path if we are using cygwin.""" 427 if sys.platform == "cygwin": 428 p = subprocess.Popen(["cygpath", path], stdout=subprocess.PIPE) 429 path = p.communicate()[0].strip() 430 if PY3: 431 path = path.decode("utf-8") 432 return path 433 434 435def _DetectVisualStudioVersions(versions_to_check, force_express): 436 """Collect the list of installed visual studio versions. 437 438 Returns: 439 A list of visual studio versions installed in descending order of 440 usage preference. 441 Base this on the registry and a quick check if devenv.exe exists. 442 Possibilities are: 443 2005(e) - Visual Studio 2005 (8) 444 2008(e) - Visual Studio 2008 (9) 445 2010(e) - Visual Studio 2010 (10) 446 2012(e) - Visual Studio 2012 (11) 447 2013(e) - Visual Studio 2013 (12) 448 2015 - Visual Studio 2015 (14) 449 2017 - Visual Studio 2017 (15) 450 2019 - Visual Studio 2019 (16) 451 Where (e) is e for express editions of MSVS and blank otherwise. 452 """ 453 version_to_year = { 454 "8.0": "2005", 455 "9.0": "2008", 456 "10.0": "2010", 457 "11.0": "2012", 458 "12.0": "2013", 459 "14.0": "2015", 460 "15.0": "2017", 461 "16.0": "2019", 462 } 463 versions = [] 464 for version in versions_to_check: 465 # Old method of searching for which VS version is installed 466 # We don't use the 2010-encouraged-way because we also want to get the 467 # path to the binaries, which it doesn't offer. 468 keys = [ 469 r"HKLM\Software\Microsoft\VisualStudio\%s" % version, 470 r"HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s" % version, 471 r"HKLM\Software\Microsoft\VCExpress\%s" % version, 472 r"HKLM\Software\Wow6432Node\Microsoft\VCExpress\%s" % version, 473 ] 474 for index in range(len(keys)): 475 path = _RegistryGetValue(keys[index], "InstallDir") 476 if not path: 477 continue 478 path = _ConvertToCygpath(path) 479 # Check for full. 480 full_path = os.path.join(path, "devenv.exe") 481 express_path = os.path.join(path, "*express.exe") 482 if not force_express and os.path.exists(full_path): 483 # Add this one. 484 versions.append( 485 _CreateVersion( 486 version_to_year[version], os.path.join(path, "..", "..") 487 ) 488 ) 489 # Check for express. 490 elif glob.glob(express_path): 491 # Add this one. 492 versions.append( 493 _CreateVersion( 494 version_to_year[version] + "e", os.path.join(path, "..", "..") 495 ) 496 ) 497 498 # The old method above does not work when only SDK is installed. 499 keys = [ 500 r"HKLM\Software\Microsoft\VisualStudio\SxS\VC7", 501 r"HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VC7", 502 r"HKLM\Software\Microsoft\VisualStudio\SxS\VS7", 503 r"HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VS7", 504 ] 505 for index in range(len(keys)): 506 path = _RegistryGetValue(keys[index], version) 507 if not path: 508 continue 509 path = _ConvertToCygpath(path) 510 if version == "15.0": 511 if os.path.exists(path): 512 versions.append(_CreateVersion("2017", path)) 513 elif version != "14.0": # There is no Express edition for 2015. 514 versions.append( 515 _CreateVersion( 516 version_to_year[version] + "e", 517 os.path.join(path, ".."), 518 sdk_based=True, 519 ) 520 ) 521 522 return versions 523 524 525def SelectVisualStudioVersion(version="auto", allow_fallback=True): 526 """Select which version of Visual Studio projects to generate. 527 528 Arguments: 529 version: Hook to allow caller to force a particular version (vs auto). 530 Returns: 531 An object representing a visual studio project format version. 532 """ 533 # In auto mode, check environment variable for override. 534 if version == "auto": 535 version = os.environ.get("GYP_MSVS_VERSION", "auto") 536 version_map = { 537 "auto": ("16.0", "15.0", "14.0", "12.0", "10.0", "9.0", "8.0", "11.0"), 538 "2005": ("8.0",), 539 "2005e": ("8.0",), 540 "2008": ("9.0",), 541 "2008e": ("9.0",), 542 "2010": ("10.0",), 543 "2010e": ("10.0",), 544 "2012": ("11.0",), 545 "2012e": ("11.0",), 546 "2013": ("12.0",), 547 "2013e": ("12.0",), 548 "2015": ("14.0",), 549 "2017": ("15.0",), 550 "2019": ("16.0",), 551 } 552 override_path = os.environ.get("GYP_MSVS_OVERRIDE_PATH") 553 if override_path: 554 msvs_version = os.environ.get("GYP_MSVS_VERSION") 555 if not msvs_version: 556 raise ValueError( 557 "GYP_MSVS_OVERRIDE_PATH requires GYP_MSVS_VERSION to be " 558 "set to a particular version (e.g. 2010e)." 559 ) 560 return _CreateVersion(msvs_version, override_path, sdk_based=True) 561 version = str(version) 562 versions = _DetectVisualStudioVersions(version_map[version], "e" in version) 563 if not versions: 564 if not allow_fallback: 565 raise ValueError("Could not locate Visual Studio installation.") 566 if version == "auto": 567 # Default to 2005 if we couldn't find anything 568 return _CreateVersion("2005", None) 569 else: 570 return _CreateVersion(version, None) 571 return versions[0] 572