Home | History | Annotate | Download | only in win
      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 
     11 import errno
     12 import os
     13 import re
     14 import subprocess
     15 import sys
     16 
     17 class 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 
    101 def _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 
    132 def _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 
    160 def _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 
    179 def _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 
    192 def _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 
    290 def _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 
    298 def _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 
    362 def 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 
    403 def 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 
    448 def 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 
    456 vs_version = None
    457 def 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 
    464 def _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 
    497 def _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 
    509 def 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 
    554 def _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(), '"'
    565 print '"', GetVSVersion({}).sdk_based, '"'
    566 
    567 #-------------------------------------------------------------------------------
    568 
    569 version_info = {
    570   '2010': {
    571     'includes': [
    572       'VC\\atlmfc\\include',
    573     ],
    574   },
    575 }
    576