• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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