chromium/tools/android/eclipse/generate_cdt_clang_settings.py

#!/usr/bin/env python
#
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Generates XML file which can be imported into an Eclipse CDT project.

The XML file contains the include directories and defines that all applications
which use the clang compiler inherit. Should be used in conjunction with the
XML file generated by "gn gen out/Release --ide=eclipse"
"""

from __future__ import print_function

from xml.sax.saxutils import escape
import os
import subprocess
import sys

def GetClangIncludeDirectories(compiler_path):
  """Gets the system include directories as determined by the clang compiler.

  Returns:
    The list of include directories.
  """

  includes_set = set()

  command = [compiler_path, '-E', '-xc++', '-v', '-']
  proc = subprocess.Popen(args=command, stdin=subprocess.PIPE,
                          stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  output = proc.communicate()[1]
  # Extract the list of include dirs from the output, which has this format:
  #   ...
  #   #include "..." search starts here:
  #   #include <...> search starts here:
  #    /usr/include/c++/4.6
  #    /usr/local/include
  #   End of search list.
  #   ...
  in_includes_list = False
  for line in output.splitlines():
    if line.startswith('#include'):
      in_includes_list = True
      continue
    if line.startswith('End of search list.'):
      break
    if in_includes_list:
      includes_set.add(line.strip())

  return sorted(includes_set)


def GetClangDefines(compiler_path):
  """Gets the system defines as determined by the clang compiler.

  Returns:
    The dict of defines.
  """

  all_defines = {}
  command = [compiler_path, '-E', '-dM', '-']
  proc = subprocess.Popen(args=command, stdin=subprocess.PIPE,
                          stdout=subprocess.PIPE)

  # Extract the list of defines from the output, which has this format:
  # #define __SIZEOF_INT__ 4
  # ...
  # #define unix 1
  output = proc.communicate()[0]
  for line in output.splitlines():
    if not line.strip():
      continue
    line_parts = line.split(' ', 2)
    key = line_parts[1]
    if len(line_parts) >= 3:
      val = 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 main(argv):
  if len(argv) != 2:
    print("Usage: generate_cdt_clang_settings.py destination_file")
    return

  compiler_path = os.path.abspath(
      'third_party/llvm-build/Release+Asserts/bin/clang')
  if not os.path.exists(compiler_path):
    print('Please run this script from the Chromium src/ directory.')
    return

  include_dirs = GetClangIncludeDirectories(compiler_path)
  if not include_dirs:
    print('ERROR: Could not extract include dirs from %s.' % compiler_path)
    return

  defines = GetClangDefines(compiler_path)
  if not defines:
    print('ERROR: Could not extract defines from %s.' % compiler_path)

  destination_file = os.path.abspath(argv[1])
  destination_dir = os.path.dirname(destination_file)
  if not os.path.exists(destination_dir):
    os.makedirs(destination_dir)

  with open(destination_file, 'w') as out:
    eclipse_langs = ['C++ Source File', 'C Source File', 'Assembly Source File',
                     'GNU C++', 'GNU C', 'Assembly']

    out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    out.write('<cdtprojectproperties>\n')
    WriteIncludePaths(out, eclipse_langs, include_dirs)
    WriteMacros(out, eclipse_langs, defines)
    out.write('</cdtprojectproperties>\n')

if __name__ == '__main__':
  sys.exit(main(sys.argv))