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