/ src / crc32c / .ycm_extra_conf.py
.ycm_extra_conf.py
  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}