Completed
Branch rebuild (e7a2d2)
by Glenn
08:26
created

GetAllIncludeDirectories()   F

Complexity

Conditions 20

Size

Total Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 87
c 0
b 0
f 0
rs 2
cc 20

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like GetAllIncludeDirectories() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Copyright (c) 2012 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
"""GYP backend that generates Eclipse CDT settings files.
6
7
This backend DOES NOT generate Eclipse CDT projects. Instead, it generates XML
8
files that can be imported into an Eclipse CDT project. The XML file contains a
9
list of include paths and symbols (i.e. defines).
10
11
Because a full .cproject definition is not created by this generator, it's not
12
possible to properly define the include dirs and symbols for each file
13
individually.  Instead, one set of includes/symbols is generated for the entire
14
project.  This works fairly well (and is a vast improvement in general), but may
15
still result in a few indexer issues here and there.
16
17
This generator has no automated tests, so expect it to be broken.
18
"""
19
20
from xml.sax.saxutils import escape
21
import os.path
22
import subprocess
23
import gyp
24
import gyp.common
25
import gyp.msvs_emulation
26
import shlex
27
import xml.etree.cElementTree as ET
28
29
generator_wants_static_library_dependencies_adjusted = False
30
31
generator_default_variables = {
32
}
33
34
for dirname in ['INTERMEDIATE_DIR', 'PRODUCT_DIR', 'LIB_DIR', 'SHARED_LIB_DIR']:
35
  # Some gyp steps fail if these are empty(!), so we convert them to variables
36
  generator_default_variables[dirname] = '$' + dirname
37
38
for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME',
39
               'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT',
40
               'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX',
41
               'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX',
42
               'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX',
43
               'CONFIGURATION_NAME']:
44
  generator_default_variables[unused] = ''
45
46
# Include dirs will occasionally use the SHARED_INTERMEDIATE_DIR variable as
47
# part of the path when dealing with generated headers.  This value will be
48
# replaced dynamically for each configuration.
49
generator_default_variables['SHARED_INTERMEDIATE_DIR'] = \
50
    '$SHARED_INTERMEDIATE_DIR'
51
52
53
def CalculateVariables(default_variables, params):
54
  generator_flags = params.get('generator_flags', {})
55
  for key, val in generator_flags.items():
56
    default_variables.setdefault(key, val)
57
  flavor = gyp.common.GetFlavor(params)
58
  default_variables.setdefault('OS', flavor)
59
  if flavor == 'win':
60
    # Copy additional generator configuration data from VS, which is shared
61
    # by the Eclipse generator.
62
    import gyp.generator.msvs as msvs_generator
63
    generator_additional_non_configuration_keys = getattr(msvs_generator,
64
        'generator_additional_non_configuration_keys', [])
65
    generator_additional_path_sections = getattr(msvs_generator,
66
        'generator_additional_path_sections', [])
67
68
    gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
69
70
71
def CalculateGeneratorInputInfo(params):
72
  """Calculate the generator specific info that gets fed to input (called by
73
  gyp)."""
74
  generator_flags = params.get('generator_flags', {})
75
  if generator_flags.get('adjust_static_libraries', False):
76
    global generator_wants_static_library_dependencies_adjusted
77
    generator_wants_static_library_dependencies_adjusted = True
78
79
80
def GetAllIncludeDirectories(target_list, target_dicts,
81
                             shared_intermediate_dirs, config_name, params,
82
                             compiler_path):
83
  """Calculate the set of include directories to be used.
84
85
  Returns:
86
    A list including all the include_dir's specified for every target followed
87
    by any include directories that were added as cflag compiler options.
88
  """
89
90
  gyp_includes_set = set()
91
  compiler_includes_list = []
92
93
  # Find compiler's default include dirs.
94
  if compiler_path:
95
    command = shlex.split(compiler_path)
96
    command.extend(['-E', '-xc++', '-v', '-'])
97
    proc = subprocess.Popen(args=command, stdin=subprocess.PIPE,
98
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
99
    output = proc.communicate()[1]
100
    # Extract the list of include dirs from the output, which has this format:
101
    #   ...
102
    #   #include "..." search starts here:
103
    #   #include <...> search starts here:
104
    #    /usr/include/c++/4.6
105
    #    /usr/local/include
106
    #   End of search list.
107
    #   ...
108
    in_include_list = False
109
    for line in output.splitlines():
110
      if line.startswith('#include'):
111
        in_include_list = True
112
        continue
113
      if line.startswith('End of search list.'):
114
        break
115
      if in_include_list:
116
        include_dir = line.strip()
117
        if include_dir not in compiler_includes_list:
118
          compiler_includes_list.append(include_dir)
119
120
  flavor = gyp.common.GetFlavor(params)
121
  if flavor == 'win':
122
    generator_flags = params.get('generator_flags', {})
123
  for target_name in target_list:
124
    target = target_dicts[target_name]
125
    if config_name in target['configurations']:
126
      config = target['configurations'][config_name]
127
128
      # Look for any include dirs that were explicitly added via cflags. This
129
      # may be done in gyp files to force certain includes to come at the end.
130
      # TODO(jgreenwald): Change the gyp files to not abuse cflags for this, and
131
      # remove this.
132
      if flavor == 'win':
133
        msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
134
        cflags = msvs_settings.GetCflags(config_name)
135
      else:
136
        cflags = config['cflags']
137
      for cflag in cflags:
138
        if cflag.startswith('-I'):
139
          include_dir = cflag[2:]
140
          if include_dir not in compiler_includes_list:
141
            compiler_includes_list.append(include_dir)
142
143
      # Find standard gyp include dirs.
144
      if config.has_key('include_dirs'):
145
        include_dirs = config['include_dirs']
146
        for shared_intermediate_dir in shared_intermediate_dirs:
147
          for include_dir in include_dirs:
148
            include_dir = include_dir.replace('$SHARED_INTERMEDIATE_DIR',
149
                                              shared_intermediate_dir)
150
            if not os.path.isabs(include_dir):
151
              base_dir = os.path.dirname(target_name)
152
153
              include_dir = base_dir + '/' + include_dir
154
              include_dir = os.path.abspath(include_dir)
155
156
            gyp_includes_set.add(include_dir)
157
158
  # Generate a list that has all the include dirs.
159
  all_includes_list = list(gyp_includes_set)
160
  all_includes_list.sort()
161
  for compiler_include in compiler_includes_list:
162
    if not compiler_include in gyp_includes_set:
163
      all_includes_list.append(compiler_include)
164
165
  # All done.
166
  return all_includes_list
167
168
169
def GetCompilerPath(target_list, data, options):
170
  """Determine a command that can be used to invoke the compiler.
171
172
  Returns:
173
    If this is a gyp project that has explicit make settings, try to determine
174
    the compiler from that.  Otherwise, see if a compiler was specified via the
175
    CC_target environment variable.
176
  """
177
  # First, see if the compiler is configured in make's settings.
178
  build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
179
  make_global_settings_dict = data[build_file].get('make_global_settings', {})
180
  for key, value in make_global_settings_dict:
181
    if key in ['CC', 'CXX']:
182
      return os.path.join(options.toplevel_dir, value)
183
184
  # Check to see if the compiler was specified as an environment variable.
185
  for key in ['CC_target', 'CC', 'CXX']:
186
    compiler = os.environ.get(key)
187
    if compiler:
188
      return compiler
189
190
  return 'gcc'
191
192
193
def GetAllDefines(target_list, target_dicts, data, config_name, params,
194
                  compiler_path):
195
  """Calculate the defines for a project.
196
197
  Returns:
198
    A dict that includes explict defines declared in gyp files along with all of
199
    the default defines that the compiler uses.
200
  """
201
202
  # Get defines declared in the gyp files.
203
  all_defines = {}
204
  flavor = gyp.common.GetFlavor(params)
205
  if flavor == 'win':
206
    generator_flags = params.get('generator_flags', {})
207
  for target_name in target_list:
208
    target = target_dicts[target_name]
209
210
    if flavor == 'win':
211
      msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
212
      extra_defines = msvs_settings.GetComputedDefines(config_name)
213
    else:
214
      extra_defines = []
215
    if config_name in target['configurations']:
216
      config = target['configurations'][config_name]
217
      target_defines = config['defines']
218
    else:
219
      target_defines = []
220
    for define in target_defines + extra_defines:
221
      split_define = define.split('=', 1)
222
      if len(split_define) == 1:
223
        split_define.append('1')
224
      if split_define[0].strip() in all_defines:
225
        # Already defined
226
        continue
227
      all_defines[split_define[0].strip()] = split_define[1].strip()
228
  # Get default compiler defines (if possible).
229
  if flavor == 'win':
230
    return all_defines  # Default defines already processed in the loop above.
231
  if compiler_path:
232
    command = shlex.split(compiler_path)
233
    command.extend(['-E', '-dM', '-'])
234
    cpp_proc = subprocess.Popen(args=command, cwd='.',
235
                                stdin=subprocess.PIPE, stdout=subprocess.PIPE)
236
    cpp_output = cpp_proc.communicate()[0]
237
    cpp_lines = cpp_output.split('\n')
238
    for cpp_line in cpp_lines:
239
      if not cpp_line.strip():
240
        continue
241
      cpp_line_parts = cpp_line.split(' ', 2)
242
      key = cpp_line_parts[1]
243
      if len(cpp_line_parts) >= 3:
244
        val = cpp_line_parts[2]
245
      else:
246
        val = '1'
247
      all_defines[key] = val
248
249
  return all_defines
250
251
252
def WriteIncludePaths(out, eclipse_langs, include_dirs):
253
  """Write the includes section of a CDT settings export file."""
254
255
  out.write('  <section name="org.eclipse.cdt.internal.ui.wizards.' \
256
            'settingswizards.IncludePaths">\n')
257
  out.write('    <language name="holder for library settings"></language>\n')
258
  for lang in eclipse_langs:
259
    out.write('    <language name="%s">\n' % lang)
260
    for include_dir in include_dirs:
261
      out.write('      <includepath workspace_path="false">%s</includepath>\n' %
262
                include_dir)
263
    out.write('    </language>\n')
264
  out.write('  </section>\n')
265
266
267
def WriteMacros(out, eclipse_langs, defines):
268
  """Write the macros section of a CDT settings export file."""
269
270
  out.write('  <section name="org.eclipse.cdt.internal.ui.wizards.' \
271
            'settingswizards.Macros">\n')
272
  out.write('    <language name="holder for library settings"></language>\n')
273
  for lang in eclipse_langs:
274
    out.write('    <language name="%s">\n' % lang)
275
    for key in sorted(defines.iterkeys()):
276
      out.write('      <macro><name>%s</name><value>%s</value></macro>\n' %
277
                (escape(key), escape(defines[key])))
278
    out.write('    </language>\n')
279
  out.write('  </section>\n')
280
281
282
def GenerateOutputForConfig(target_list, target_dicts, data, params,
283
                            config_name):
284
  options = params['options']
285
  generator_flags = params.get('generator_flags', {})
286
287
  # build_dir: relative path from source root to our output files.
288
  # e.g. "out/Debug"
289
  build_dir = os.path.join(generator_flags.get('output_dir', 'out'),
290
                           config_name)
291
292
  toplevel_build = os.path.join(options.toplevel_dir, build_dir)
293
  # Ninja uses out/Debug/gen while make uses out/Debug/obj/gen as the
294
  # SHARED_INTERMEDIATE_DIR. Include both possible locations.
295
  shared_intermediate_dirs = [os.path.join(toplevel_build, 'obj', 'gen'),
296
                              os.path.join(toplevel_build, 'gen')]
297
298
  GenerateCdtSettingsFile(target_list,
299
                          target_dicts,
300
                          data,
301
                          params,
302
                          config_name,
303
                          os.path.join(toplevel_build,
304
                                       'eclipse-cdt-settings.xml'),
305
                          options,
306
                          shared_intermediate_dirs)
307
  GenerateClasspathFile(target_list,
308
                        target_dicts,
309
                        options.toplevel_dir,
310
                        toplevel_build,
311
                        os.path.join(toplevel_build,
312
                                     'eclipse-classpath.xml'))
313
314
315
def GenerateCdtSettingsFile(target_list, target_dicts, data, params,
316
                            config_name, out_name, options,
317
                            shared_intermediate_dirs):
318
  gyp.common.EnsureDirExists(out_name)
319
  with open(out_name, 'w') as out:
320
    out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
321
    out.write('<cdtprojectproperties>\n')
322
323
    eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File',
324
                     'GNU C++', 'GNU C', 'Assembly']
325
    compiler_path = GetCompilerPath(target_list, data, options)
326
    include_dirs = GetAllIncludeDirectories(target_list, target_dicts,
327
                                            shared_intermediate_dirs,
328
                                            config_name, params, compiler_path)
329
    WriteIncludePaths(out, eclipse_langs, include_dirs)
330
    defines = GetAllDefines(target_list, target_dicts, data, config_name,
331
                            params, compiler_path)
332
    WriteMacros(out, eclipse_langs, defines)
333
334
    out.write('</cdtprojectproperties>\n')
335
336
337
def GenerateClasspathFile(target_list, target_dicts, toplevel_dir,
338
                          toplevel_build, out_name):
339
  '''Generates a classpath file suitable for symbol navigation and code
340
  completion of Java code (such as in Android projects) by finding all
341
  .java and .jar files used as action inputs.'''
342
  gyp.common.EnsureDirExists(out_name)
343
  result = ET.Element('classpath')
344
345
  def AddElements(kind, paths):
346
    # First, we need to normalize the paths so they are all relative to the
347
    # toplevel dir.
348
    rel_paths = set()
349
    for path in paths:
350
      if os.path.isabs(path):
351
        rel_paths.add(os.path.relpath(path, toplevel_dir))
352
      else:
353
        rel_paths.add(path)
354
355
    for path in sorted(rel_paths):
356
      entry_element = ET.SubElement(result, 'classpathentry')
357
      entry_element.set('kind', kind)
358
      entry_element.set('path', path)
359
360
  AddElements('lib', GetJavaJars(target_list, target_dicts, toplevel_dir))
361
  AddElements('src', GetJavaSourceDirs(target_list, target_dicts, toplevel_dir))
362
  # Include the standard JRE container and a dummy out folder
363
  AddElements('con', ['org.eclipse.jdt.launching.JRE_CONTAINER'])
364
  # Include a dummy out folder so that Eclipse doesn't use the default /bin
365
  # folder in the root of the project.
366
  AddElements('output', [os.path.join(toplevel_build, '.eclipse-java-build')])
367
368
  ET.ElementTree(result).write(out_name)
369
370
371
def GetJavaJars(target_list, target_dicts, toplevel_dir):
372
  '''Generates a sequence of all .jars used as inputs.'''
373
  for target_name in target_list:
374
    target = target_dicts[target_name]
375
    for action in target.get('actions', []):
376
      for input_ in action['inputs']:
377
        if os.path.splitext(input_)[1] == '.jar' and not input_.startswith('$'):
378
          if os.path.isabs(input_):
379
            yield input_
380
          else:
381
            yield os.path.join(os.path.dirname(target_name), input_)
382
383
384
def GetJavaSourceDirs(target_list, target_dicts, toplevel_dir):
385
  '''Generates a sequence of all likely java package root directories.'''
386
  for target_name in target_list:
387
    target = target_dicts[target_name]
388
    for action in target.get('actions', []):
389
      for input_ in action['inputs']:
390
        if (os.path.splitext(input_)[1] == '.java' and
391
            not input_.startswith('$')):
392
          dir_ = os.path.dirname(os.path.join(os.path.dirname(target_name),
393
                                              input_))
394
          # If there is a parent 'src' or 'java' folder, navigate up to it -
395
          # these are canonical package root names in Chromium.  This will
396
          # break if 'src' or 'java' exists in the package structure. This
397
          # could be further improved by inspecting the java file for the
398
          # package name if this proves to be too fragile in practice.
399
          parent_search = dir_
400
          while os.path.basename(parent_search) not in ['src', 'java']:
401
            parent_search, _ = os.path.split(parent_search)
402
            if not parent_search or parent_search == toplevel_dir:
403
              # Didn't find a known root, just return the original path
404
              yield dir_
405
              break
406
          else:
407
            yield parent_search
408
409
410
def GenerateOutput(target_list, target_dicts, data, params):
411
  """Generate an XML settings file that can be imported into a CDT project."""
412
413
  if params['options'].generator_output:
414
    raise NotImplementedError("--generator_output not implemented for eclipse")
415
416
  user_config = params.get('generator_flags', {}).get('config', None)
417
  if user_config:
418
    GenerateOutputForConfig(target_list, target_dicts, data, params,
419
                            user_config)
420
  else:
421
    config_names = target_dicts[target_list[0]]['configurations'].keys()
422
    for config_name in config_names:
423
      GenerateOutputForConfig(target_list, target_dicts, data, params,
424
                              config_name)
425
426