blob: 0db23070032748e7a09c24879f04cca83c70867a [file] [log] [blame]
[email protected]a08a4282013-02-05 23:45:521# 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#
asanka385a4faa2015-06-03 18:40:3712# 2. Create a symbolic link to this file called .ycm_extra_conf.py in the
13# directory above your Chromium checkout (i.e. next to your .gclient file).
[email protected]a08a4282013-02-05 23:45:5214#
asanka385a4faa2015-06-03 18:40:3715# cd src
16# ln -rs tools/vim/chromium.ycm_extra_conf.py ../.ycm_extra_conf.py
17#
18# 3. (optional) Whitelist the .ycm_extra_conf.py from step #2 by adding the
19# following to your .vimrc:
20#
21# let g:ycm_extra_conf_globlist=['<path to .ycm_extra_conf.py>']
22#
23# You can also add other .ycm_extra_conf.py files you want to use to this
24# list to prevent excessive prompting each time you visit a directory
25# covered by a config file.
26#
27# 4. Profit
[email protected]a08a4282013-02-05 23:45:5228#
29#
30# Usage notes:
31#
32# * You must use ninja & clang to build Chromium.
33#
34# * You must have run gyp_chromium and built Chromium recently.
35#
36#
37# Hacking notes:
38#
39# * The purpose of this script is to construct an accurate enough command line
40# for YCM to pass to clang so it can build and extract the symbols.
41#
42# * Right now, we only pull the -I and -D flags. That seems to be sufficient
43# for everything I've used it for.
44#
45# * That whole ninja & clang thing? We could support other configs if someone
46# were willing to write the correct commands and a parser.
47#
48# * This has only been tested on gPrecise.
49
50
51import os
dzhioev37accc22014-08-29 23:08:2152import os.path
johnme8d6edba2015-01-21 14:26:2853import re
asanka01c39d082015-05-18 20:47:1154import shlex
[email protected]a08a4282013-02-05 23:45:5255import subprocess
rousland026bc92014-10-29 17:54:0156import sys
[email protected]a08a4282013-02-05 23:45:5257
58# Flags from YCM's default config.
asanka01c39d082015-05-18 20:47:1159_default_flags = [
60 '-DUSE_CLANG_COMPLETER',
61 '-std=c++11',
62 '-x',
63 'c++',
[email protected]a08a4282013-02-05 23:45:5264]
65
asanka3346ab662015-06-03 00:27:1766
[email protected]1cab75682013-04-24 19:50:4967def PathExists(*args):
68 return os.path.exists(os.path.join(*args))
69
70
[email protected]a08a4282013-02-05 23:45:5271def FindChromeSrcFromFilename(filename):
72 """Searches for the root of the Chromium checkout.
73
74 Simply checks parent directories until it finds .gclient and src/.
75
76 Args:
77 filename: (String) Path to source file being edited.
78
79 Returns:
80 (String) Path of 'src/', or None if unable to find.
81 """
82 curdir = os.path.normpath(os.path.dirname(filename))
sdy50d51772016-09-09 21:36:5083 while not (os.path.basename(curdir) == 'src'
johnme956993f2015-01-21 14:29:4384 and PathExists(curdir, 'DEPS')
85 and (PathExists(curdir, '..', '.gclient')
86 or PathExists(curdir, '.git'))):
[email protected]a08a4282013-02-05 23:45:5287 nextdir = os.path.normpath(os.path.join(curdir, '..'))
88 if nextdir == curdir:
89 return None
90 curdir = nextdir
johnme956993f2015-01-21 14:29:4391 return curdir
[email protected]a08a4282013-02-05 23:45:5292
93
asanka234b8d22015-06-26 22:40:2394def GetDefaultSourceFile(chrome_root, filename):
95 """Returns the default source file to use as an alternative to |filename|.
[email protected]a08a4282013-02-05 23:45:5296
asanka234b8d22015-06-26 22:40:2397 Compile flags used to build the default source file is assumed to be a
98 close-enough approximation for building |filename|.
[email protected]a08a4282013-02-05 23:45:5299
100 Args:
asanka3346ab662015-06-03 00:27:17101 chrome_root: (String) Absolute path to the root of Chromium checkout.
asanka234b8d22015-06-26 22:40:23102 filename: (String) Absolute path to the source file.
[email protected]a08a4282013-02-05 23:45:52103
104 Returns:
asanka234b8d22015-06-26 22:40:23105 (String) Absolute path to substitute source file.
[email protected]a08a4282013-02-05 23:45:52106 """
[email protected]3025ebc42014-07-08 15:37:22107 blink_root = os.path.join(chrome_root, 'third_party', 'WebKit')
asanka3346ab662015-06-03 00:27:17108 if filename.startswith(blink_root):
109 return os.path.join(blink_root, 'Source', 'core', 'Init.cpp')
110 else:
asanka7dc195b12016-06-02 15:11:09111 if 'test.' in filename:
112 return os.path.join(chrome_root, 'base', 'logging_unittest.cc')
asanka3346ab662015-06-03 00:27:17113 return os.path.join(chrome_root, 'base', 'logging.cc')
[email protected]3025ebc42014-07-08 15:37:22114
asanka3346ab662015-06-03 00:27:17115
asanka234b8d22015-06-26 22:40:23116def GetBuildableSourceFile(chrome_root, filename):
117 """Returns a buildable source file corresponding to |filename|.
118
119 A buildable source file is one which is likely to be passed into clang as a
120 source file during the build. For .h files, returns the closest matching .cc,
121 .cpp or .c file. If no such file is found, returns the same as
122 GetDefaultSourceFile().
asanka3346ab662015-06-03 00:27:17123
124 Args:
125 chrome_root: (String) Absolute path to the root of Chromium checkout.
126 filename: (String) Absolute path to the target source file.
127
128 Returns:
asanka234b8d22015-06-26 22:40:23129 (String) Absolute path to source file.
asanka3346ab662015-06-03 00:27:17130 """
[email protected]eea749f2013-02-06 19:30:38131 if filename.endswith('.h'):
asanka3346ab662015-06-03 00:27:17132 # Header files can't be built. Instead, try to match a header file to its
133 # corresponding source file.
134 alternates = ['.cc', '.cpp', '.c']
[email protected]eea749f2013-02-06 19:30:38135 for alt_extension in alternates:
136 alt_name = filename[:-2] + alt_extension
137 if os.path.exists(alt_name):
asanka3346ab662015-06-03 00:27:17138 return alt_name
[email protected]eea749f2013-02-06 19:30:38139
asanka234b8d22015-06-26 22:40:23140 return GetDefaultSourceFile(chrome_root, filename)
[email protected]b944e062013-05-08 01:40:55141
asanka3346ab662015-06-03 00:27:17142 return filename
143
144
asanka234b8d22015-06-26 22:40:23145def GetNinjaBuildOutputsForSourceFile(out_dir, filename):
146 """Returns a list of build outputs for filename.
asanka3346ab662015-06-03 00:27:17147
asanka234b8d22015-06-26 22:40:23148 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
149 inputs and outputs of |filename|. This list is then filtered to only include
150 .o and .obj outputs.
asanka3346ab662015-06-03 00:27:17151
152 Args:
153 out_dir: (String) Absolute path to ninja build output directory.
154 filename: (String) Absolute path to source file.
155
156 Returns:
asanka234b8d22015-06-26 22:40:23157 (List of Strings) List of target names. Will return [] if |filename| doesn't
158 yield any .o or .obj outputs.
asanka3346ab662015-06-03 00:27:17159 """
dzhioev37accc22014-08-29 23:08:21160 # Ninja needs the path to the source file relative to the output build
161 # directory.
sdy50d51772016-09-09 21:36:50162 rel_filename = os.path.relpath(filename, out_dir)
dzhioev37accc22014-08-29 23:08:21163
asanka234b8d22015-06-26 22:40:23164 p = subprocess.Popen(['ninja', '-C', out_dir, '-t', 'query', rel_filename],
pastarmovj7e3be85f2016-06-07 17:53:58165 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
166 universal_newlines=True)
asanka234b8d22015-06-26 22:40:23167 stdout, _ = p.communicate()
pastarmovj7e3be85f2016-06-07 17:53:58168 if p.returncode != 0:
asanka234b8d22015-06-26 22:40:23169 return []
170
171 # The output looks like:
172 # ../../relative/path/to/source.cc:
173 # outputs:
174 # obj/reative/path/to/target.source.o
175 # obj/some/other/target2.source.o
176 # another/target.txt
177 #
178 outputs_text = stdout.partition('\n outputs:\n')[2]
179 output_lines = [line.strip() for line in outputs_text.split('\n')]
180 return [target for target in output_lines
181 if target and (target.endswith('.o') or target.endswith('.obj'))]
182
183
184def GetClangCommandLineForNinjaOutput(out_dir, build_target):
185 """Returns the Clang command line for building |build_target|
186
187 Asks ninja for the list of commands used to build |filename| and returns the
188 final Clang invocation.
189
190 Args:
191 out_dir: (String) Absolute path to ninja build output directory.
192 build_target: (String) A build target understood by ninja
193
194 Returns:
195 (String or None) Clang command line or None if a Clang command line couldn't
196 be determined.
197 """
198 p = subprocess.Popen(['ninja', '-v', '-C', out_dir,
199 '-t', 'commands', build_target],
pastarmovj7e3be85f2016-06-07 17:53:58200 stdout=subprocess.PIPE, universal_newlines=True)
[email protected]a08a4282013-02-05 23:45:52201 stdout, stderr = p.communicate()
pastarmovj7e3be85f2016-06-07 17:53:58202 if p.returncode != 0:
asanka3346ab662015-06-03 00:27:17203 return None
[email protected]a08a4282013-02-05 23:45:52204
asanka234b8d22015-06-26 22:40:23205 # Ninja will return multiple build steps for all dependencies up to
206 # |build_target|. The build step we want is the last Clang invocation, which
207 # is expected to be the one that outputs |build_target|.
[email protected]a08a4282013-02-05 23:45:52208 for line in reversed(stdout.split('\n')):
[email protected]eea749f2013-02-06 19:30:38209 if 'clang' in line:
asanka3346ab662015-06-03 00:27:17210 return line
211 return None
212
213
asanka234b8d22015-06-26 22:40:23214def GetClangCommandLineFromNinjaForSource(out_dir, filename):
215 """Returns a Clang command line used to build |filename|.
216
217 The same source file could be built multiple times using different tool
218 chains. In such cases, this command returns the first Clang invocation. We
219 currently don't prefer one toolchain over another. Hopefully the tool chain
220 corresponding to the Clang command line is compatible with the Clang build
221 used by YCM.
222
223 Args:
224 out_dir: (String) Absolute path to Chromium checkout.
225 filename: (String) Absolute path to source file.
226
227 Returns:
228 (String or None): Command line for Clang invocation using |filename| as a
229 source. Returns None if no such command line could be found.
230 """
231 build_targets = GetNinjaBuildOutputsForSourceFile(out_dir, filename)
232 for build_target in build_targets:
233 command_line = GetClangCommandLineForNinjaOutput(out_dir, build_target)
234 if command_line:
235 return command_line
236 return None
237
238
asanka3346ab662015-06-03 00:27:17239def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
240 additional_flags):
241 """Extracts relevant command line options from |clang_commandline|
242
243 Args:
244 clang_commandline: (String) Full Clang invocation.
245 out_dir: (String) Absolute path to ninja build directory. Relative paths in
246 the command line are relative to |out_dir|.
247 additional_flags: (List of String) Additional flags to return.
248
249 Returns:
asanka00a5f592015-07-20 19:37:01250 (List of Strings) The list of command line flags for this source file. Can
251 be empty.
asanka3346ab662015-06-03 00:27:17252 """
asanka00a5f592015-07-20 19:37:01253 clang_flags = [] + additional_flags
[email protected]a08a4282013-02-05 23:45:52254
[email protected]e666f182014-01-09 12:21:37255 # Parse flags that are important for YCM's purposes.
asanka3346ab662015-06-03 00:27:17256 clang_tokens = shlex.split(clang_commandline)
eromanf184b3a2015-08-13 19:12:38257 for flag_index, flag in enumerate(clang_tokens):
[email protected]a08a4282013-02-05 23:45:52258 if flag.startswith('-I'):
259 # Relative paths need to be resolved, because they're relative to the
260 # output dir, not the source.
261 if flag[2] == '/':
asanka00a5f592015-07-20 19:37:01262 clang_flags.append(flag)
[email protected]a08a4282013-02-05 23:45:52263 else:
[email protected]b944e062013-05-08 01:40:55264 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
asanka00a5f592015-07-20 19:37:01265 clang_flags.append('-I' + abs_path)
[email protected]e666f182014-01-09 12:21:37266 elif flag.startswith('-std'):
asanka00a5f592015-07-20 19:37:01267 clang_flags.append(flag)
[email protected]eea749f2013-02-06 19:30:38268 elif flag.startswith('-') and flag[1] in 'DWFfmO':
[email protected]dc569cf92013-07-12 03:21:12269 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
270 # These flags causes libclang (3.3) to crash. Remove it until things
[email protected]06a4f3e2013-07-09 00:16:49271 # are fixed.
272 continue
asanka00a5f592015-07-20 19:37:01273 clang_flags.append(flag)
eromanf184b3a2015-08-13 19:12:38274 elif flag == '-isysroot':
275 # On Mac -isysroot <path> is used to find the system headers.
276 # Copy over both flags.
277 if flag_index + 1 < len(clang_tokens):
278 clang_flags.append(flag)
279 clang_flags.append(clang_tokens[flag_index + 1])
sunnyps3a2e1e82016-02-12 18:35:57280 elif flag.startswith('--sysroot='):
281 # On Linux we use a sysroot image.
282 sysroot_path = flag.lstrip('--sysroot=')
283 if sysroot_path.startswith('/'):
284 clang_flags.append(flag)
285 else:
286 abs_path = os.path.normpath(os.path.join(out_dir, sysroot_path))
287 clang_flags.append('--sysroot=' + abs_path)
asanka00a5f592015-07-20 19:37:01288 return clang_flags
asanka3346ab662015-06-03 00:27:17289
290
291def GetClangOptionsFromNinjaForFilename(chrome_root, filename):
292 """Returns the Clang command line options needed for building |filename|.
293
294 Command line options are based on the command used by ninja for building
295 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
296 If a suitable companion file can't be located or if ninja doesn't know about
297 |filename|, then uses default source files in Blink and Chromium for
298 determining the commandline.
299
300 Args:
301 chrome_root: (String) Path to src/.
302 filename: (String) Absolute path to source file being edited.
303
304 Returns:
asanka00a5f592015-07-20 19:37:01305 (List of Strings) The list of command line flags for this source file. Can
306 be empty.
asanka3346ab662015-06-03 00:27:17307 """
308 if not chrome_root:
asanka00a5f592015-07-20 19:37:01309 return []
asanka3346ab662015-06-03 00:27:17310
311 # Generally, everyone benefits from including Chromium's src/, because all of
312 # Chromium's includes are relative to that.
313 additional_flags = ['-I' + os.path.join(chrome_root)]
314
315 # Version of Clang used to compile Chromium can be newer then version of
316 # libclang that YCM uses for completion. So it's possible that YCM's libclang
317 # doesn't know about some used warning options, which causes compilation
318 # warnings (and errors, because of '-Werror');
319 additional_flags.append('-Wno-unknown-warning-option')
320
321 sys.path.append(os.path.join(chrome_root, 'tools', 'vim'))
322 from ninja_output import GetNinjaOutputDirectory
sdy50d51772016-09-09 21:36:50323 out_dir = GetNinjaOutputDirectory(chrome_root)
asanka3346ab662015-06-03 00:27:17324
asanka234b8d22015-06-26 22:40:23325 clang_line = GetClangCommandLineFromNinjaForSource(
326 out_dir, GetBuildableSourceFile(chrome_root, filename))
asanka3346ab662015-06-03 00:27:17327 if not clang_line:
328 # If ninja didn't know about filename or it's companion files, then try a
329 # default build target. It is possible that the file is new, or build.ninja
330 # is stale.
asanka234b8d22015-06-26 22:40:23331 clang_line = GetClangCommandLineFromNinjaForSource(
332 out_dir, GetDefaultSourceFile(chrome_root, filename))
333
334 if not clang_line:
pastarmovj7e3be85f2016-06-07 17:53:58335 return additional_flags
asanka3346ab662015-06-03 00:27:17336
337 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
[email protected]a08a4282013-02-05 23:45:52338
339
340def FlagsForFile(filename):
341 """This is the main entry point for YCM. Its interface is fixed.
342
343 Args:
344 filename: (String) Path to source file being edited.
345
346 Returns:
347 (Dictionary)
348 'flags': (List of Strings) Command line flags.
349 'do_cache': (Boolean) True if the result should be cached.
350 """
asanka3346ab662015-06-03 00:27:17351 abs_filename = os.path.abspath(filename)
352 chrome_root = FindChromeSrcFromFilename(abs_filename)
asanka00a5f592015-07-20 19:37:01353 clang_flags = GetClangOptionsFromNinjaForFilename(chrome_root, abs_filename)
asanka3346ab662015-06-03 00:27:17354
asanka00a5f592015-07-20 19:37:01355 # If clang_flags could not be determined, then assume that was due to a
356 # transient failure. Preventing YCM from caching the flags allows us to try to
357 # determine the flags again.
358 should_cache_flags_for_file = bool(clang_flags)
asanka3346ab662015-06-03 00:27:17359
asanka00a5f592015-07-20 19:37:01360 final_flags = _default_flags + clang_flags
[email protected]a08a4282013-02-05 23:45:52361
[email protected]a08a4282013-02-05 23:45:52362 return {
363 'flags': final_flags,
asanka3346ab662015-06-03 00:27:17364 'do_cache': should_cache_flags_for_file
[email protected]a08a4282013-02-05 23:45:52365 }