Home | History | Annotate | Download | only in gyp
      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 """Utility functions shared amongst the Windows generators."""
      6 
      7 import copy
      8 import os
      9 
     10 
     11 _TARGET_TYPE_EXT = {
     12   'executable': '.exe',
     13   'loadable_module': '.dll',
     14   'shared_library': '.dll',
     15 }
     16 
     17 
     18 def _GetLargePdbShimCcPath():
     19   """Returns the path of the large_pdb_shim.cc file."""
     20   this_dir = os.path.abspath(os.path.dirname(__file__))
     21   src_dir = os.path.abspath(os.path.join(this_dir, '..', '..'))
     22   win_data_dir = os.path.join(src_dir, 'data', 'win')
     23   large_pdb_shim_cc = os.path.join(win_data_dir, 'large-pdb-shim.cc')
     24   return large_pdb_shim_cc
     25 
     26 
     27 def _DeepCopySomeKeys(in_dict, keys):
     28   """Performs a partial deep-copy on |in_dict|, only copying the keys in |keys|.
     29 
     30   Arguments:
     31     in_dict: The dictionary to copy.
     32     keys: The keys to be copied. If a key is in this list and doesn't exist in
     33         |in_dict| this is not an error.
     34   Returns:
     35     The partially deep-copied dictionary.
     36   """
     37   d = {}
     38   for key in keys:
     39     if key not in in_dict:
     40       continue
     41     d[key] = copy.deepcopy(in_dict[key])
     42   return d
     43 
     44 
     45 def _SuffixName(name, suffix):
     46   """Add a suffix to the end of a target.
     47 
     48   Arguments:
     49     name: name of the target (foo#target)
     50     suffix: the suffix to be added
     51   Returns:
     52     Target name with suffix added (foo_suffix#target)
     53   """
     54   parts = name.rsplit('#', 1)
     55   parts[0] = '%s_%s' % (parts[0], suffix)
     56   return '#'.join(parts)
     57 
     58 
     59 def _ShardName(name, number):
     60   """Add a shard number to the end of a target.
     61 
     62   Arguments:
     63     name: name of the target (foo#target)
     64     number: shard number
     65   Returns:
     66     Target name with shard added (foo_1#target)
     67   """
     68   return _SuffixName(name, str(number))
     69 
     70 
     71 def ShardTargets(target_list, target_dicts):
     72   """Shard some targets apart to work around the linkers limits.
     73 
     74   Arguments:
     75     target_list: List of target pairs: 'base/base.gyp:base'.
     76     target_dicts: Dict of target properties keyed on target pair.
     77   Returns:
     78     Tuple of the new sharded versions of the inputs.
     79   """
     80   # Gather the targets to shard, and how many pieces.
     81   targets_to_shard = {}
     82   for t in target_dicts:
     83     shards = int(target_dicts[t].get('msvs_shard', 0))
     84     if shards:
     85       targets_to_shard[t] = shards
     86   # Shard target_list.
     87   new_target_list = []
     88   for t in target_list:
     89     if t in targets_to_shard:
     90       for i in range(targets_to_shard[t]):
     91         new_target_list.append(_ShardName(t, i))
     92     else:
     93       new_target_list.append(t)
     94   # Shard target_dict.
     95   new_target_dicts = {}
     96   for t in target_dicts:
     97     if t in targets_to_shard:
     98       for i in range(targets_to_shard[t]):
     99         name = _ShardName(t, i)
    100         new_target_dicts[name] = copy.copy(target_dicts[t])
    101         new_target_dicts[name]['target_name'] = _ShardName(
    102              new_target_dicts[name]['target_name'], i)
    103         sources = new_target_dicts[name].get('sources', [])
    104         new_sources = []
    105         for pos in range(i, len(sources), targets_to_shard[t]):
    106           new_sources.append(sources[pos])
    107         new_target_dicts[name]['sources'] = new_sources
    108     else:
    109       new_target_dicts[t] = target_dicts[t]
    110   # Shard dependencies.
    111   for t in new_target_dicts:
    112     dependencies = copy.copy(new_target_dicts[t].get('dependencies', []))
    113     new_dependencies = []
    114     for d in dependencies:
    115       if d in targets_to_shard:
    116         for i in range(targets_to_shard[d]):
    117           new_dependencies.append(_ShardName(d, i))
    118       else:
    119         new_dependencies.append(d)
    120     new_target_dicts[t]['dependencies'] = new_dependencies
    121 
    122   return (new_target_list, new_target_dicts)
    123 
    124 
    125 def _GetPdbPath(target_dict, config_name, vars):
    126   """Returns the path to the PDB file that will be generated by a given
    127   configuration.
    128 
    129   The lookup proceeds as follows:
    130     - Look for an explicit path in the VCLinkerTool configuration block.
    131     - Look for an 'msvs_large_pdb_path' variable.
    132     - Use '<(PRODUCT_DIR)/<(product_name).(exe|dll).pdb' if 'product_name' is
    133       specified.
    134     - Use '<(PRODUCT_DIR)/<(target_name).(exe|dll).pdb'.
    135 
    136   Arguments:
    137     target_dict: The target dictionary to be searched.
    138     config_name: The name of the configuration of interest.
    139     vars: A dictionary of common GYP variables with generator-specific values.
    140   Returns:
    141     The path of the corresponding PDB file.
    142   """
    143   config = target_dict['configurations'][config_name]
    144   msvs = config.setdefault('msvs_settings', {})
    145 
    146   linker = msvs.get('VCLinkerTool', {})
    147 
    148   pdb_path = linker.get('ProgramDatabaseFile')
    149   if pdb_path:
    150     return pdb_path
    151 
    152   variables = target_dict.get('variables', {})
    153   pdb_path = variables.get('msvs_large_pdb_path', None)
    154   if pdb_path:
    155     return pdb_path
    156 
    157 
    158   pdb_base = target_dict.get('product_name', target_dict['target_name'])
    159   pdb_base = '%s%s.pdb' % (pdb_base, _TARGET_TYPE_EXT[target_dict['type']])
    160   pdb_path = vars['PRODUCT_DIR'] + '/' + pdb_base
    161 
    162   return pdb_path
    163 
    164 
    165 def InsertLargePdbShims(target_list, target_dicts, vars):
    166   """Insert a shim target that forces the linker to use 4KB pagesize PDBs.
    167 
    168   This is a workaround for targets with PDBs greater than 1GB in size, the
    169   limit for the 1KB pagesize PDBs created by the linker by default.
    170 
    171   Arguments:
    172     target_list: List of target pairs: 'base/base.gyp:base'.
    173     target_dicts: Dict of target properties keyed on target pair.
    174     vars: A dictionary of common GYP variables with generator-specific values.
    175   Returns:
    176     Tuple of the shimmed version of the inputs.
    177   """
    178   # Determine which targets need shimming.
    179   targets_to_shim = []
    180   for t in target_dicts:
    181     target_dict = target_dicts[t]
    182 
    183     # We only want to shim targets that have msvs_large_pdb enabled.
    184     if not int(target_dict.get('msvs_large_pdb', 0)):
    185       continue
    186     # This is intended for executable, shared_library and loadable_module
    187     # targets where every configuration is set up to produce a PDB output.
    188     # If any of these conditions is not true then the shim logic will fail
    189     # below.
    190     targets_to_shim.append(t)
    191 
    192   large_pdb_shim_cc = _GetLargePdbShimCcPath()
    193 
    194   for t in targets_to_shim:
    195     target_dict = target_dicts[t]
    196     target_name = target_dict.get('target_name')
    197 
    198     base_dict = _DeepCopySomeKeys(target_dict,
    199           ['configurations', 'default_configuration', 'toolset'])
    200 
    201     # This is the dict for copying the source file (part of the GYP tree)
    202     # to the intermediate directory of the project. This is necessary because
    203     # we can't always build a relative path to the shim source file (on Windows
    204     # GYP and the project may be on different drives), and Ninja hates absolute
    205     # paths (it ends up generating the .obj and .obj.d alongside the source
    206     # file, polluting GYPs tree).
    207     copy_suffix = 'large_pdb_copy'
    208     copy_target_name = target_name + '_' + copy_suffix
    209     full_copy_target_name = _SuffixName(t, copy_suffix)
    210     shim_cc_basename = os.path.basename(large_pdb_shim_cc)
    211     shim_cc_dir = vars['SHARED_INTERMEDIATE_DIR'] + '/' + copy_target_name
    212     shim_cc_path = shim_cc_dir + '/' + shim_cc_basename
    213     copy_dict = copy.deepcopy(base_dict)
    214     copy_dict['target_name'] = copy_target_name
    215     copy_dict['type'] = 'none'
    216     copy_dict['sources'] = [ large_pdb_shim_cc ]
    217     copy_dict['copies'] = [{
    218       'destination': shim_cc_dir,
    219       'files': [ large_pdb_shim_cc ]
    220     }]
    221 
    222     # This is the dict for the PDB generating shim target. It depends on the
    223     # copy target.
    224     shim_suffix = 'large_pdb_shim'
    225     shim_target_name = target_name + '_' + shim_suffix
    226     full_shim_target_name = _SuffixName(t, shim_suffix)
    227     shim_dict = copy.deepcopy(base_dict)
    228     shim_dict['target_name'] = shim_target_name
    229     shim_dict['type'] = 'static_library'
    230     shim_dict['sources'] = [ shim_cc_path ]
    231     shim_dict['dependencies'] = [ full_copy_target_name ]
    232 
    233     # Set up the shim to output its PDB to the same location as the final linker
    234     # target.
    235     for config_name, config in shim_dict.get('configurations').iteritems():
    236       pdb_path = _GetPdbPath(target_dict, config_name, vars)
    237 
    238       # A few keys that we don't want to propagate.
    239       for key in ['msvs_precompiled_header', 'msvs_precompiled_source', 'test']:
    240         config.pop(key, None)
    241 
    242       msvs = config.setdefault('msvs_settings', {})
    243 
    244       # Update the compiler directives in the shim target.
    245       compiler = msvs.setdefault('VCCLCompilerTool', {})
    246       compiler['DebugInformationFormat'] = '3'
    247       compiler['ProgramDataBaseFileName'] = pdb_path
    248 
    249       # Set the explicit PDB path in the appropriate configuration of the
    250       # original target.
    251       config = target_dict['configurations'][config_name]
    252       msvs = config.setdefault('msvs_settings', {})
    253       linker = msvs.setdefault('VCLinkerTool', {})
    254       linker['GenerateDebugInformation'] = 'true'
    255       linker['ProgramDatabaseFile'] = pdb_path
    256 
    257     # Add the new targets. They must go to the beginning of the list so that
    258     # the dependency generation works as expected in ninja.
    259     target_list.insert(0, full_copy_target_name)
    260     target_list.insert(0, full_shim_target_name)
    261     target_dicts[full_copy_target_name] = copy_dict
    262     target_dicts[full_shim_target_name] = shim_dict
    263 
    264     # Update the original target to depend on the shim target.
    265     target_dict.setdefault('dependencies', []).append(full_shim_target_name)
    266 
    267   return (target_list, target_dicts)