Initial commit
[yaffs-website] / node_modules / node-gyp / gyp / pylib / gyp / generator / eclipse.py
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