1# Copyright (c) 2013 The Chromium Authors. 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# This file copies the logic from GYP to find the MSVC configuration. It's not
6# currently used because it is too slow. We will probably build this
7# functionality into the C++ code in the future.
8
9"""Handle version information related to Visual Stuio."""
10
11import errno
12import os
13import re
14import subprocess
15import sys
16
17class VisualStudioVersion(object):
18  """Information regarding a version of Visual Studio."""
19
20  def __init__(self, short_name, description,
21               solution_version, project_version, flat_sln, uses_vcxproj,
22               path, sdk_based, default_toolset=None):
23    self.short_name = short_name
24    self.description = description
25    self.solution_version = solution_version
26    self.project_version = project_version
27    self.flat_sln = flat_sln
28    self.uses_vcxproj = uses_vcxproj
29    self.path = path
30    self.sdk_based = sdk_based
31    self.default_toolset = default_toolset
32
33  def ShortName(self):
34    return self.short_name
35
36  def Description(self):
37    """Get the full description of the version."""
38    return self.description
39
40  def SolutionVersion(self):
41    """Get the version number of the sln files."""
42    return self.solution_version
43
44  def ProjectVersion(self):
45    """Get the version number of the vcproj or vcxproj files."""
46    return self.project_version
47
48  def FlatSolution(self):
49    return self.flat_sln
50
51  def UsesVcxproj(self):
52    """Returns true if this version uses a vcxproj file."""
53    return self.uses_vcxproj
54
55  def ProjectExtension(self):
56    """Returns the file extension for the project."""
57    return self.uses_vcxproj and '.vcxproj' or '.vcproj'
58
59  def Path(self):
60    """Returns the path to Visual Studio installation."""
61    return self.path
62
63  def ToolPath(self, tool):
64    """Returns the path to a given compiler tool. """
65    return os.path.normpath(os.path.join(self.path, "VC/bin", tool))
66
67  def DefaultToolset(self):
68    """Returns the msbuild toolset version that will be used in the absence
69    of a user override."""
70    return self.default_toolset
71
72  def SetupScript(self, target_arch):
73    """Returns a command (with arguments) to be used to set up the
74    environment."""
75    # Check if we are running in the SDK command line environment and use
76    # the setup script from the SDK if so. |target_arch| should be either
77    # 'x86' or 'x64'.
78    assert target_arch in ('x86', 'x64')
79    sdk_dir = os.environ.get('WindowsSDKDir')
80    if self.sdk_based and sdk_dir:
81      return [os.path.normpath(os.path.join(sdk_dir, 'Bin/SetEnv.Cmd')),
82              '/' + target_arch]
83    else:
84      # We don't use VC/vcvarsall.bat for x86 because vcvarsall calls
85      # vcvars32, which it can only find if VS??COMNTOOLS is set, which it
86      # isn't always.
87      if target_arch == 'x86':
88        return [os.path.normpath(
89          os.path.join(self.path, 'Common7/Tools/vsvars32.bat'))]
90      else:
91        assert target_arch == 'x64'
92        arg = 'x86_amd64'
93        if (os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or
94            os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64'):
95          # Use the 64-on-64 compiler if we can.
96          arg = 'amd64'
97        return [os.path.normpath(
98            os.path.join(self.path, 'VC/vcvarsall.bat')), arg]
99
100
101def _RegistryQueryBase(sysdir, key, value):
102  """Use reg.exe to read a particular key.
103
104  While ideally we might use the win32 module, we would like gyp to be
105  python neutral, so for instance cygwin python lacks this module.
106
107  Arguments:
108    sysdir: The system subdirectory to attempt to launch reg.exe from.
109    key: The registry key to read from.
110    value: The particular value to read.
111  Return:
112    stdout from reg.exe, or None for failure.
113  """
114  # Skip if not on Windows or Python Win32 setup issue
115  if sys.platform not in ('win32', 'cygwin'):
116    return None
117  # Setup params to pass to and attempt to launch reg.exe
118  cmd = [os.path.join(os.environ.get('WINDIR', ''), sysdir, 'reg.exe'),
119         'query', key]
120  if value:
121    cmd.extend(['/v', value])
122  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
123  # Obtain the stdout from reg.exe, reading to the end so p.returncode is valid
124  # Note that the error text may be in [1] in some cases
125  text = p.communicate()[0]
126  # Check return code from reg.exe; officially 0==success and 1==error
127  if p.returncode:
128    return None
129  return text
130
131
132def _RegistryQuery(key, value=None):
133  """Use reg.exe to read a particular key through _RegistryQueryBase.
134
135  First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
136  that fails, it falls back to System32.  Sysnative is available on Vista and
137  up and available on Windows Server 2003 and XP through KB patch 942589. Note
138  that Sysnative will always fail if using 64-bit python due to it being a
139  virtual directory and System32 will work correctly in the first place.
140
141  KB 942589 - http://support.microsoft.com/kb/942589/en-us.
142
143  Arguments:
144    key: The registry key.
145    value: The particular registry value to read (optional).
146  Return:
147    stdout from reg.exe, or None for failure.
148  """
149  text = None
150  try:
151    text = _RegistryQueryBase('Sysnative', key, value)
152  except OSError, e:
153    if e.errno == errno.ENOENT:
154      text = _RegistryQueryBase('System32', key, value)
155    else:
156      raise
157  return text
158
159
160def _RegistryGetValue(key, value):
161  """Use reg.exe to obtain the value of a registry key.
162
163  Args:
164    key: The registry key.
165    value: The particular registry value to read.
166  Return:
167    contents of the registry key's value, or None on failure.
168  """
169  text = _RegistryQuery(key, value)
170  if not text:
171    return None
172  # Extract value.
173  match = re.search(r'REG_\w+\s+([^\r]+)\r\n', text)
174  if not match:
175    return None
176  return match.group(1)
177
178
179def _RegistryKeyExists(key):
180  """Use reg.exe to see if a key exists.
181
182  Args:
183    key: The registry key to check.
184  Return:
185    True if the key exists
186  """
187  if not _RegistryQuery(key):
188    return False
189  return True
190
191
192def _CreateVersion(name, path, sdk_based=False):
193  """Sets up MSVS project generation.
194
195  Setup is based off the GYP_MSVS_VERSION environment variable or whatever is
196  autodetected if GYP_MSVS_VERSION is not explicitly specified. If a version is
197  passed in that doesn't match a value in versions python will throw a error.
198  """
199  if path:
200    path = os.path.normpath(path)
201  versions = {
202      '2013': VisualStudioVersion('2013',
203                                  'Visual Studio 2013',
204                                  solution_version='13.00',
205                                  project_version='4.0',
206                                  flat_sln=False,
207                                  uses_vcxproj=True,
208                                  path=path,
209                                  sdk_based=sdk_based,
210                                  default_toolset='v110'),
211      '2013e': VisualStudioVersion('2013e',
212                                   'Visual Studio 2013',
213                                   solution_version='13.00',
214                                   project_version='4.0',
215                                   flat_sln=True,
216                                   uses_vcxproj=True,
217                                   path=path,
218                                   sdk_based=sdk_based,
219                                   default_toolset='v110'),
220      '2012': VisualStudioVersion('2012',
221                                  'Visual Studio 2012',
222                                  solution_version='12.00',
223                                  project_version='4.0',
224                                  flat_sln=False,
225                                  uses_vcxproj=True,
226                                  path=path,
227                                  sdk_based=sdk_based,
228                                  default_toolset='v110'),
229      '2012e': VisualStudioVersion('2012e',
230                                   'Visual Studio 2012',
231                                   solution_version='12.00',
232                                   project_version='4.0',
233                                   flat_sln=True,
234                                   uses_vcxproj=True,
235                                   path=path,
236                                   sdk_based=sdk_based,
237                                   default_toolset='v110'),
238      '2010': VisualStudioVersion('2010',
239                                  'Visual Studio 2010',
240                                  solution_version='11.00',
241                                  project_version='4.0',
242                                  flat_sln=False,
243                                  uses_vcxproj=True,
244                                  path=path,
245                                  sdk_based=sdk_based),
246      '2010e': VisualStudioVersion('2010e',
247                                   'Visual Studio 2010',
248                                   solution_version='11.00',
249                                   project_version='4.0',
250                                   flat_sln=True,
251                                   uses_vcxproj=True,
252                                   path=path,
253                                   sdk_based=sdk_based),
254      '2008': VisualStudioVersion('2008',
255                                  'Visual Studio 2008',
256                                  solution_version='10.00',
257                                  project_version='9.00',
258                                  flat_sln=False,
259                                  uses_vcxproj=False,
260                                  path=path,
261                                  sdk_based=sdk_based),
262      '2008e': VisualStudioVersion('2008e',
263                                   'Visual Studio 2008',
264                                   solution_version='10.00',
265                                   project_version='9.00',
266                                   flat_sln=True,
267                                   uses_vcxproj=False,
268                                   path=path,
269                                   sdk_based=sdk_based),
270      '2005': VisualStudioVersion('2005',
271                                  'Visual Studio 2005',
272                                  solution_version='9.00',
273                                  project_version='8.00',
274                                  flat_sln=False,
275                                  uses_vcxproj=False,
276                                  path=path,
277                                  sdk_based=sdk_based),
278      '2005e': VisualStudioVersion('2005e',
279                                   'Visual Studio 2005',
280                                   solution_version='9.00',
281                                   project_version='8.00',
282                                   flat_sln=True,
283                                   uses_vcxproj=False,
284                                   path=path,
285                                   sdk_based=sdk_based),
286  }
287  return versions[str(name)]
288
289
290def _ConvertToCygpath(path):
291  """Convert to cygwin path if we are using cygwin."""
292  if sys.platform == 'cygwin':
293    p = subprocess.Popen(['cygpath', path], stdout=subprocess.PIPE)
294    path = p.communicate()[0].strip()
295  return path
296
297
298def _DetectVisualStudioVersions(versions_to_check, force_express):
299  """Collect the list of installed visual studio versions.
300
301  Returns:
302    A list of visual studio versions installed in descending order of
303    usage preference.
304    Base this on the registry and a quick check if devenv.exe exists.
305    Only versions 8-10 are considered.
306    Possibilities are:
307      2005(e) - Visual Studio 2005 (8)
308      2008(e) - Visual Studio 2008 (9)
309      2010(e) - Visual Studio 2010 (10)
310      2012(e) - Visual Studio 2012 (11)
311      2013(e) - Visual Studio 2013 (11)
312    Where (e) is e for express editions of MSVS and blank otherwise.
313  """
314  version_to_year = {
315      '8.0': '2005',
316      '9.0': '2008',
317      '10.0': '2010',
318      '11.0': '2012',
319      '12.0': '2013',
320  }
321  versions = []
322  for version in versions_to_check:
323    # Old method of searching for which VS version is installed
324    # We don't use the 2010-encouraged-way because we also want to get the
325    # path to the binaries, which it doesn't offer.
326    keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version,
327            r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version,
328            r'HKLM\Software\Microsoft\VCExpress\%s' % version,
329            r'HKLM\Software\Wow6432Node\Microsoft\VCExpress\%s' % version]
330    for index in range(len(keys)):
331      path = _RegistryGetValue(keys[index], 'InstallDir')
332      if not path:
333        continue
334      path = _ConvertToCygpath(path)
335      # Check for full.
336      full_path = os.path.join(path, 'devenv.exe')
337      express_path = os.path.join(path, 'vcexpress.exe')
338      if not force_express and os.path.exists(full_path):
339        # Add this one.
340        versions.append(_CreateVersion(version_to_year[version],
341            os.path.join(path, '..', '..')))
342      # Check for express.
343      elif os.path.exists(express_path):
344        # Add this one.
345        versions.append(_CreateVersion(version_to_year[version] + 'e',
346            os.path.join(path, '..', '..')))
347
348    # The old method above does not work when only SDK is installed.
349    keys = [r'HKLM\Software\Microsoft\VisualStudio\SxS\VC7',
350            r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\SxS\VC7']
351    for index in range(len(keys)):
352      path = _RegistryGetValue(keys[index], version)
353      if not path:
354        continue
355      path = _ConvertToCygpath(path)
356      versions.append(_CreateVersion(version_to_year[version] + 'e',
357          os.path.join(path, '..'), sdk_based=True))
358
359  return versions
360
361
362def SelectVisualStudioVersion(version='auto'):
363  """Select which version of Visual Studio projects to generate.
364
365  Arguments:
366    version: Hook to allow caller to force a particular version (vs auto).
367  Returns:
368    An object representing a visual studio project format version.
369  """
370  # In auto mode, check environment variable for override.
371  if version == 'auto':
372    version = os.environ.get('GYP_MSVS_VERSION', 'auto')
373  version_map = {
374    'auto': ('10.0', '9.0', '8.0', '11.0'),
375    '2005': ('8.0',),
376    '2005e': ('8.0',),
377    '2008': ('9.0',),
378    '2008e': ('9.0',),
379    '2010': ('10.0',),
380    '2010e': ('10.0',),
381    '2012': ('11.0',),
382    '2012e': ('11.0',),
383    '2013': ('12.0',),
384    '2013e': ('12.0',),
385  }
386  override_path = os.environ.get('GYP_MSVS_OVERRIDE_PATH')
387  if override_path:
388    msvs_version = os.environ.get('GYP_MSVS_VERSION')
389    if not msvs_version or 'e' not in msvs_version:
390      raise ValueError('GYP_MSVS_OVERRIDE_PATH requires GYP_MSVS_VERSION to be '
391                       'set to an "e" version (e.g. 2010e)')
392    return _CreateVersion(msvs_version, override_path, sdk_based=True)
393  version = str(version)
394  versions = _DetectVisualStudioVersions(version_map[version], 'e' in version)
395  if not versions:
396    if version == 'auto':
397      # Default to 2005 if we couldn't find anything
398      return _CreateVersion('2005', None)
399    else:
400      return _CreateVersion(version, None)
401  return versions[0]
402
403def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags, open_out):
404  """It's not sufficient to have the absolute path to the compiler, linker,
405  etc. on Windows, as those tools rely on .dlls being in the PATH. We also
406  need to support both x86 and x64 compilers within the same build (to support
407  msvs_target_platform hackery). Different architectures require a different
408  compiler binary, and different supporting environment variables (INCLUDE,
409  LIB, LIBPATH). So, we extract the environment here, wrap all invocations
410  of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
411  sets up the environment, and then we do not prefix the compiler with
412  an absolute path, instead preferring something like "cl.exe" in the rule
413  which will then run whichever the environment setup has put in the path.
414  When the following procedure to generate environment files does not
415  meet your requirement (e.g. for custom toolchains), you can pass
416  "-G ninja_use_custom_environment_files" to the gyp to suppress file
417  generation and use custom environment files prepared by yourself."""
418  archs = ('x86', 'x64')
419  if generator_flags.get('ninja_use_custom_environment_files', 0):
420    cl_paths = {}
421    for arch in archs:
422      cl_paths[arch] = 'cl.exe'
423    return cl_paths
424  vs = GetVSVersion(generator_flags)
425  cl_paths = {}
426  for arch in archs:
427    # Extract environment variables for subprocesses.
428    args = vs.SetupScript(arch)
429    args.extend(('&&', 'set'))
430    popen = subprocess.Popen(
431        args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
432    variables, _ = popen.communicate()
433    env = _ExtractImportantEnvironment(variables)
434    env_block = _FormatAsEnvironmentBlock(env)
435    f = open_out(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
436    f.write(env_block)
437    f.close()
438
439    # Find cl.exe location for this architecture.
440    args = vs.SetupScript(arch)
441    args.extend(('&&',
442      'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i'))
443    popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
444    output, _ = popen.communicate()
445    cl_paths[arch] = _ExtractCLPath(output)
446  return cl_paths
447
448def OpenOutput(path, mode='w'):
449  """Open |path| for writing, creating directories if necessary."""
450  try:
451    os.makedirs(os.path.dirname(path))
452  except OSError:
453    pass
454  return open(path, mode)
455
456vs_version = None
457def GetVSVersion(generator_flags):
458  global vs_version
459  if not vs_version:
460    vs_version = SelectVisualStudioVersion(
461        generator_flags.get('msvs_version', 'auto'))
462  return vs_version
463
464def _ExtractImportantEnvironment(output_of_set):
465  """Extracts environment variables required for the toolchain to run from
466  a textual dump output by the cmd.exe 'set' command."""
467  envvars_to_save = (
468      'goma_.*', # TODO(scottmg): This is ugly, but needed for goma.
469      'include',
470      'lib',
471      'libpath',
472      'path',
473      'pathext',
474      'systemroot',
475      'temp',
476      'tmp',
477      )
478  env = {}
479  for line in output_of_set.splitlines():
480    for envvar in envvars_to_save:
481      if re.match(envvar + '=', line.lower()):
482        var, setting = line.split('=', 1)
483        if envvar == 'path':
484          # Our own rules (for running gyp-win-tool) and other actions in
485          # Chromium rely on python being in the path. Add the path to this
486          # python here so that if it's not in the path when ninja is run
487          # later, python will still be found.
488          setting = os.path.dirname(sys.executable) + os.pathsep + setting
489        env[var.upper()] = setting
490        break
491  for required in ('SYSTEMROOT', 'TEMP', 'TMP'):
492    if required not in env:
493      raise Exception('Environment variable "%s" '
494                      'required to be set to valid path' % required)
495  return env
496
497def _FormatAsEnvironmentBlock(envvar_dict):
498  """Format as an 'environment block' directly suitable for CreateProcess.
499  Briefly this is a list of key=value\0, terminated by an additional \0. See
500  CreateProcess documentation for more details."""
501  block = ''
502  nul = '\0'
503  for key, value in envvar_dict.iteritems():
504    block += key + '=' + value + nul
505  block += nul
506  return block
507
508
509def GenerateEnvironmentFiles(toplevel_build_dir, generator_flags):
510  """It's not sufficient to have the absolute path to the compiler, linker,
511  etc. on Windows, as those tools rely on .dlls being in the PATH. We also
512  need to support both x86 and x64 compilers within the same build (to support
513  msvs_target_platform hackery). Different architectures require a different
514  compiler binary, and different supporting environment variables (INCLUDE,
515  LIB, LIBPATH). So, we extract the environment here, wrap all invocations
516  of compiler tools (cl, link, lib, rc, midl, etc.) via win_tool.py which
517  sets up the environment, and then we do not prefix the compiler with
518  an absolute path, instead preferring something like "cl.exe" in the rule
519  which will then run whichever the environment setup has put in the path.
520  When the following procedure to generate environment files does not
521  meet your requirement (e.g. for custom toolchains), you can pass
522  "-G ninja_use_custom_environment_files" to the gyp to suppress file
523  generation and use custom environment files prepared by yourself."""
524  archs = ('x86', 'x64')
525  if generator_flags.get('ninja_use_custom_environment_files', 0):
526    cl_paths = {}
527    for arch in archs:
528      cl_paths[arch] = 'cl.exe'
529    return cl_paths
530  vs = GetVSVersion(generator_flags)
531  cl_paths = {}
532  for arch in archs:
533    # Extract environment variables for subprocesses.
534    args = vs.SetupScript(arch)
535    args.extend(('&&', 'set'))
536    popen = subprocess.Popen(
537        args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
538    variables, _ = popen.communicate()
539    env = _ExtractImportantEnvironment(variables)
540    env_block = _FormatAsEnvironmentBlock(env)
541    f = OpenOutput(os.path.join(toplevel_build_dir, 'environment.' + arch), 'wb')
542    f.write(env_block)
543    f.close()
544
545    # Find cl.exe location for this architecture.
546    args = vs.SetupScript(arch)
547    args.extend(('&&',
548      'for', '%i', 'in', '(cl.exe)', 'do', '@echo', 'LOC:%~$PATH:i'))
549    popen = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
550    output, _ = popen.communicate()
551    cl_paths[arch] = _ExtractCLPath(output)
552  return cl_paths
553
554def _ExtractCLPath(output_of_where):
555  """Gets the path to cl.exe based on the output of calling the environment
556  setup batch file, followed by the equivalent of `where`."""
557  # Take the first line, as that's the first found in the PATH.
558  for line in output_of_where.strip().splitlines():
559    if line.startswith('LOC:'):
560      return line[len('LOC:'):].strip()
561
562#print SelectVisualStudioVersion().DefaultToolset()
563#GenerateEnvironmentFiles("D:\\src\\src1\\src\\out\\gn\\eraseme", {})
564#print '"', GetVSVersion({}).Path(), '"'
565print '"', GetVSVersion({}).sdk_based, '"'
566
567#-------------------------------------------------------------------------------
568
569version_info = {
570  '2010': {
571    'includes': [
572      'VC\\atlmfc\\include',
573    ],
574  },
575}
576