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 40 import os 41 import subprocess 42 43 44 # Flags from YCM's default config. 45 flags = [ 46 '-DUSE_CLANG_COMPLETER', 47 '-std=c++11', 48 '-x', 49 'c++', 50 ] 51 52 53 def PathExists(*args): 54 return os.path.exists(os.path.join(*args)) 55 56 57 def 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) 80 def GetNinjaOutputDirectory(chrome_root): 81 """Returns <chrome_root>/<output_dir>/(Release|Debug). 82 83 The configuration chosen is the one most recently generated/built. Detects 84 a custom output_dir specified by GYP_GENERATOR_FLAGS.""" 85 86 output_dir = 'out' 87 generator_flags = os.getenv('GYP_GENERATOR_FLAGS', '').split(' ') 88 for flag in generator_flags: 89 name_value = flag.split('=', 1) 90 if len(name_value) == 2 and name_value[0] == 'output_dir': 91 output_dir = name_value[1] 92 93 root = os.path.join(chrome_root, output_dir) 94 debug_path = os.path.join(root, 'Debug') 95 release_path = os.path.join(root, 'Release') 96 97 def is_release_15s_newer(test_path): 98 try: 99 debug_mtime = os.path.getmtime(os.path.join(debug_path, test_path)) 100 except os.error: 101 debug_mtime = 0 102 try: 103 rel_mtime = os.path.getmtime(os.path.join(release_path, test_path)) 104 except os.error: 105 rel_mtime = 0 106 return rel_mtime - debug_mtime >= 15 107 108 if is_release_15s_newer('build.ninja') or is_release_15s_newer('protoc'): 109 return release_path 110 return debug_path 111 112 113 def GetClangCommandFromNinjaForFilename(chrome_root, filename): 114 """Returns the command line to build |filename|. 115 116 Asks ninja how it would build the source file. If the specified file is a 117 header, tries to find its companion source file first. 118 119 Args: 120 chrome_root: (String) Path to src/. 121 filename: (String) Path to source file being edited. 122 123 Returns: 124 (List of Strings) Command line arguments for clang. 125 """ 126 if not chrome_root: 127 return [] 128 129 # Generally, everyone benefits from including Chromium's src/, because all of 130 # Chromium's includes are relative to that. 131 chrome_flags = ['-I' + os.path.join(chrome_root)] 132 133 # Header files can't be built. Instead, try to match a header file to its 134 # corresponding source file. 135 if filename.endswith('.h'): 136 alternates = ['.cc', '.cpp'] 137 for alt_extension in alternates: 138 alt_name = filename[:-2] + alt_extension 139 if os.path.exists(alt_name): 140 filename = alt_name 141 break 142 else: 143 # If this is a standalone .h file with no source, the best we can do is 144 # try to use the default flags. 145 return chrome_flags 146 147 # Ninja needs the path to the source file from the output build directory. 148 # Cut off the common part and /. 149 subdir_filename = filename[len(chrome_root)+1:] 150 rel_filename = os.path.join('..', '..', subdir_filename) 151 152 out_dir = GetNinjaOutputDirectory(chrome_root) 153 154 # Ask ninja how it would build our source file. 155 p = subprocess.Popen(['ninja', '-v', '-C', out_dir, '-t', 156 'commands', rel_filename + '^'], 157 stdout=subprocess.PIPE) 158 stdout, stderr = p.communicate() 159 if p.returncode: 160 return chrome_flags 161 162 # Ninja might execute several commands to build something. We want the last 163 # clang command. 164 clang_line = None 165 for line in reversed(stdout.split('\n')): 166 if 'clang' in line: 167 clang_line = line 168 break 169 else: 170 return chrome_flags 171 172 # Parse flags that are important for YCM's purposes. 173 for flag in clang_line.split(' '): 174 if flag.startswith('-I'): 175 # Relative paths need to be resolved, because they're relative to the 176 # output dir, not the source. 177 if flag[2] == '/': 178 chrome_flags.append(flag) 179 else: 180 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:])) 181 chrome_flags.append('-I' + abs_path) 182 elif flag.startswith('-std'): 183 chrome_flags.append(flag) 184 elif flag.startswith('-') and flag[1] in 'DWFfmO': 185 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard': 186 # These flags causes libclang (3.3) to crash. Remove it until things 187 # are fixed. 188 continue 189 chrome_flags.append(flag) 190 191 return chrome_flags 192 193 194 def FlagsForFile(filename): 195 """This is the main entry point for YCM. Its interface is fixed. 196 197 Args: 198 filename: (String) Path to source file being edited. 199 200 Returns: 201 (Dictionary) 202 'flags': (List of Strings) Command line flags. 203 'do_cache': (Boolean) True if the result should be cached. 204 """ 205 chrome_root = FindChromeSrcFromFilename(filename) 206 chrome_flags = GetClangCommandFromNinjaForFilename(chrome_root, 207 filename) 208 final_flags = flags + chrome_flags 209 210 return { 211 'flags': final_flags, 212 'do_cache': True 213 } 214