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