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 7import copy 8import os 9 10 11_TARGET_TYPE_EXT = { 12 'executable': '.exe', 13 'loadable_module': '.dll', 14 'shared_library': '.dll', 15} 16 17 18def _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 27def _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 45def _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 59def _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 71def 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 125def _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 165def 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)