blob: 2eb3c388840a22d95fb82c2c597eca7f0c47b269 [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 FlagsForFile(filename):
"""This is the main entry point for YCM. Its interface is fixed.
Args:
filename: (String) Path to source file being edited.
Returns:
(Dictionary)
'flags': (List of Strings) Command line flags.
'do_cache': (Boolean) True if the result should be cached.
"""
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}