.ycm_extra_conf.py raw

   1  # Copyright 2017 The CRC32C Authors. 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  """YouCompleteMe configuration that interprets a .clang_complete file.
   5  
   6  This module implementes the YouCompleteMe configuration API documented at:
   7  https://github.com/ycm-core/ycmd#ycm_extra_confpy-specification
   8  
   9  The implementation loads and processes a .clang_complete file, documented at:
  10  https://github.com/xavierd/clang_complete/blob/master/README.md
  11  """
  12  
  13  import os
  14  
  15  # Flags added to the list in .clang_complete.
  16  BASE_FLAGS = [
  17      '-Werror',  # Unlike clang_complete, YCM can also be used as a linter.
  18      '-DUSE_CLANG_COMPLETER',  # YCM needs this.
  19      '-xc++',  # YCM needs this to avoid compiling headers as C code.
  20  ]
  21  
  22  # Clang flags that take in paths.
  23  # See https://clang.llvm.org/docs/ClangCommandLineReference.html
  24  PATH_FLAGS = [
  25      '-isystem',
  26      '-I',
  27      '-iquote',
  28      '--sysroot='
  29  ]
  30  
  31  
  32  def DirectoryOfThisScript():
  33    """Returns the absolute path to the directory containing this script."""
  34    return os.path.dirname(os.path.abspath(__file__))
  35  
  36  
  37  def MakeRelativePathsInFlagsAbsolute(flags, build_root):
  38    """Expands relative paths in a list of Clang command-line flags.
  39  
  40    Args:
  41      flags: The list of flags passed to Clang.
  42      build_root: The current directory when running the Clang compiler. Should be
  43          an absolute path.
  44  
  45    Returns:
  46      A list of flags with relative paths replaced by absolute paths.
  47    """
  48    new_flags = []
  49    make_next_absolute = False
  50    for flag in flags:
  51      new_flag = flag
  52  
  53      if make_next_absolute:
  54        make_next_absolute = False
  55        if not flag.startswith('/'):
  56          new_flag = os.path.join(build_root, flag)
  57  
  58      for path_flag in PATH_FLAGS:
  59        if flag == path_flag:
  60          make_next_absolute = True
  61          break
  62  
  63        if flag.startswith(path_flag):
  64          path = flag[len(path_flag):]
  65          new_flag = path_flag + os.path.join(build_root, path)
  66          break
  67  
  68      if new_flag:
  69        new_flags.append(new_flag)
  70    return new_flags
  71  
  72  
  73  def FindNearest(target, path, build_root):
  74    """Looks for a file with a specific name closest to a project path.
  75  
  76    This is similar to the logic used by a version-control system (like git) to
  77    find its configuration directory (.git) based on the current directory when a
  78    command is invoked.
  79  
  80    Args:
  81      target: The file name to search for.
  82      path: The directory where the search starts. The search will explore the
  83          given directory's ascendants using the parent relationship. Should be an
  84          absolute path.
  85      build_root: A directory that acts as a fence for the search. If the search
  86          reaches this directory, it will not advance to its parent. Should be an
  87          absolute path.
  88  
  89    Returns:
  90      The path to a file with the desired name. None if the search failed.
  91    """
  92    candidate = os.path.join(path, target)
  93    if os.path.isfile(candidate):
  94      return candidate
  95  
  96    if path == build_root:
  97      return None
  98  
  99    parent = os.path.dirname(path)
 100    if parent == path:
 101      return None
 102  
 103    return FindNearest(target, parent, build_root)
 104  
 105  
 106  def FlagsForClangComplete(file_path, build_root):
 107    """Reads the .clang_complete flags for a source file.
 108  
 109    Args:
 110      file_path: The path to the source file. Should be inside the project. Used
 111        to locate the relevant .clang_complete file.
 112      build_root: The current directory when running the Clang compiler for this
 113          file. Should be an absolute path.
 114  
 115    Returns:
 116      A list of strings, where each element is a Clang command-line flag.
 117    """
 118    clang_complete_path = FindNearest('.clang_complete', file_path, build_root)
 119    if clang_complete_path is None:
 120      return None
 121    clang_complete_flags = open(clang_complete_path, 'r').read().splitlines()
 122    return clang_complete_flags
 123  
 124  
 125  def FlagsForFile(filename, **kwargs):
 126    """Implements the YouCompleteMe API."""
 127  
 128    # kwargs can be used to pass 'client_data' to the YCM configuration. This
 129    # configuration script does not need any extra information, so
 130    # pylint: disable=unused-argument
 131  
 132    build_root = DirectoryOfThisScript()
 133    file_path = os.path.realpath(filename)
 134  
 135    flags = BASE_FLAGS
 136    clang_flags = FlagsForClangComplete(file_path, build_root)
 137    if clang_flags:
 138      flags += clang_flags
 139  
 140    final_flags = MakeRelativePathsInFlagsAbsolute(flags, build_root)
 141  
 142    return {'flags': final_flags}
 143