blob: 65a99a2ef427d0ffe303c9ae09425b27af965cc0 [file] [log] [blame]
# 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 {}