Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / 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
28 generator_wants_static_library_dependencies_adjusted = False
29
30 generator_default_variables = {
31 }
32
33 for dirname in ['INTERMEDIATE_DIR', 'PRODUCT_DIR', 'LIB_DIR', 'SHARED_LIB_DIR']:
34   # Some gyp steps fail if these are empty(!).
35   generator_default_variables[dirname] = 'dir'
36
37 for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME',
38                'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT',
39                'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX',
40                'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX',
41                'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX',
42                'CONFIGURATION_NAME']:
43   generator_default_variables[unused] = ''
44
45 # Include dirs will occasionally use the SHARED_INTERMEDIATE_DIR variable as
46 # part of the path when dealing with generated headers.  This value will be
47 # replaced dynamically for each configuration.
48 generator_default_variables['SHARED_INTERMEDIATE_DIR'] = \
49     '$SHARED_INTERMEDIATE_DIR'
50
51
52 def CalculateVariables(default_variables, params):
53   generator_flags = params.get('generator_flags', {})
54   for key, val in generator_flags.items():
55     default_variables.setdefault(key, val)
56   flavor = gyp.common.GetFlavor(params)
57   default_variables.setdefault('OS', flavor)
58   if flavor == 'win':
59     # Copy additional generator configuration data from VS, which is shared
60     # by the Eclipse generator.
61     import gyp.generator.msvs as msvs_generator
62     generator_additional_non_configuration_keys = getattr(msvs_generator,
63         'generator_additional_non_configuration_keys', [])
64     generator_additional_path_sections = getattr(msvs_generator,
65         'generator_additional_path_sections', [])
66
67     gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
68
69
70 def CalculateGeneratorInputInfo(params):
71   """Calculate the generator specific info that gets fed to input (called by
72   gyp)."""
73   generator_flags = params.get('generator_flags', {})
74   if generator_flags.get('adjust_static_libraries', False):
75     global generator_wants_static_library_dependencies_adjusted
76     generator_wants_static_library_dependencies_adjusted = True
77
78
79 def GetAllIncludeDirectories(target_list, target_dicts,
80                              shared_intermediate_dirs, config_name, params,
81                              compiler_path):
82   """Calculate the set of include directories to be used.
83
84   Returns:
85     A list including all the include_dir's specified for every target followed
86     by any include directories that were added as cflag compiler options.
87   """
88
89   gyp_includes_set = set()
90   compiler_includes_list = []
91
92   # Find compiler's default include dirs.
93   if compiler_path:
94     command = shlex.split(compiler_path)
95     command.extend(['-E', '-xc++', '-v', '-'])
96     proc = subprocess.Popen(args=command, stdin=subprocess.PIPE,
97                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
98     output = proc.communicate()[1]
99     # Extract the list of include dirs from the output, which has this format:
100     #   ...
101     #   #include "..." search starts here:
102     #   #include <...> search starts here:
103     #    /usr/include/c++/4.6
104     #    /usr/local/include
105     #   End of search list.
106     #   ...
107     in_include_list = False
108     for line in output.splitlines():
109       if line.startswith('#include'):
110         in_include_list = True
111         continue
112       if line.startswith('End of search list.'):
113         break
114       if in_include_list:
115         include_dir = line.strip()
116         if include_dir not in compiler_includes_list:
117           compiler_includes_list.append(include_dir)
118
119   flavor = gyp.common.GetFlavor(params)
120   if flavor == 'win':
121     generator_flags = params.get('generator_flags', {})
122   for target_name in target_list:
123     target = target_dicts[target_name]
124     if config_name in target['configurations']:
125       config = target['configurations'][config_name]
126
127       # Look for any include dirs that were explicitly added via cflags. This
128       # may be done in gyp files to force certain includes to come at the end.
129       # TODO(jgreenwald): Change the gyp files to not abuse cflags for this, and
130       # remove this.
131       if flavor == 'win':
132         msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
133         cflags = msvs_settings.GetCflags(config_name)
134       else:
135         cflags = config['cflags']
136       for cflag in cflags:
137         if cflag.startswith('-I'):
138           include_dir = cflag[2:]
139           if include_dir not in compiler_includes_list:
140             compiler_includes_list.append(include_dir)
141
142       # Find standard gyp include dirs.
143       if config.has_key('include_dirs'):
144         include_dirs = config['include_dirs']
145         for shared_intermediate_dir in shared_intermediate_dirs:
146           for include_dir in include_dirs:
147             include_dir = include_dir.replace('$SHARED_INTERMEDIATE_DIR',
148                                               shared_intermediate_dir)
149             if not os.path.isabs(include_dir):
150               base_dir = os.path.dirname(target_name)
151
152               include_dir = base_dir + '/' + include_dir
153               include_dir = os.path.abspath(include_dir)
154
155             gyp_includes_set.add(include_dir)
156
157   # Generate a list that has all the include dirs.
158   all_includes_list = list(gyp_includes_set)
159   all_includes_list.sort()
160   for compiler_include in compiler_includes_list:
161     if not compiler_include in gyp_includes_set:
162       all_includes_list.append(compiler_include)
163
164   # All done.
165   return all_includes_list
166
167
168 def GetCompilerPath(target_list, data, options):
169   """Determine a command that can be used to invoke the compiler.
170
171   Returns:
172     If this is a gyp project that has explicit make settings, try to determine
173     the compiler from that.  Otherwise, see if a compiler was specified via the
174     CC_target environment variable.
175   """
176   # First, see if the compiler is configured in make's settings.
177   build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
178   make_global_settings_dict = data[build_file].get('make_global_settings', {})
179   for key, value in make_global_settings_dict:
180     if key in ['CC', 'CXX']:
181       return os.path.join(options.toplevel_dir, value)
182
183   # Check to see if the compiler was specified as an environment variable.
184   for key in ['CC_target', 'CC', 'CXX']:
185     compiler = os.environ.get(key)
186     if compiler:
187       return compiler
188
189   return 'gcc'
190
191
192 def GetAllDefines(target_list, target_dicts, data, config_name, params,
193                   compiler_path):
194   """Calculate the defines for a project.
195
196   Returns:
197     A dict that includes explict defines declared in gyp files along with all of
198     the default defines that the compiler uses.
199   """
200
201   # Get defines declared in the gyp files.
202   all_defines = {}
203   flavor = gyp.common.GetFlavor(params)
204   if flavor == 'win':
205     generator_flags = params.get('generator_flags', {})
206   for target_name in target_list:
207     target = target_dicts[target_name]
208
209     if flavor == 'win':
210       msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
211       extra_defines = msvs_settings.GetComputedDefines(config_name)
212     else:
213       extra_defines = []
214     if config_name in target['configurations']:
215       config = target['configurations'][config_name]
216       target_defines = config['defines']
217     else:
218       target_defines = []
219     for define in target_defines + extra_defines:
220       split_define = define.split('=', 1)
221       if len(split_define) == 1:
222         split_define.append('1')
223       if split_define[0].strip() in all_defines:
224         # Already defined
225         continue
226       all_defines[split_define[0].strip()] = split_define[1].strip()
227   # Get default compiler defines (if possible).
228   if flavor == 'win':
229     return all_defines  # Default defines already processed in the loop above.
230   if compiler_path:
231     command = shlex.split(compiler_path)
232     command.extend(['-E', '-dM', '-'])
233     cpp_proc = subprocess.Popen(args=command, cwd='.',
234                                 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
235     cpp_output = cpp_proc.communicate()[0]
236     cpp_lines = cpp_output.split('\n')
237     for cpp_line in cpp_lines:
238       if not cpp_line.strip():
239         continue
240       cpp_line_parts = cpp_line.split(' ', 2)
241       key = cpp_line_parts[1]
242       if len(cpp_line_parts) >= 3:
243         val = cpp_line_parts[2]
244       else:
245         val = '1'
246       all_defines[key] = val
247
248   return all_defines
249
250
251 def WriteIncludePaths(out, eclipse_langs, include_dirs):
252   """Write the includes section of a CDT settings export file."""
253
254   out.write('  <section name="org.eclipse.cdt.internal.ui.wizards.' \
255             'settingswizards.IncludePaths">\n')
256   out.write('    <language name="holder for library settings"></language>\n')
257   for lang in eclipse_langs:
258     out.write('    <language name="%s">\n' % lang)
259     for include_dir in include_dirs:
260       out.write('      <includepath workspace_path="false">%s</includepath>\n' %
261                 include_dir)
262     out.write('    </language>\n')
263   out.write('  </section>\n')
264
265
266 def WriteMacros(out, eclipse_langs, defines):
267   """Write the macros section of a CDT settings export file."""
268
269   out.write('  <section name="org.eclipse.cdt.internal.ui.wizards.' \
270             'settingswizards.Macros">\n')
271   out.write('    <language name="holder for library settings"></language>\n')
272   for lang in eclipse_langs:
273     out.write('    <language name="%s">\n' % lang)
274     for key in sorted(defines.iterkeys()):
275       out.write('      <macro><name>%s</name><value>%s</value></macro>\n' %
276                 (escape(key), escape(defines[key])))
277     out.write('    </language>\n')
278   out.write('  </section>\n')
279
280
281 def GenerateOutputForConfig(target_list, target_dicts, data, params,
282                             config_name):
283   options = params['options']
284   generator_flags = params.get('generator_flags', {})
285
286   # build_dir: relative path from source root to our output files.
287   # e.g. "out/Debug"
288   build_dir = os.path.join(generator_flags.get('output_dir', 'out'),
289                            config_name)
290
291   toplevel_build = os.path.join(options.toplevel_dir, build_dir)
292   # Ninja uses out/Debug/gen while make uses out/Debug/obj/gen as the
293   # SHARED_INTERMEDIATE_DIR. Include both possible locations.
294   shared_intermediate_dirs = [os.path.join(toplevel_build, 'obj', 'gen'),
295                               os.path.join(toplevel_build, 'gen')]
296
297   out_name = os.path.join(toplevel_build, 'eclipse-cdt-settings.xml')
298   gyp.common.EnsureDirExists(out_name)
299   out = open(out_name, 'w')
300
301   out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
302   out.write('<cdtprojectproperties>\n')
303
304   eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File',
305                    'GNU C++', 'GNU C', 'Assembly']
306   compiler_path = GetCompilerPath(target_list, data, options)
307   include_dirs = GetAllIncludeDirectories(target_list, target_dicts,
308                                           shared_intermediate_dirs, config_name,
309                                           params, compiler_path)
310   WriteIncludePaths(out, eclipse_langs, include_dirs)
311   defines = GetAllDefines(target_list, target_dicts, data, config_name, params,
312                           compiler_path)
313   WriteMacros(out, eclipse_langs, defines)
314
315   out.write('</cdtprojectproperties>\n')
316   out.close()
317
318
319 def GenerateOutput(target_list, target_dicts, data, params):
320   """Generate an XML settings file that can be imported into a CDT project."""
321
322   if params['options'].generator_output:
323     raise NotImplementedError, "--generator_output not implemented for eclipse"
324
325   user_config = params.get('generator_flags', {}).get('config', None)
326   if user_config:
327     GenerateOutputForConfig(target_list, target_dicts, data, params,
328                             user_config)
329   else:
330     config_names = target_dicts[target_list[0]]['configurations'].keys()
331     for config_name in config_names:
332       GenerateOutputForConfig(target_list, target_dicts, data, params,
333                               config_name)
334