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)