| # 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} |