blob: 2113776386f562f8856ac6169b9f89ec7933871a [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: is a direct copy from //build/gn/
# If there is an issue with the paths not being valid, just pull a new copy.
import sys
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:
if os.path.exists(os.path.join(zircon_dir, 'compile_commands.json')):
zircon_database = ycm_core.CompilationDatabase(zircon_dir)
fuchsia_build = subprocess.check_output(
[os.path.join(fuchsia_paths.FUCHSIA_ROOT, 'scripts/fx'),
fuchsia_clang = os.path.realpath(fuchsia_paths.CLANG_PATH)
fuchsia_sysroot = os.path.join(fuchsia_paths.PREBUILT_PATH,
# 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 is significantly faster than running `gn args` so we do
# that.
target_cpu = None
args = open(os.path.join(fuchsia_build, '')).read()
match ='target_cpu\s*=\s*"([^"]+)"', args)
if match:
target_cpu = match.groups()[0]
common_flags = [
'-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,
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.
filename: (String) Path to source file being edited.
(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', '']
for alt_extension in alternates:
alt_name = filename[:-2] + alt_extension
if os.path.exists(alt_name):
filename = alt_name
# 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
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] == '/':
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
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.
**kwargs: (Dictionary) Contains at least 'language' and 'filename'.
'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(
if zircon_compilation_info.compiler_flags_:
return {
'flags': zircon_compilation_info.compiler_flags_,
'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 {}