| # Copyright (c) 2012 Google Inc. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """GYP backend that generates Eclipse CDT settings files. |
| |
| This backend DOES NOT generate Eclipse CDT projects. Instead, it generates XML |
| files that can be imported into an Eclipse CDT project. The XML file contains a |
| list of include paths and symbols (i.e. defines). |
| |
| Because a full .cproject definition is not created by this generator, it's not |
| possible to properly define the include dirs and symbols for each file |
| individually. Instead, one set of includes/symbols is generated for the entire |
| project. This works fairly well (and is a vast improvement in general), but may |
| still result in a few indexer issues here and there. |
| |
| This generator has no automated tests, so expect it to be broken. |
| """ |
| |
| from xml.sax.saxutils import escape |
| import os.path |
| import subprocess |
| import gyp |
| import gyp.common |
| import shlex |
| |
| generator_wants_static_library_dependencies_adjusted = False |
| |
| generator_default_variables = { |
| } |
| |
| for dirname in ['INTERMEDIATE_DIR', 'PRODUCT_DIR', 'LIB_DIR', 'SHARED_LIB_DIR']: |
| # Some gyp steps fail if these are empty(!). |
| generator_default_variables[dirname] = 'dir' |
| |
| for unused in ['RULE_INPUT_PATH', 'RULE_INPUT_ROOT', 'RULE_INPUT_NAME', |
| 'RULE_INPUT_DIRNAME', 'RULE_INPUT_EXT', |
| 'EXECUTABLE_PREFIX', 'EXECUTABLE_SUFFIX', |
| 'STATIC_LIB_PREFIX', 'STATIC_LIB_SUFFIX', |
| 'SHARED_LIB_PREFIX', 'SHARED_LIB_SUFFIX', |
| 'CONFIGURATION_NAME']: |
| generator_default_variables[unused] = '' |
| |
| # Include dirs will occasionaly use the SHARED_INTERMEDIATE_DIR variable as |
| # part of the path when dealing with generated headers. This value will be |
| # replaced dynamically for each configuration. |
| generator_default_variables['SHARED_INTERMEDIATE_DIR'] = \ |
| '$SHARED_INTERMEDIATES_DIR' |
| |
| |
| def CalculateVariables(default_variables, params): |
| generator_flags = params.get('generator_flags', {}) |
| for key, val in generator_flags.items(): |
| default_variables.setdefault(key, val) |
| default_variables.setdefault('OS', gyp.common.GetFlavor(params)) |
| |
| |
| def CalculateGeneratorInputInfo(params): |
| """Calculate the generator specific info that gets fed to input (called by |
| gyp).""" |
| generator_flags = params.get('generator_flags', {}) |
| if generator_flags.get('adjust_static_libraries', False): |
| global generator_wants_static_library_dependencies_adjusted |
| generator_wants_static_library_dependencies_adjusted = True |
| |
| |
| def GetAllIncludeDirectories(target_list, target_dicts, |
| shared_intermediates_dir, config_name): |
| """Calculate the set of include directories to be used. |
| |
| Returns: |
| A list including all the include_dir's specified for every target followed |
| by any include directories that were added as cflag compiler options. |
| """ |
| |
| gyp_includes_set = set() |
| compiler_includes_list = [] |
| |
| for target_name in target_list: |
| target = target_dicts[target_name] |
| if config_name in target['configurations']: |
| config = target['configurations'][config_name] |
| |
| # Look for any include dirs that were explicitly added via cflags. This |
| # may be done in gyp files to force certain includes to come at the end. |
| # TODO(jgreenwald): Change the gyp files to not abuse cflags for this, and |
| # remove this. |
| cflags = config['cflags'] |
| for cflag in cflags: |
| include_dir = '' |
| if cflag.startswith('-I'): |
| include_dir = cflag[2:] |
| if include_dir and not include_dir in compiler_includes_list: |
| compiler_includes_list.append(include_dir) |
| |
| # Find standard gyp include dirs. |
| if config.has_key('include_dirs'): |
| include_dirs = config['include_dirs'] |
| for include_dir in include_dirs: |
| include_dir = include_dir.replace('$SHARED_INTERMEDIATES_DIR', |
| shared_intermediates_dir) |
| if not os.path.isabs(include_dir): |
| base_dir = os.path.dirname(target_name) |
| |
| include_dir = base_dir + '/' + include_dir |
| include_dir = os.path.abspath(include_dir) |
| |
| if not include_dir in gyp_includes_set: |
| gyp_includes_set.add(include_dir) |
| |
| |
| # Generate a list that has all the include dirs. |
| all_includes_list = list(gyp_includes_set) |
| all_includes_list.sort() |
| for compiler_include in compiler_includes_list: |
| if not compiler_include in gyp_includes_set: |
| all_includes_list.append(compiler_include) |
| |
| # All done. |
| return all_includes_list |
| |
| |
| def GetCompilerPath(target_list, target_dicts, data): |
| """Determine a command that can be used to invoke the compiler. |
| |
| Returns: |
| If this is a gyp project that has explicit make settings, try to determine |
| the compiler from that. Otherwise, see if a compiler was specified via the |
| CC_target environment variable. |
| """ |
| |
| # First, see if the compiler is configured in make's settings. |
| build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0]) |
| make_global_settings_dict = data[build_file].get('make_global_settings', {}) |
| for key, value in make_global_settings_dict: |
| if key in ['CC', 'CXX']: |
| return value |
| |
| # Check to see if the compiler was specified as an environment variable. |
| for key in ['CC_target', 'CC', 'CXX']: |
| compiler = os.environ.get(key) |
| if compiler: |
| return compiler |
| |
| return 'gcc' |
| |
| |
| def GetAllDefines(target_list, target_dicts, data, config_name): |
| """Calculate the defines for a project. |
| |
| Returns: |
| A dict that includes explict defines declared in gyp files along with all of |
| the default defines that the compiler uses. |
| """ |
| |
| # Get defines declared in the gyp files. |
| all_defines = {} |
| for target_name in target_list: |
| target = target_dicts[target_name] |
| |
| if config_name in target['configurations']: |
| config = target['configurations'][config_name] |
| for define in config['defines']: |
| split_define = define.split('=', 1) |
| if len(split_define) == 1: |
| split_define.append('1') |
| if split_define[0].strip() in all_defines: |
| # Already defined |
| continue |
| |
| all_defines[split_define[0].strip()] = split_define[1].strip() |
| |
| # Get default compiler defines (if possible). |
| cc_target = GetCompilerPath(target_list, target_dicts, data) |
| if cc_target: |
| command = shlex.split(cc_target) |
| command.extend(['-E', '-dM', '-']) |
| cpp_proc = subprocess.Popen(args=command, cwd='.', |
| stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| cpp_output = cpp_proc.communicate()[0] |
| cpp_lines = cpp_output.split('\n') |
| for cpp_line in cpp_lines: |
| if not cpp_line.strip(): |
| continue |
| cpp_line_parts = cpp_line.split(' ', 2) |
| key = cpp_line_parts[1] |
| if len(cpp_line_parts) >= 3: |
| val = cpp_line_parts[2] |
| else: |
| val = '1' |
| all_defines[key] = val |
| |
| return all_defines |
| |
| |
| def WriteIncludePaths(out, eclipse_langs, include_dirs): |
| """Write the includes section of a CDT settings export file.""" |
| |
| out.write(' <section name="org.eclipse.cdt.internal.ui.wizards.' \ |
| 'settingswizards.IncludePaths">\n') |
| out.write(' <language name="holder for library settings"></language>\n') |
| for lang in eclipse_langs: |
| out.write(' <language name="%s">\n' % lang) |
| for include_dir in include_dirs: |
| out.write(' <includepath workspace_path="false">%s</includepath>\n' % |
| include_dir) |
| out.write(' </language>\n') |
| out.write(' </section>\n') |
| |
| |
| def WriteMacros(out, eclipse_langs, defines): |
| """Write the macros section of a CDT settings export file.""" |
| |
| out.write(' <section name="org.eclipse.cdt.internal.ui.wizards.' \ |
| 'settingswizards.Macros">\n') |
| out.write(' <language name="holder for library settings"></language>\n') |
| for lang in eclipse_langs: |
| out.write(' <language name="%s">\n' % lang) |
| for key in sorted(defines.iterkeys()): |
| out.write(' <macro><name>%s</name><value>%s</value></macro>\n' % |
| (escape(key), escape(defines[key]))) |
| out.write(' </language>\n') |
| out.write(' </section>\n') |
| |
| |
| def GenerateOutputForConfig(target_list, target_dicts, data, params, |
| config_name): |
| options = params['options'] |
| generator_flags = params.get('generator_flags', {}) |
| |
| # build_dir: relative path from source root to our output files. |
| # e.g. "out/Debug" |
| build_dir = os.path.join(generator_flags.get('output_dir', 'out'), |
| config_name) |
| |
| toplevel_build = os.path.join(options.toplevel_dir, build_dir) |
| shared_intermediate_dir = os.path.join(toplevel_build, 'obj', 'gen') |
| |
| if not os.path.exists(toplevel_build): |
| os.makedirs(toplevel_build) |
| out = open(os.path.join(toplevel_build, 'eclipse-cdt-settings.xml'), 'w') |
| |
| out.write('<?xml version="1.0" encoding="UTF-8"?>\n') |
| out.write('<cdtprojectproperties>\n') |
| |
| eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File', |
| 'GNU C++', 'GNU C', 'Assembly'] |
| include_dirs = GetAllIncludeDirectories(target_list, target_dicts, |
| shared_intermediate_dir, config_name) |
| WriteIncludePaths(out, eclipse_langs, include_dirs) |
| defines = GetAllDefines(target_list, target_dicts, data, config_name) |
| WriteMacros(out, eclipse_langs, defines) |
| |
| out.write('</cdtprojectproperties>\n') |
| out.close() |
| |
| |
| def GenerateOutput(target_list, target_dicts, data, params): |
| """Generate an XML settings file that can be imported into a CDT project.""" |
| |
| if params['options'].generator_output: |
| raise NotImplementedError, "--generator_output not implemented for eclipse" |
| |
| user_config = params.get('generator_flags', {}).get('config', None) |
| if user_config: |
| GenerateOutputForConfig(target_list, target_dicts, data, params, |
| user_config) |
| else: |
| config_names = target_dicts[target_list[0]]['configurations'].keys() |
| for config_name in config_names: |
| GenerateOutputForConfig(target_list, target_dicts, data, params, |
| config_name) |
| |