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 7This backend DOES NOT generate Eclipse CDT projects. Instead, it generates XML 8files that can be imported into an Eclipse CDT project. The XML file contains a 9list of include paths and symbols (i.e. defines). 10 11Because a full .cproject definition is not created by this generator, it's not 12possible to properly define the include dirs and symbols for each file 13individually. Instead, one set of includes/symbols is generated for the entire 14project. This works fairly well (and is a vast improvement in general), but may 15still result in a few indexer issues here and there. 16 17This generator has no automated tests, so expect it to be broken. 18""" 19 20from xml.sax.saxutils import escape 21import os.path 22import subprocess 23import gyp 24import gyp.common 25import gyp.msvs_emulation 26import shlex 27 28generator_wants_static_library_dependencies_adjusted = False 29 30generator_default_variables = { 31} 32 33for 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 37for 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. 48generator_default_variables['SHARED_INTERMEDIATE_DIR'] = \ 49 '$SHARED_INTERMEDIATE_DIR' 50 51 52def 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 70def 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 79def GetAllIncludeDirectories(target_list, target_dicts, 80 shared_intermediate_dirs, config_name, params): 81 """Calculate the set of include directories to be used. 82 83 Returns: 84 A list including all the include_dir's specified for every target followed 85 by any include directories that were added as cflag compiler options. 86 """ 87 88 gyp_includes_set = set() 89 compiler_includes_list = [] 90 91 flavor = gyp.common.GetFlavor(params) 92 if flavor == 'win': 93 generator_flags = params.get('generator_flags', {}) 94 for target_name in target_list: 95 target = target_dicts[target_name] 96 if config_name in target['configurations']: 97 config = target['configurations'][config_name] 98 99 # Look for any include dirs that were explicitly added via cflags. This 100 # may be done in gyp files to force certain includes to come at the end. 101 # TODO(jgreenwald): Change the gyp files to not abuse cflags for this, and 102 # remove this. 103 if flavor == 'win': 104 msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags) 105 cflags = msvs_settings.GetCflags(config_name) 106 else: 107 cflags = config['cflags'] 108 for cflag in cflags: 109 include_dir = '' 110 if cflag.startswith('-I'): 111 include_dir = cflag[2:] 112 if include_dir and not include_dir in compiler_includes_list: 113 compiler_includes_list.append(include_dir) 114 115 # Find standard gyp include dirs. 116 if config.has_key('include_dirs'): 117 include_dirs = config['include_dirs'] 118 for shared_intermediate_dir in shared_intermediate_dirs: 119 for include_dir in include_dirs: 120 include_dir = include_dir.replace('$SHARED_INTERMEDIATE_DIR', 121 shared_intermediate_dir) 122 if not os.path.isabs(include_dir): 123 base_dir = os.path.dirname(target_name) 124 125 include_dir = base_dir + '/' + include_dir 126 include_dir = os.path.abspath(include_dir) 127 128 if not include_dir in gyp_includes_set: 129 gyp_includes_set.add(include_dir) 130 131 132 # Generate a list that has all the include dirs. 133 all_includes_list = list(gyp_includes_set) 134 all_includes_list.sort() 135 for compiler_include in compiler_includes_list: 136 if not compiler_include in gyp_includes_set: 137 all_includes_list.append(compiler_include) 138 139 # All done. 140 return all_includes_list 141 142 143def GetCompilerPath(target_list, target_dicts, data): 144 """Determine a command that can be used to invoke the compiler. 145 146 Returns: 147 If this is a gyp project that has explicit make settings, try to determine 148 the compiler from that. Otherwise, see if a compiler was specified via the 149 CC_target environment variable. 150 """ 151 152 # First, see if the compiler is configured in make's settings. 153 build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) 154 make_global_settings_dict = data[build_file].get('make_global_settings', {}) 155 for key, value in make_global_settings_dict: 156 if key in ['CC', 'CXX']: 157 return value 158 159 # Check to see if the compiler was specified as an environment variable. 160 for key in ['CC_target', 'CC', 'CXX']: 161 compiler = os.environ.get(key) 162 if compiler: 163 return compiler 164 165 return 'gcc' 166 167 168def GetAllDefines(target_list, target_dicts, data, config_name, params): 169 """Calculate the defines for a project. 170 171 Returns: 172 A dict that includes explict defines declared in gyp files along with all of 173 the default defines that the compiler uses. 174 """ 175 176 # Get defines declared in the gyp files. 177 all_defines = {} 178 flavor = gyp.common.GetFlavor(params) 179 if flavor == 'win': 180 generator_flags = params.get('generator_flags', {}) 181 for target_name in target_list: 182 target = target_dicts[target_name] 183 184 if flavor == 'win': 185 msvs_settings = gyp.msvs_emulation.MsvsSettings(target, generator_flags) 186 extra_defines = msvs_settings.GetComputedDefines(config_name) 187 else: 188 extra_defines = [] 189 if config_name in target['configurations']: 190 config = target['configurations'][config_name] 191 target_defines = config['defines'] 192 else: 193 target_defines = [] 194 for define in target_defines + extra_defines: 195 split_define = define.split('=', 1) 196 if len(split_define) == 1: 197 split_define.append('1') 198 if split_define[0].strip() in all_defines: 199 # Already defined 200 continue 201 all_defines[split_define[0].strip()] = split_define[1].strip() 202 # Get default compiler defines (if possible). 203 if flavor == 'win': 204 return all_defines # Default defines already processed in the loop above. 205 cc_target = GetCompilerPath(target_list, target_dicts, data) 206 if cc_target: 207 command = shlex.split(cc_target) 208 command.extend(['-E', '-dM', '-']) 209 cpp_proc = subprocess.Popen(args=command, cwd='.', 210 stdin=subprocess.PIPE, stdout=subprocess.PIPE) 211 cpp_output = cpp_proc.communicate()[0] 212 cpp_lines = cpp_output.split('\n') 213 for cpp_line in cpp_lines: 214 if not cpp_line.strip(): 215 continue 216 cpp_line_parts = cpp_line.split(' ', 2) 217 key = cpp_line_parts[1] 218 if len(cpp_line_parts) >= 3: 219 val = cpp_line_parts[2] 220 else: 221 val = '1' 222 all_defines[key] = val 223 224 return all_defines 225 226 227def WriteIncludePaths(out, eclipse_langs, include_dirs): 228 """Write the includes section of a CDT settings export file.""" 229 230 out.write(' <section name="org.eclipse.cdt.internal.ui.wizards.' \ 231 'settingswizards.IncludePaths">\n') 232 out.write(' <language name="holder for library settings"></language>\n') 233 for lang in eclipse_langs: 234 out.write(' <language name="%s">\n' % lang) 235 for include_dir in include_dirs: 236 out.write(' <includepath workspace_path="false">%s</includepath>\n' % 237 include_dir) 238 out.write(' </language>\n') 239 out.write(' </section>\n') 240 241 242def WriteMacros(out, eclipse_langs, defines): 243 """Write the macros section of a CDT settings export file.""" 244 245 out.write(' <section name="org.eclipse.cdt.internal.ui.wizards.' \ 246 'settingswizards.Macros">\n') 247 out.write(' <language name="holder for library settings"></language>\n') 248 for lang in eclipse_langs: 249 out.write(' <language name="%s">\n' % lang) 250 for key in sorted(defines.iterkeys()): 251 out.write(' <macro><name>%s</name><value>%s</value></macro>\n' % 252 (escape(key), escape(defines[key]))) 253 out.write(' </language>\n') 254 out.write(' </section>\n') 255 256 257def GenerateOutputForConfig(target_list, target_dicts, data, params, 258 config_name): 259 options = params['options'] 260 generator_flags = params.get('generator_flags', {}) 261 262 # build_dir: relative path from source root to our output files. 263 # e.g. "out/Debug" 264 build_dir = os.path.join(generator_flags.get('output_dir', 'out'), 265 config_name) 266 267 toplevel_build = os.path.join(options.toplevel_dir, build_dir) 268 # Ninja uses out/Debug/gen while make uses out/Debug/obj/gen as the 269 # SHARED_INTERMEDIATE_DIR. Include both possible locations. 270 shared_intermediate_dirs = [os.path.join(toplevel_build, 'obj', 'gen'), 271 os.path.join(toplevel_build, 'gen')] 272 273 out_name = os.path.join(toplevel_build, 'eclipse-cdt-settings.xml') 274 gyp.common.EnsureDirExists(out_name) 275 out = open(out_name, 'w') 276 277 out.write('<?xml version="1.0" encoding="UTF-8"?>\n') 278 out.write('<cdtprojectproperties>\n') 279 280 eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File', 281 'GNU C++', 'GNU C', 'Assembly'] 282 include_dirs = GetAllIncludeDirectories(target_list, target_dicts, 283 shared_intermediate_dirs, config_name, 284 params) 285 WriteIncludePaths(out, eclipse_langs, include_dirs) 286 defines = GetAllDefines(target_list, target_dicts, data, config_name, params) 287 WriteMacros(out, eclipse_langs, defines) 288 289 out.write('</cdtprojectproperties>\n') 290 out.close() 291 292 293def GenerateOutput(target_list, target_dicts, data, params): 294 """Generate an XML settings file that can be imported into a CDT project.""" 295 296 if params['options'].generator_output: 297 raise NotImplementedError, "--generator_output not implemented for eclipse" 298 299 user_config = params.get('generator_flags', {}).get('config', None) 300 if user_config: 301 GenerateOutputForConfig(target_list, target_dicts, data, params, 302 user_config) 303 else: 304 config_names = target_dicts[target_list[0]]['configurations'].keys() 305 for config_name in config_names: 306 GenerateOutputForConfig(target_list, target_dicts, data, params, 307 config_name) 308 309