1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5# Autocompletion config for YouCompleteMe in Chromium. 6# 7# USAGE: 8# 9# 1. Install YCM [https://github.com/Valloric/YouCompleteMe] 10# (Googlers should check out [go/ycm]) 11# 12# 2. Point to this config file in your .vimrc: 13# let g:ycm_global_ycm_extra_conf = 14# '<chrome_depot>/src/tools/vim/chromium.ycm_extra_conf.py' 15# 16# 3. Profit 17# 18# 19# Usage notes: 20# 21# * You must use ninja & clang to build Chromium. 22# 23# * You must have run gyp_chromium and built Chromium recently. 24# 25# 26# Hacking notes: 27# 28# * The purpose of this script is to construct an accurate enough command line 29# for YCM to pass to clang so it can build and extract the symbols. 30# 31# * Right now, we only pull the -I and -D flags. That seems to be sufficient 32# for everything I've used it for. 33# 34# * That whole ninja & clang thing? We could support other configs if someone 35# were willing to write the correct commands and a parser. 36# 37# * This has only been tested on gPrecise. 38 39 40import os 41import subprocess 42 43 44# Flags from YCM's default config. 45flags = [ 46'-DUSE_CLANG_COMPLETER', 47'-std=c++11', 48'-x', 49'c++', 50] 51 52 53def PathExists(*args): 54 return os.path.exists(os.path.join(*args)) 55 56 57def FindChromeSrcFromFilename(filename): 58 """Searches for the root of the Chromium checkout. 59 60 Simply checks parent directories until it finds .gclient and src/. 61 62 Args: 63 filename: (String) Path to source file being edited. 64 65 Returns: 66 (String) Path of 'src/', or None if unable to find. 67 """ 68 curdir = os.path.normpath(os.path.dirname(filename)) 69 while not (PathExists(curdir, 'src') and PathExists(curdir, 'src', 'DEPS') 70 and (PathExists(curdir, '.gclient') 71 or PathExists(curdir, 'src', '.git'))): 72 nextdir = os.path.normpath(os.path.join(curdir, '..')) 73 if nextdir == curdir: 74 return None 75 curdir = nextdir 76 return os.path.join(curdir, 'src') 77 78 79# Largely copied from ninja-build.vim (guess_configuration) 80def GetNinjaOutputDirectory(chrome_root): 81 """Returns either <chrome_root>/out/Release or <chrome_root>/out/Debug. 82 83 The configuration chosen is the one most recently generated/built.""" 84 root = os.path.join(chrome_root, 'out') 85 debug_path = os.path.join(root, 'Debug') 86 release_path = os.path.join(root, 'Release') 87 88 def is_release_15s_newer(test_path): 89 try: 90 debug_mtime = os.path.getmtime(os.path.join(debug_path, test_path)) 91 except os.error: 92 debug_mtime = 0 93 try: 94 rel_mtime = os.path.getmtime(os.path.join(release_path, test_path)) 95 except os.error: 96 rel_mtime = 0 97 return rel_mtime - debug_mtime >= 15 98 99 if is_release_15s_newer('build.ninja') or is_release_15s_newer('protoc'): 100 return release_path 101 return debug_path 102 103 104def GetClangCommandFromNinjaForFilename(chrome_root, filename): 105 """Returns the command line to build |filename|. 106 107 Asks ninja how it would build the source file. If the specified file is a 108 header, tries to find its companion source file first. 109 110 Args: 111 chrome_root: (String) Path to src/. 112 filename: (String) Path to source file being edited. 113 114 Returns: 115 (List of Strings) Command line arguments for clang. 116 """ 117 if not chrome_root: 118 return [] 119 120 # Generally, everyone benefits from including Chromium's src/, because all of 121 # Chromium's includes are relative to that. 122 chrome_flags = ['-I' + os.path.join(chrome_root)] 123 124 # Header files can't be built. Instead, try to match a header file to its 125 # corresponding source file. 126 if filename.endswith('.h'): 127 alternates = ['.cc', '.cpp'] 128 for alt_extension in alternates: 129 alt_name = filename[:-2] + alt_extension 130 if os.path.exists(alt_name): 131 filename = alt_name 132 break 133 else: 134 # If this is a standalone .h file with no source, the best we can do is 135 # try to use the default flags. 136 return chrome_flags 137 138 # Ninja needs the path to the source file from the output build directory. 139 # Cut off the common part and /. 140 subdir_filename = filename[len(chrome_root)+1:] 141 rel_filename = os.path.join('..', '..', subdir_filename) 142 143 out_dir = GetNinjaOutputDirectory(chrome_root) 144 145 # Ask ninja how it would build our source file. 146 p = subprocess.Popen(['ninja', '-v', '-C', out_dir, '-t', 147 'commands', rel_filename + '^'], 148 stdout=subprocess.PIPE) 149 stdout, stderr = p.communicate() 150 if p.returncode: 151 return chrome_flags 152 153 # Ninja might execute several commands to build something. We want the last 154 # clang command. 155 clang_line = None 156 for line in reversed(stdout.split('\n')): 157 if 'clang' in line: 158 clang_line = line 159 break 160 else: 161 return chrome_flags 162 163 # Parse out the -I and -D flags. These seem to be the only ones that are 164 # important for YCM's purposes. 165 for flag in clang_line.split(' '): 166 if flag.startswith('-I'): 167 # Relative paths need to be resolved, because they're relative to the 168 # output dir, not the source. 169 if flag[2] == '/': 170 chrome_flags.append(flag) 171 else: 172 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:])) 173 chrome_flags.append('-I' + abs_path) 174 elif flag.startswith('-') and flag[1] in 'DWFfmO': 175 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard': 176 # These flags causes libclang (3.3) to crash. Remove it until things 177 # are fixed. 178 continue 179 chrome_flags.append(flag) 180 181 return chrome_flags 182 183 184def FlagsForFile(filename): 185 """This is the main entry point for YCM. Its interface is fixed. 186 187 Args: 188 filename: (String) Path to source file being edited. 189 190 Returns: 191 (Dictionary) 192 'flags': (List of Strings) Command line flags. 193 'do_cache': (Boolean) True if the result should be cached. 194 """ 195 chrome_root = FindChromeSrcFromFilename(filename) 196 chrome_flags = GetClangCommandFromNinjaForFilename(chrome_root, 197 filename) 198 final_flags = flags + chrome_flags 199 200 return { 201 'flags': final_flags, 202 'do_cache': True 203 } 204