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
27 generator_wants_static_library_dependencies_adjusted = False
29 generator_default_variables = {
32 for dirname in ['INTERMEDIATE_DIR', 'PRODUCT_DIR', 'LIB_DIR', 'SHARED_LIB_DIR']:
33 # Some gyp steps fail if these are empty(!).
34 generator_default_variables[dirname] = 'dir'
36 for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME',
37 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT',
38 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX',
39 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX',
40 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX',
41 'CONFIGURATION_NAME']:
42 generator_default_variables[unused] = ''
44 # Include dirs will occasionally use the SHARED_INTERMEDIATE_DIR variable as
45 # part of the path when dealing with generated headers. This value will be
46 # replaced dynamically for each configuration.
47 generator_default_variables['SHARED_INTERMEDIATE_DIR'] = \
48 '$SHARED_INTERMEDIATE_DIR'
51 def CalculateVariables(default_variables, params):
52 generator_flags = params.get('generator_flags', {})
53 for key, val in generator_flags.items():
54 default_variables.setdefault(key, val)
55 default_variables.setdefault('OS', gyp.common.GetFlavor(params))
58 def CalculateGeneratorInputInfo(params):
59 """Calculate the generator specific info that gets fed to input (called by
61 generator_flags = params.get('generator_flags', {})
62 if generator_flags.get('adjust_static_libraries', False):
63 global generator_wants_static_library_dependencies_adjusted
64 generator_wants_static_library_dependencies_adjusted = True
67 def GetAllIncludeDirectories(target_list, target_dicts,
68 shared_intermediate_dirs, config_name):
69 """Calculate the set of include directories to be used.
72 A list including all the include_dir's specified for every target followed
73 by any include directories that were added as cflag compiler options.
76 gyp_includes_set = set()
77 compiler_includes_list = []
79 for target_name in target_list:
80 target = target_dicts[target_name]
81 if config_name in target['configurations']:
82 config = target['configurations'][config_name]
84 # Look for any include dirs that were explicitly added via cflags. This
85 # may be done in gyp files to force certain includes to come at the end.
86 # TODO(jgreenwald): Change the gyp files to not abuse cflags for this, and
88 cflags = config['cflags']
91 if cflag.startswith('-I'):
92 include_dir = cflag[2:]
93 if include_dir and not include_dir in compiler_includes_list:
94 compiler_includes_list.append(include_dir)
96 # Find standard gyp include dirs.
97 if config.has_key('include_dirs'):
98 include_dirs = config['include_dirs']
99 for shared_intermediate_dir in shared_intermediate_dirs:
100 for include_dir in include_dirs:
101 include_dir = include_dir.replace('$SHARED_INTERMEDIATE_DIR',
102 shared_intermediate_dir)
103 if not os.path.isabs(include_dir):
104 base_dir = os.path.dirname(target_name)
106 include_dir = base_dir + '/' + include_dir
107 include_dir = os.path.abspath(include_dir)
109 if not include_dir in gyp_includes_set:
110 gyp_includes_set.add(include_dir)
113 # Generate a list that has all the include dirs.
114 all_includes_list = list(gyp_includes_set)
115 all_includes_list.sort()
116 for compiler_include in compiler_includes_list:
117 if not compiler_include in gyp_includes_set:
118 all_includes_list.append(compiler_include)
121 return all_includes_list
124 def GetCompilerPath(target_list, target_dicts, data):
125 """Determine a command that can be used to invoke the compiler.
128 If this is a gyp project that has explicit make settings, try to determine
129 the compiler from that. Otherwise, see if a compiler was specified via the
130 CC_target environment variable.
133 # First, see if the compiler is configured in make's settings.
134 build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
135 make_global_settings_dict = data[build_file].get('make_global_settings', {})
136 for key, value in make_global_settings_dict:
137 if key in ['CC', 'CXX']:
140 # Check to see if the compiler was specified as an environment variable.
141 for key in ['CC_target', 'CC', 'CXX']:
142 compiler = os.environ.get(key)
149 def GetAllDefines(target_list, target_dicts, data, config_name):
150 """Calculate the defines for a project.
153 A dict that includes explict defines declared in gyp files along with all of
154 the default defines that the compiler uses.
157 # Get defines declared in the gyp files.
159 for target_name in target_list:
160 target = target_dicts[target_name]
162 if config_name in target['configurations']:
163 config = target['configurations'][config_name]
164 for define in config['defines']:
165 split_define = define.split('=', 1)
166 if len(split_define) == 1:
167 split_define.append('1')
168 if split_define[0].strip() in all_defines:
172 all_defines[split_define[0].strip()] = split_define[1].strip()
174 # Get default compiler defines (if possible).
175 cc_target = GetCompilerPath(target_list, target_dicts, data)
177 command = shlex.split(cc_target)
178 command.extend(['-E', '-dM', '-'])
179 cpp_proc = subprocess.Popen(args=command, cwd='.',
180 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
181 cpp_output = cpp_proc.communicate()[0]
182 cpp_lines = cpp_output.split('\n')
183 for cpp_line in cpp_lines:
184 if not cpp_line.strip():
186 cpp_line_parts = cpp_line.split(' ', 2)
187 key = cpp_line_parts[1]
188 if len(cpp_line_parts) >= 3:
189 val = cpp_line_parts[2]
192 all_defines[key] = val
197 def WriteIncludePaths(out, eclipse_langs, include_dirs):
198 """Write the includes section of a CDT settings export file."""
200 out.write(' <section name="org.eclipse.cdt.internal.ui.wizards.' \
201 'settingswizards.IncludePaths">\n')
202 out.write(' <language name="holder for library settings"></language>\n')
203 for lang in eclipse_langs:
204 out.write(' <language name="%s">\n' % lang)
205 for include_dir in include_dirs:
206 out.write(' <includepath workspace_path="false">%s</includepath>\n' %
208 out.write(' </language>\n')
209 out.write(' </section>\n')
212 def WriteMacros(out, eclipse_langs, defines):
213 """Write the macros section of a CDT settings export file."""
215 out.write(' <section name="org.eclipse.cdt.internal.ui.wizards.' \
216 'settingswizards.Macros">\n')
217 out.write(' <language name="holder for library settings"></language>\n')
218 for lang in eclipse_langs:
219 out.write(' <language name="%s">\n' % lang)
220 for key in sorted(defines.iterkeys()):
221 out.write(' <macro><name>%s</name><value>%s</value></macro>\n' %
222 (escape(key), escape(defines[key])))
223 out.write(' </language>\n')
224 out.write(' </section>\n')
227 def GenerateOutputForConfig(target_list, target_dicts, data, params,
229 options = params['options']
230 generator_flags = params.get('generator_flags', {})
232 # build_dir: relative path from source root to our output files.
234 build_dir = os.path.join(generator_flags.get('output_dir', 'out'),
237 toplevel_build = os.path.join(options.toplevel_dir, build_dir)
238 # Ninja uses out/Debug/gen while make uses out/Debug/obj/gen as the
239 # SHARED_INTERMEDIATE_DIR. Include both possible locations.
240 shared_intermediate_dirs = [os.path.join(toplevel_build, 'obj', 'gen'),
241 os.path.join(toplevel_build, 'gen')]
243 if not os.path.exists(toplevel_build):
244 os.makedirs(toplevel_build)
245 out = open(os.path.join(toplevel_build, 'eclipse-cdt-settings.xml'), 'w')
247 out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
248 out.write('<cdtprojectproperties>\n')
250 eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File',
251 'GNU C++', 'GNU C', 'Assembly']
252 include_dirs = GetAllIncludeDirectories(target_list, target_dicts,
253 shared_intermediate_dirs, config_name)
254 WriteIncludePaths(out, eclipse_langs, include_dirs)
255 defines = GetAllDefines(target_list, target_dicts, data, config_name)
256 WriteMacros(out, eclipse_langs, defines)
258 out.write('</cdtprojectproperties>\n')
262 def GenerateOutput(target_list, target_dicts, data, params):
263 """Generate an XML settings file that can be imported into a CDT project."""
265 if params['options'].generator_output:
266 raise NotImplementedError, "--generator_output not implemented for eclipse"
268 user_config = params.get('generator_flags', {}).get('config', None)
270 GenerateOutputForConfig(target_list, target_dicts, data, params,
273 config_names = target_dicts[target_list[0]]['configurations'].keys()
274 for config_name in config_names:
275 GenerateOutputForConfig(target_list, target_dicts, data, params,