| # Copyright 2017 The CRC32C Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """YouCompleteMe configuration that interprets a .clang_complete file. |
| |
| This module implementes the YouCompleteMe configuration API documented at: |
| https://github.com/Valloric/ycmd#ycm_extra_confpy-specification |
| |
| The implementation loads and processes a .clang_complete file, documented at: |
| https://github.com/Rip-Rip/clang_complete/blob/master/README.md |
| """ |
| |
| import os |
| |
| # Flags added to the list in .clang_complete. |
| BASE_FLAGS = [ |
| '-Werror', # Unlike clang_complete, YCM can also be used as a linter. |
| '-DUSE_CLANG_COMPLETER', # YCM needs this. |
| '-xc++', # YCM needs this to avoid compiling headers as C code. |
| ] |
| |
| # Clang flags that take in paths. |
| # See https://clang.llvm.org/docs/ClangCommandLineReference.html |
| PATH_FLAGS = [ |
| '-isystem', |
| '-I', |
| '-iquote', |
| '--sysroot=' |
| ] |
| |
| |
| def DirectoryOfThisScript(): |
| """Returns the absolute path to the directory containing this script.""" |
| return os.path.dirname(os.path.abspath(__file__)) |
| |
| |
| def MakeRelativePathsInFlagsAbsolute(flags, build_root): |
| """Expands relative paths in a list of Clang command-line flags. |
| |
| Args: |
| flags: The list of flags passed to Clang. |
| build_root: The current directory when running the Clang compiler. Should be |
| an absolute path. |
| |
| Returns: |
| A list of flags with relative paths replaced by absolute paths. |
| """ |
| new_flags = [] |
| make_next_absolute = False |
| for flag in flags: |
| new_flag = flag |
| |
| if make_next_absolute: |
| make_next_absolute = False |
| if not flag.startswith('/'): |
| new_flag = os.path.join(build_root, flag) |
| |
| for path_flag in PATH_FLAGS: |
| if flag == path_flag: |
| make_next_absolute = True |
| break |
| |
| if flag.startswith(path_flag): |
| path = flag[len(path_flag):] |
| new_flag = path_flag + os.path.join(build_root, path) |
| break |
| |
| if new_flag: |
| new_flags.append(new_flag) |
| return new_flags |
| |
| |
| def FindNearest(target, path, build_root): |
| """Looks for a file with a specific name closest to a project path. |
| |
| This is similar to the logic used by a version-control system (like git) to |
| find its configuration directory (.git) based on the current directory when a |
| command is invoked. |
| |
| Args: |
| target: The file name to search for. |
| path: The directory where the search starts. The search will explore the |
| given directory's ascendants using the parent relationship. Should be an |
| absolute path. |
| build_root: A directory that acts as a fence for the search. If the search |
| reaches this directory, it will not advance to its parent. Should be an |
| absolute path. |
| |
| Returns: |
| The path to a file with the desired name. None if the search failed. |
| """ |
| candidate = os.path.join(path, target) |
| if os.path.isfile(candidate): |
| return candidate |
| |
| if path == build_root: |
| return None |
| |
| parent = os.path.dirname(path) |
| if parent == path: |
| return None |
| |
| return FindNearest(target, parent, build_root) |
| |
| |
| def FlagsForClangComplete(file_path, build_root): |
| """Reads the .clang_complete flags for a source file. |
| |
| Args: |
| file_path: The path to the source file. Should be inside the project. Used |
| to locate the relevant .clang_complete file. |
| build_root: The current directory when running the Clang compiler for this |
| file. Should be an absolute path. |
| |
| Returns: |
| A list of strings, where each element is a Clang command-line flag. |
| """ |
| clang_complete_path = FindNearest('.clang_complete', file_path, build_root) |
| if clang_complete_path is None: |
| return None |
| clang_complete_flags = open(clang_complete_path, 'r').read().splitlines() |
| return clang_complete_flags |
| |
| |
| def FlagsForFile(filename, **kwargs): |
| """Implements the YouCompleteMe API.""" |
| |
| # kwargs can be used to pass 'client_data' to the YCM configuration. This |
| # configuration script does not need any extra information, so |
| # pylint: disable=unused-argument |
| |
| build_root = DirectoryOfThisScript() |
| file_path = os.path.realpath(filename) |
| |
| flags = BASE_FLAGS |
| clang_flags = FlagsForClangComplete(file_path, build_root) |
| if clang_flags: |
| flags += clang_flags |
| |
| final_flags = MakeRelativePathsInFlagsAbsolute(flags, build_root) |
| |
| return {'flags': final_flags} |