Home | History | Annotate | Download | only in gypfiles
      1 #!/usr/bin/env python
      2 # Copyright 2015 the V8 project authors. All rights reserved.
      3 # Copyright 2014 The Chromium Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 import glob
      8 import json
      9 import os
     10 import pipes
     11 import shutil
     12 import subprocess
     13 import sys
     14 
     15 
     16 script_dir = os.path.dirname(os.path.realpath(__file__))
     17 chrome_src = os.path.abspath(os.path.join(script_dir, os.pardir))
     18 SRC_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
     19 sys.path.insert(1, os.path.join(chrome_src, 'tools'))
     20 sys.path.insert(0, os.path.join(chrome_src, 'tools', 'gyp', 'pylib'))
     21 json_data_file = os.path.join(script_dir, 'win_toolchain.json')
     22 
     23 
     24 import gyp
     25 
     26 
     27 # Use MSVS2013 as the default toolchain.
     28 CURRENT_DEFAULT_TOOLCHAIN_VERSION = '2013'
     29 
     30 
     31 def SetEnvironmentAndGetRuntimeDllDirs():
     32   """Sets up os.environ to use the depot_tools VS toolchain with gyp, and
     33   returns the location of the VS runtime DLLs so they can be copied into
     34   the output directory after gyp generation.
     35   """
     36   vs_runtime_dll_dirs = None
     37   depot_tools_win_toolchain = \
     38       bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
     39   # When running on a non-Windows host, only do this if the SDK has explicitly
     40   # been downloaded before (in which case json_data_file will exist).
     41   if ((sys.platform in ('win32', 'cygwin') or os.path.exists(json_data_file))
     42       and depot_tools_win_toolchain):
     43     if ShouldUpdateToolchain():
     44       Update()
     45     with open(json_data_file, 'r') as tempf:
     46       toolchain_data = json.load(tempf)
     47 
     48     toolchain = toolchain_data['path']
     49     version = toolchain_data['version']
     50     win_sdk = toolchain_data.get('win_sdk')
     51     if not win_sdk:
     52       win_sdk = toolchain_data['win8sdk']
     53     wdk = toolchain_data['wdk']
     54     # TODO(scottmg): The order unfortunately matters in these. They should be
     55     # split into separate keys for x86 and x64. (See CopyVsRuntimeDlls call
     56     # below). http://crbug.com/345992
     57     vs_runtime_dll_dirs = toolchain_data['runtime_dirs']
     58 
     59     os.environ['GYP_MSVS_OVERRIDE_PATH'] = toolchain
     60     os.environ['GYP_MSVS_VERSION'] = version
     61     # We need to make sure windows_sdk_path is set to the automated
     62     # toolchain values in GYP_DEFINES, but don't want to override any
     63     # otheroptions.express
     64     # values there.
     65     gyp_defines_dict = gyp.NameValueListToDict(gyp.ShlexEnv('GYP_DEFINES'))
     66     gyp_defines_dict['windows_sdk_path'] = win_sdk
     67     os.environ['GYP_DEFINES'] = ' '.join('%s=%s' % (k, pipes.quote(str(v)))
     68         for k, v in gyp_defines_dict.iteritems())
     69     os.environ['WINDOWSSDKDIR'] = win_sdk
     70     os.environ['WDK_DIR'] = wdk
     71     # Include the VS runtime in the PATH in case it's not machine-installed.
     72     runtime_path = os.path.pathsep.join(vs_runtime_dll_dirs)
     73     os.environ['PATH'] = runtime_path + os.path.pathsep + os.environ['PATH']
     74   elif sys.platform == 'win32' and not depot_tools_win_toolchain:
     75     if not 'GYP_MSVS_OVERRIDE_PATH' in os.environ:
     76       os.environ['GYP_MSVS_OVERRIDE_PATH'] = DetectVisualStudioPath()
     77     if not 'GYP_MSVS_VERSION' in os.environ:
     78       os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
     79 
     80   return vs_runtime_dll_dirs
     81 
     82 
     83 def _RegistryGetValueUsingWinReg(key, value):
     84   """Use the _winreg module to obtain the value of a registry key.
     85 
     86   Args:
     87     key: The registry key.
     88     value: The particular registry value to read.
     89   Return:
     90     contents of the registry key's value, or None on failure.  Throws
     91     ImportError if _winreg is unavailable.
     92   """
     93   import _winreg
     94   try:
     95     root, subkey = key.split('\\', 1)
     96     assert root == 'HKLM'  # Only need HKLM for now.
     97     with _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, subkey) as hkey:
     98       return _winreg.QueryValueEx(hkey, value)[0]
     99   except WindowsError:
    100     return None
    101 
    102 
    103 def _RegistryGetValue(key, value):
    104   try:
    105     return _RegistryGetValueUsingWinReg(key, value)
    106   except ImportError:
    107     raise Exception('The python library _winreg not found.')
    108 
    109 
    110 def GetVisualStudioVersion():
    111   """Return GYP_MSVS_VERSION of Visual Studio.
    112   """
    113   return os.environ.get('GYP_MSVS_VERSION', CURRENT_DEFAULT_TOOLCHAIN_VERSION)
    114 
    115 
    116 def DetectVisualStudioPath():
    117   """Return path to the GYP_MSVS_VERSION of Visual Studio.
    118   """
    119 
    120   # Note that this code is used from
    121   # build/toolchain/win/setup_toolchain.py as well.
    122   version_as_year = GetVisualStudioVersion()
    123   year_to_version = {
    124       '2013': '12.0',
    125       '2015': '14.0',
    126   }
    127   if version_as_year not in year_to_version:
    128     raise Exception(('Visual Studio version %s (from GYP_MSVS_VERSION)'
    129                      ' not supported. Supported versions are: %s') % (
    130                        version_as_year, ', '.join(year_to_version.keys())))
    131   version = year_to_version[version_as_year]
    132   keys = [r'HKLM\Software\Microsoft\VisualStudio\%s' % version,
    133           r'HKLM\Software\Wow6432Node\Microsoft\VisualStudio\%s' % version]
    134   for key in keys:
    135     path = _RegistryGetValue(key, 'InstallDir')
    136     if not path:
    137       continue
    138     path = os.path.normpath(os.path.join(path, '..', '..'))
    139     return path
    140 
    141   raise Exception(('Visual Studio Version %s (from GYP_MSVS_VERSION)'
    142                    ' not found.') % (version_as_year))
    143 
    144 
    145 def _VersionNumber():
    146   """Gets the standard version number ('120', '140', etc.) based on
    147   GYP_MSVS_VERSION."""
    148   vs_version = GetVisualStudioVersion()
    149   if vs_version == '2013':
    150     return '120'
    151   elif vs_version == '2015':
    152     return '140'
    153   else:
    154     raise ValueError('Unexpected GYP_MSVS_VERSION')
    155 
    156 
    157 def _CopyRuntimeImpl(target, source, verbose=True):
    158   """Copy |source| to |target| if it doesn't already exist or if it
    159   needs to be updated.
    160   """
    161   if (os.path.isdir(os.path.dirname(target)) and
    162       (not os.path.isfile(target) or
    163       os.stat(target).st_mtime != os.stat(source).st_mtime)):
    164     if verbose:
    165       print 'Copying %s to %s...' % (source, target)
    166     if os.path.exists(target):
    167       os.unlink(target)
    168     shutil.copy2(source, target)
    169 
    170 
    171 def _CopyRuntime2013(target_dir, source_dir, dll_pattern):
    172   """Copy both the msvcr and msvcp runtime DLLs, only if the target doesn't
    173   exist, but the target directory does exist."""
    174   for file_part in ('p', 'r'):
    175     dll = dll_pattern % file_part
    176     target = os.path.join(target_dir, dll)
    177     source = os.path.join(source_dir, dll)
    178     _CopyRuntimeImpl(target, source)
    179 
    180 
    181 def _CopyRuntime2015(target_dir, source_dir, dll_pattern, suffix):
    182   """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't
    183   exist, but the target directory does exist."""
    184   for file_part in ('msvcp', 'vccorlib', 'vcruntime'):
    185     dll = dll_pattern % file_part
    186     target = os.path.join(target_dir, dll)
    187     source = os.path.join(source_dir, dll)
    188     _CopyRuntimeImpl(target, source)
    189   ucrt_src_dir = os.path.join(source_dir, 'api-ms-win-*.dll')
    190   print 'Copying %s to %s...' % (ucrt_src_dir, target_dir)
    191   for ucrt_src_file in glob.glob(ucrt_src_dir):
    192     file_part = os.path.basename(ucrt_src_file)
    193     ucrt_dst_file = os.path.join(target_dir, file_part)
    194     _CopyRuntimeImpl(ucrt_dst_file, ucrt_src_file, False)
    195   _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix),
    196                     os.path.join(source_dir, 'ucrtbase' + suffix))
    197 
    198 
    199 def _CopyRuntime(target_dir, source_dir, target_cpu, debug):
    200   """Copy the VS runtime DLLs, only if the target doesn't exist, but the target
    201   directory does exist. Handles VS 2013 and VS 2015."""
    202   suffix = "d.dll" if debug else ".dll"
    203   if GetVisualStudioVersion() == '2015':
    204     _CopyRuntime2015(target_dir, source_dir, '%s140' + suffix, suffix)
    205   else:
    206     _CopyRuntime2013(target_dir, source_dir, 'msvc%s120' + suffix)
    207 
    208   # Copy the PGO runtime library to the release directories.
    209   if not debug and os.environ.get('GYP_MSVS_OVERRIDE_PATH'):
    210     pgo_x86_runtime_dir = os.path.join(os.environ.get('GYP_MSVS_OVERRIDE_PATH'),
    211                                         'VC', 'bin')
    212     pgo_x64_runtime_dir = os.path.join(pgo_x86_runtime_dir, 'amd64')
    213     pgo_runtime_dll = 'pgort' + _VersionNumber() + '.dll'
    214     if target_cpu == "x86":
    215       source_x86 = os.path.join(pgo_x86_runtime_dir, pgo_runtime_dll)
    216       if os.path.exists(source_x86):
    217         _CopyRuntimeImpl(os.path.join(target_dir, pgo_runtime_dll), source_x86)
    218     elif target_cpu == "x64":
    219       source_x64 = os.path.join(pgo_x64_runtime_dir, pgo_runtime_dll)
    220       if os.path.exists(source_x64):
    221         _CopyRuntimeImpl(os.path.join(target_dir, pgo_runtime_dll),
    222                           source_x64)
    223     else:
    224       raise NotImplementedError("Unexpected target_cpu value:" + target_cpu)
    225 
    226 
    227 def CopyVsRuntimeDlls(output_dir, runtime_dirs):
    228   """Copies the VS runtime DLLs from the given |runtime_dirs| to the output
    229   directory so that even if not system-installed, built binaries are likely to
    230   be able to run.
    231 
    232   This needs to be run after gyp has been run so that the expected target
    233   output directories are already created.
    234 
    235   This is used for the GYP build and gclient runhooks.
    236   """
    237   x86, x64 = runtime_dirs
    238   out_debug = os.path.join(output_dir, 'Debug')
    239   out_release = os.path.join(output_dir, 'Release')
    240   out_debug_x64 = os.path.join(output_dir, 'Debug_x64')
    241   out_release_x64 = os.path.join(output_dir, 'Release_x64')
    242 
    243   _CopyRuntime(out_debug,          x86, "x86", debug=True)
    244   _CopyRuntime(out_release,        x86, "x86", debug=False)
    245   _CopyRuntime(out_debug_x64,      x64, "x64", debug=True)
    246   _CopyRuntime(out_release_x64,    x64, "x64", debug=False)
    247 
    248 
    249 def CopyDlls(target_dir, configuration, target_cpu):
    250   """Copy the VS runtime DLLs into the requested directory as needed.
    251 
    252   configuration is one of 'Debug' or 'Release'.
    253   target_cpu is one of 'x86' or 'x64'.
    254 
    255   The debug configuration gets both the debug and release DLLs; the
    256   release config only the latter.
    257 
    258   This is used for the GN build.
    259   """
    260   vs_runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
    261   if not vs_runtime_dll_dirs:
    262     return
    263 
    264   x64_runtime, x86_runtime = vs_runtime_dll_dirs
    265   runtime_dir = x64_runtime if target_cpu == 'x64' else x86_runtime
    266   _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=False)
    267   if configuration == 'Debug':
    268     _CopyRuntime(target_dir, runtime_dir, target_cpu, debug=True)
    269 
    270 
    271 def _GetDesiredVsToolchainHashes():
    272   """Load a list of SHA1s corresponding to the toolchains that we want installed
    273   to build with."""
    274   if GetVisualStudioVersion() == '2015':
    275     # Update 2.
    276     return ['95ddda401ec5678f15eeed01d2bee08fcbc5ee97']
    277   else:
    278     return ['03a4e939cd325d6bc5216af41b92d02dda1366a6']
    279 
    280 
    281 def ShouldUpdateToolchain():
    282   """Check if the toolchain should be upgraded."""
    283   if not os.path.exists(json_data_file):
    284     return True
    285   with open(json_data_file, 'r') as tempf:
    286     toolchain_data = json.load(tempf)
    287   version = toolchain_data['version']
    288   env_version = GetVisualStudioVersion()
    289   # If there's a mismatch between the version set in the environment and the one
    290   # in the json file then the toolchain should be updated.
    291   return version != env_version
    292 
    293 
    294 def Update(force=False):
    295   """Requests an update of the toolchain to the specific hashes we have at
    296   this revision. The update outputs a .json of the various configuration
    297   information required to pass to gyp which we use in |GetToolchainDir()|.
    298   """
    299   if force != False and force != '--force':
    300     print >>sys.stderr, 'Unknown parameter "%s"' % force
    301     return 1
    302   if force == '--force' or os.path.exists(json_data_file):
    303     force = True
    304 
    305   depot_tools_win_toolchain = \
    306       bool(int(os.environ.get('DEPOT_TOOLS_WIN_TOOLCHAIN', '1')))
    307   if ((sys.platform in ('win32', 'cygwin') or force) and
    308         depot_tools_win_toolchain):
    309     import find_depot_tools
    310     depot_tools_path = find_depot_tools.add_depot_tools_to_path()
    311     # Necessary so that get_toolchain_if_necessary.py will put the VS toolkit
    312     # in the correct directory.
    313     os.environ['GYP_MSVS_VERSION'] = GetVisualStudioVersion()
    314     get_toolchain_args = [
    315         sys.executable,
    316         os.path.join(depot_tools_path,
    317                     'win_toolchain',
    318                     'get_toolchain_if_necessary.py'),
    319         '--output-json', json_data_file,
    320       ] + _GetDesiredVsToolchainHashes()
    321     if force:
    322       get_toolchain_args.append('--force')
    323     subprocess.check_call(get_toolchain_args)
    324 
    325   return 0
    326 
    327 
    328 def NormalizePath(path):
    329   while path.endswith("\\"):
    330     path = path[:-1]
    331   return path
    332 
    333 
    334 def GetToolchainDir():
    335   """Gets location information about the current toolchain (must have been
    336   previously updated by 'update'). This is used for the GN build."""
    337   runtime_dll_dirs = SetEnvironmentAndGetRuntimeDllDirs()
    338 
    339   # If WINDOWSSDKDIR is not set, search the default SDK path and set it.
    340   if not 'WINDOWSSDKDIR' in os.environ:
    341     default_sdk_path = 'C:\\Program Files (x86)\\Windows Kits\\10'
    342     if os.path.isdir(default_sdk_path):
    343       os.environ['WINDOWSSDKDIR'] = default_sdk_path
    344 
    345   print '''vs_path = "%s"
    346 sdk_path = "%s"
    347 vs_version = "%s"
    348 wdk_dir = "%s"
    349 runtime_dirs = "%s"
    350 ''' % (
    351       NormalizePath(os.environ['GYP_MSVS_OVERRIDE_PATH']),
    352       NormalizePath(os.environ['WINDOWSSDKDIR']),
    353       GetVisualStudioVersion(),
    354       NormalizePath(os.environ.get('WDK_DIR', '')),
    355       os.path.pathsep.join(runtime_dll_dirs or ['None']))
    356 
    357 
    358 def main():
    359   commands = {
    360       'update': Update,
    361       'get_toolchain_dir': GetToolchainDir,
    362       'copy_dlls': CopyDlls,
    363   }
    364   if len(sys.argv) < 2 or sys.argv[1] not in commands:
    365     print >>sys.stderr, 'Expected one of: %s' % ', '.join(commands)
    366     return 1
    367   return commands[sys.argv[1]](*sys.argv[2:])
    368 
    369 
    370 if __name__ == '__main__':
    371   sys.exit(main())
    372