| # vim: set expandtab:ts=2:sw=2 |
| # Copyright 2017 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Autocompletion config for YouCompleteMe in Fuchsia |
| |
| import glob |
| import os |
| import re |
| import stat |
| import subprocess |
| import ycm_core |
| |
| # NOTE: paths.py is a direct copy from //build/gn/paths.py |
| # If there is an issue with the paths not being valid, just pull a new copy. |
| import sys |
| |
| sys.path.append(os.path.dirname(os.path.realpath(__file__))) |
| import paths as fuchsia_paths |
| |
| fuchsia_root = os.path.realpath(fuchsia_paths.FUCHSIA_ROOT) |
| zircon_database = None |
| zircon_dir = os.path.join(fuchsia_root, "zircon") |
| # This doc explains how to generate compile_commands.json for Zircon: |
| # https://fuchsia.dev/fuchsia-src/development/editors/youcompleteme |
| if os.path.exists(os.path.join(zircon_dir, "compile_commands.json")): |
| zircon_database = ycm_core.CompilationDatabase(zircon_dir) |
| |
| os.chdir(fuchsia_root) |
| fuchsia_build = ( |
| subprocess.check_output( |
| [ |
| os.path.join(fuchsia_paths.FUCHSIA_ROOT, "scripts/fx"), |
| "get-build-dir", |
| ] |
| ) |
| .strip() |
| .decode("utf-8") |
| ) |
| |
| fuchsia_clang = os.path.realpath(fuchsia_paths.CLANG_PATH) |
| fuchsia_sysroot = os.path.join( |
| fuchsia_paths.PREBUILT_PATH, |
| "third_party", |
| "sysroot", |
| fuchsia_paths.get_os(), |
| ) |
| |
| # Get the clang include paths. |
| fuchsia_cpp_v1_includes = fuchsia_paths.recursive_search( |
| fuchsia_clang, "include/c++/v1" |
| ) |
| fuchsia_cpp_includes = glob.glob("%s/lib/clang/*/include" % fuchsia_clang)[0] |
| |
| ninja_path = os.path.realpath(fuchsia_paths.NINJA_PATH) |
| |
| # Get the name of the zircon project from GN args. |
| # Reading the args.gn is significantly faster than running `gn args` so we do |
| # that. |
| target_cpu = None |
| args = open(os.path.join(fuchsia_build, "args.gn")).read() |
| match = re.search(r'target_cpu\s*=\s*"([^"]+)"', args) |
| if match: |
| target_cpu = match.groups()[0] |
| |
| common_flags = [ |
| "-std=c++14", |
| "-xc++", |
| "-I", |
| fuchsia_root, |
| "-I", |
| os.path.join(fuchsia_build, "gen"), |
| "-isystem", |
| os.path.join(fuchsia_sysroot, "usr", "include"), |
| "-isystem", |
| fuchsia_cpp_v1_includes, |
| "-isystem", |
| fuchsia_cpp_includes, |
| ] |
| |
| arch_flags = [] |
| |
| # Add the sysroot include if we found the zircon project |
| if target_cpu: |
| arch_flags = [ |
| "-I" |
| + os.path.join( |
| fuchsia_build, |
| "sdk", |
| "exported", |
| "zircon_sysroot", |
| "arch", |
| target_cpu, |
| "sysroot", |
| "include", |
| ) |
| ] |
| |
| |
| def GetClangCommandFromNinjaForFilename(filename): |
| """Returns the command line to build |filename|. |
| |
| Asks ninja how it would build the source file. If the specified file is a |
| header, tries to find its companion source file first. |
| |
| Args: |
| filename: (String) Path to source file being edited. |
| |
| Returns: |
| (List of Strings) Command line arguments for clang. |
| """ |
| |
| # Header files can't be built. Instead, try to match a header file to its |
| # corresponding source file. |
| if filename.endswith(".h"): |
| alternates = [".cc", ".cpp", "_unittest.cc"] |
| for alt_extension in alternates: |
| alt_name = filename[:-2] + alt_extension |
| if os.path.exists(alt_name): |
| filename = alt_name |
| break |
| else: |
| # If this is a standalone .h file with no source, the best we can do is |
| # try to use the default flags. |
| return common_flags |
| |
| # Ninja needs the path to the source file from the output build directory. |
| # Cut off the common part and /. Also ensure that paths are real and don't |
| # contain symlinks that throw the len() calculation off. |
| filename = os.path.realpath(filename) |
| subdir_filename = filename[len(fuchsia_root) + 1 :] |
| rel_filename = os.path.join("..", "..", subdir_filename) |
| |
| # Ask ninja how it would build our source file. |
| ninja_command = [ |
| ninja_path, |
| "-v", |
| "-C", |
| fuchsia_build, |
| "-t", |
| "commands", |
| rel_filename + "^", |
| ] |
| p = subprocess.Popen(ninja_command, stdout=subprocess.PIPE) |
| stdout, stderr = p.communicate() |
| if p.returncode: |
| return common_flags |
| stdout = stdout.decode("utf-8") |
| |
| # Ninja might execute several commands to build something. We want the last |
| # clang command. |
| clang_line = None |
| for line in reversed(stdout.split("\n")): |
| if "clang" in line: |
| clang_line = line |
| break |
| else: |
| return common_flags |
| |
| # By default we start with the common to every file flags |
| fuchsia_flags = [] |
| |
| # Parse out the -I and -D flags. These seem to be the only ones that are |
| # important for YCM's purposes. |
| for flag in clang_line.split(" "): |
| if flag.startswith("-I"): |
| # Relative paths need to be resolved, because they're relative to the |
| # output dir, not the source. |
| if flag[2] == "/": |
| fuchsia_flags.append(flag) |
| else: |
| abs_path = os.path.normpath( |
| os.path.join(fuchsia_build, flag[2:]) |
| ) |
| fuchsia_flags.append("-I" + abs_path) |
| elif ( |
| (flag.startswith("-") and flag[1] in "DWFfmO") |
| or flag.startswith("-std=") |
| or flag.startswith("--target=") |
| or flag.startswith("--sysroot=") |
| ): |
| fuchsia_flags.append(flag) |
| else: |
| print("Ignoring flag: %s" % flag) |
| |
| return fuchsia_flags + common_flags |
| |
| |
| def Settings(**kwargs): |
| """This is the main entry point for YCM. Its interface is fixed. |
| |
| https://github.com/ycm-core/YouCompleteMe#option-2-provide-the-flags-manually |
| |
| Args: |
| **kwargs: (Dictionary) Contains at least 'language' and 'filename'. |
| |
| Returns: |
| (Dictionary) |
| 'flags': (List of Strings) Command line flags. |
| 'do_cache': (Boolean) True if the result should be cached. |
| 'ls': (Dictionary) Language server configs. |
| """ |
| if kwargs["language"] == "rust": |
| return { |
| "ls": { |
| "checkOnSave": {"enable": False}, |
| "diagnostics": {"disabled": ["unresolved-proc-macro"]}, |
| } |
| } |
| |
| if kwargs["language"] == "cfamily": |
| filename = kwargs["filename"] |
| if zircon_database and ("zircon/" in filename): |
| zircon_compilation_info = zircon_database.GetCompilationInfoForFile( |
| filename |
| ) |
| if zircon_compilation_info.compiler_flags_: |
| return { |
| "flags": zircon_compilation_info.compiler_flags_, |
| "include_paths_relative_to_dir": zircon_compilation_info.compiler_working_dir_, |
| "do_cache": True, |
| } |
| file_flags = GetClangCommandFromNinjaForFilename(filename) |
| # We add the arch specific flags |
| final_flags = file_flags + arch_flags |
| return {"flags": final_flags, "do_cache": True} |
| |
| return {} |