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.
5 """GYP backend that generates Eclipse CDT settings files.
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).
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.
17 This generator has no automated tests, so expect it to be broken.
20 from xml.sax.saxutils import escape
25 import gyp.msvs_emulation
28 generator_wants_static_library_dependencies_adjusted = False
30 generator_default_variables = {
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'
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] = ''
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'
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)
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', [])
67 gyp.msvs_emulation.CalculateCommonVariables(default_variables, params)
70 def CalculateGeneratorInputInfo(params):
71 """Calculate the generator specific info that gets fed to input (called by
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
79 def GetAllIncludeDirectories(target_list, target_dicts,
80 shared_intermediate_dirs, config_name, params,
82 """Calculate the set of include directories to be used.
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.
89 gyp_includes_set = set()
90 compiler_includes_list = []
92 # Find compiler's default include dirs.
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:
101 # #include "..." search starts here:
102 # #include <...> search starts here:
103 # /usr/include/c++/4.6
105 # End of search list.
107 in_include_list = False
108 for line in output.splitlines():
109 if line.startswith('#include'):
110 in_include_list = True
112 if line.startswith('End of search list.'):
115 include_dir = line.strip()
116 if include_dir not in compiler_includes_list:
117 compiler_includes_list.append(include_dir)
119 flavor = gyp.common.GetFlavor(params)
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]
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
132 msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
133 cflags = msvs_settings.GetCflags(config_name)
135 cflags = config['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)
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)
152 include_dir = base_dir + '/' + include_dir
153 include_dir = os.path.abspath(include_dir)
155 gyp_includes_set.add(include_dir)
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)
165 return all_includes_list
168 def GetCompilerPath(target_list, data, options):
169 """Determine a command that can be used to invoke the compiler.
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.
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)
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)
192 def GetAllDefines(target_list, target_dicts, data, config_name, params,
194 """Calculate the defines for a project.
197 A dict that includes explict defines declared in gyp files along with all of
198 the default defines that the compiler uses.
201 # Get defines declared in the gyp files.
203 flavor = gyp.common.GetFlavor(params)
205 generator_flags = params.get('generator_flags', {})
206 for target_name in target_list:
207 target = target_dicts[target_name]
210 msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags)
211 extra_defines = msvs_settings.GetComputedDefines(config_name)
214 if config_name in target['configurations']:
215 config = target['configurations'][config_name]
216 target_defines = config['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:
226 all_defines[split_define[0].strip()] = split_define[1].strip()
227 # Get default compiler defines (if possible).
229 return all_defines # Default defines already processed in the loop above.
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():
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]
246 all_defines[key] = val
251 def WriteIncludePaths(out, eclipse_langs, include_dirs):
252 """Write the includes section of a CDT settings export file."""
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' %
262 out.write(' </language>\n')
263 out.write(' </section>\n')
266 def WriteMacros(out, eclipse_langs, defines):
267 """Write the macros section of a CDT settings export file."""
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')
281 def GenerateOutputForConfig(target_list, target_dicts, data, params,
283 options = params['options']
284 generator_flags = params.get('generator_flags', {})
286 # build_dir: relative path from source root to our output files.
288 build_dir = os.path.join(generator_flags.get('output_dir', 'out'),
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')]
297 out_name = os.path.join(toplevel_build, 'eclipse-cdt-settings.xml')
298 gyp.common.EnsureDirExists(out_name)
299 out = open(out_name, 'w')
301 out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
302 out.write('<cdtprojectproperties>\n')
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,
313 WriteMacros(out, eclipse_langs, defines)
315 out.write('</cdtprojectproperties>\n')
319 def GenerateOutput(target_list, target_dicts, data, params):
320 """Generate an XML settings file that can be imported into a CDT project."""
322 if params['options'].generator_output:
323 raise NotImplementedError, "--generator_output not implemented for eclipse"
325 user_config = params.get('generator_flags', {}).get('config', None)
327 GenerateOutputForConfig(target_list, target_dicts, data, params,
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,