blob: 214500014274ce4edea8bcff5b05b265296456be [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
asanka3346ab662015-06-03 00:27:1758# A dictionary mapping Clang binary path to a list of Clang command line
59# arguments that specify the system include paths. It is used as a cache of the
60# system include options since these options aren't expected to change per
61# source file for the same clang binary. SystemIncludeDirectoryFlags() updates
62# this map each time it runs a Clang binary to determine system include paths.
63#
64# Entries look like:
65# '/home/username/my-llvm/bin/clang++': ['-isystem',
66# '/home/username/my-llvm/include', '-isystem', '/usr/include']
67_clang_system_include_map = {}
[email protected]eea749f2013-02-06 19:30:3868
johnme8d6edba2015-01-21 14:26:2869
[email protected]a08a4282013-02-05 23:45:5270# Flags from YCM's default config.
asanka01c39d082015-05-18 20:47:1171_default_flags = [
72 '-DUSE_CLANG_COMPLETER',
73 '-std=c++11',
74 '-x',
75 'c++',
[email protected]a08a4282013-02-05 23:45:5276]
77
asanka3346ab662015-06-03 00:27:1778
79def FallbackSystemIncludeDirectoryFlags():
80 """Returns a best guess list of system include directory flags for Clang.
81
82 If Ninja doesn't give us a build step that specifies a Clang invocation or if
83 something goes wrong while determining the system include paths, then this
84 function can be used to determine some set of values that's better than
85 nothing.
86
87 Returns:
88 (List of Strings) Compiler flags that specify the system include paths.
89 """
90 if _clang_system_include_map:
91 return _clang_system_include_map.itervalues().next()
92 return []
93
94
95def SystemIncludeDirectoryFlags(clang_binary, clang_flags):
96 """Determines compile flags for specifying system include directories.
97
98 Use as a workaround for https://github.com/Valloric/YouCompleteMe/issues/303
99
100 Caches the results of determining the system include directories in
101 _clang_system_include_map. Subsequent calls to SystemIncludeDirectoryFlags()
102 uses the cached results for the same binary even if |clang_flags| differ.
103
104 Args:
105 clang_binary: (String) Path to clang binary.
106 clang_flags: (List of Strings) List of additional flags to clang. It may
107 affect the choice of system include directories if -stdlib= is specified.
108 _default_flags are always included in the list of flags passed to clang.
109
110 Returns:
111 (List of Strings) Compile flags to append.
112 """
113
114 if clang_binary in _clang_system_include_map:
115 return _clang_system_include_map[clang_binary]
116
117 all_clang_flags = [] + _default_flags
118 all_clang_flags += [flag for flag in clang_flags
119 if flag.startswith('-std=') or flag.startswith('-stdlib=')]
120 all_clang_flags += ['-v', '-E', '-']
121 try:
122 with open(os.devnull, 'rb') as DEVNULL:
123 output = subprocess.check_output([clang_binary] + all_clang_flags,
124 stdin=DEVNULL, stderr=subprocess.STDOUT)
125 except:
126 # Even though we couldn't figure out the flags for the given binary, if we
127 # have results from another one, we'll use that. This logic assumes that the
128 # list of default system directories for one binary can be used with
129 # another.
130 return FallbackSystemIncludeDirectoryFlags()
131 includes_regex = r'#include <\.\.\.> search starts here:\s*' \
132 r'(.*?)End of search list\.'
133 includes = re.search(includes_regex, output.decode(), re.DOTALL).group(1)
134 system_include_flags = []
135 for path in includes.splitlines():
136 path = path.strip()
137 if os.path.isdir(path):
138 system_include_flags.append('-isystem')
139 system_include_flags.append(path)
140 if system_include_flags:
141 _clang_system_include_map[clang_binary] = system_include_flags
142 return system_include_flags
143
144
[email protected]1cab75682013-04-24 19:50:49145def PathExists(*args):
146 return os.path.exists(os.path.join(*args))
147
148
[email protected]a08a4282013-02-05 23:45:52149def FindChromeSrcFromFilename(filename):
150 """Searches for the root of the Chromium checkout.
151
152 Simply checks parent directories until it finds .gclient and src/.
153
154 Args:
155 filename: (String) Path to source file being edited.
156
157 Returns:
158 (String) Path of 'src/', or None if unable to find.
159 """
160 curdir = os.path.normpath(os.path.dirname(filename))
johnme956993f2015-01-21 14:29:43161 while not (os.path.basename(os.path.realpath(curdir)) == 'src'
162 and PathExists(curdir, 'DEPS')
163 and (PathExists(curdir, '..', '.gclient')
164 or PathExists(curdir, '.git'))):
[email protected]a08a4282013-02-05 23:45:52165 nextdir = os.path.normpath(os.path.join(curdir, '..'))
166 if nextdir == curdir:
167 return None
168 curdir = nextdir
johnme956993f2015-01-21 14:29:43169 return curdir
[email protected]a08a4282013-02-05 23:45:52170
171
asanka234b8d22015-06-26 22:40:23172def GetDefaultSourceFile(chrome_root, filename):
173 """Returns the default source file to use as an alternative to |filename|.
[email protected]a08a4282013-02-05 23:45:52174
asanka234b8d22015-06-26 22:40:23175 Compile flags used to build the default source file is assumed to be a
176 close-enough approximation for building |filename|.
[email protected]a08a4282013-02-05 23:45:52177
178 Args:
asanka3346ab662015-06-03 00:27:17179 chrome_root: (String) Absolute path to the root of Chromium checkout.
asanka234b8d22015-06-26 22:40:23180 filename: (String) Absolute path to the source file.
[email protected]a08a4282013-02-05 23:45:52181
182 Returns:
asanka234b8d22015-06-26 22:40:23183 (String) Absolute path to substitute source file.
[email protected]a08a4282013-02-05 23:45:52184 """
[email protected]3025ebc42014-07-08 15:37:22185 blink_root = os.path.join(chrome_root, 'third_party', 'WebKit')
asanka3346ab662015-06-03 00:27:17186 if filename.startswith(blink_root):
187 return os.path.join(blink_root, 'Source', 'core', 'Init.cpp')
188 else:
189 return os.path.join(chrome_root, 'base', 'logging.cc')
[email protected]3025ebc42014-07-08 15:37:22190
asanka3346ab662015-06-03 00:27:17191
asanka234b8d22015-06-26 22:40:23192def GetBuildableSourceFile(chrome_root, filename):
193 """Returns a buildable source file corresponding to |filename|.
194
195 A buildable source file is one which is likely to be passed into clang as a
196 source file during the build. For .h files, returns the closest matching .cc,
197 .cpp or .c file. If no such file is found, returns the same as
198 GetDefaultSourceFile().
asanka3346ab662015-06-03 00:27:17199
200 Args:
201 chrome_root: (String) Absolute path to the root of Chromium checkout.
202 filename: (String) Absolute path to the target source file.
203
204 Returns:
asanka234b8d22015-06-26 22:40:23205 (String) Absolute path to source file.
asanka3346ab662015-06-03 00:27:17206 """
[email protected]eea749f2013-02-06 19:30:38207 if filename.endswith('.h'):
asanka3346ab662015-06-03 00:27:17208 # Header files can't be built. Instead, try to match a header file to its
209 # corresponding source file.
210 alternates = ['.cc', '.cpp', '.c']
[email protected]eea749f2013-02-06 19:30:38211 for alt_extension in alternates:
212 alt_name = filename[:-2] + alt_extension
213 if os.path.exists(alt_name):
asanka3346ab662015-06-03 00:27:17214 return alt_name
[email protected]eea749f2013-02-06 19:30:38215
asanka234b8d22015-06-26 22:40:23216 return GetDefaultSourceFile(chrome_root, filename)
[email protected]b944e062013-05-08 01:40:55217
asanka3346ab662015-06-03 00:27:17218 return filename
219
220
asanka234b8d22015-06-26 22:40:23221def GetNinjaBuildOutputsForSourceFile(out_dir, filename):
222 """Returns a list of build outputs for filename.
asanka3346ab662015-06-03 00:27:17223
asanka234b8d22015-06-26 22:40:23224 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
225 inputs and outputs of |filename|. This list is then filtered to only include
226 .o and .obj outputs.
asanka3346ab662015-06-03 00:27:17227
228 Args:
229 out_dir: (String) Absolute path to ninja build output directory.
230 filename: (String) Absolute path to source file.
231
232 Returns:
asanka234b8d22015-06-26 22:40:23233 (List of Strings) List of target names. Will return [] if |filename| doesn't
234 yield any .o or .obj outputs.
asanka3346ab662015-06-03 00:27:17235 """
dzhioev37accc22014-08-29 23:08:21236 # Ninja needs the path to the source file relative to the output build
237 # directory.
dzhioev95e4bfd2014-09-05 14:39:53238 rel_filename = os.path.relpath(os.path.realpath(filename), out_dir)
dzhioev37accc22014-08-29 23:08:21239
asanka234b8d22015-06-26 22:40:23240 p = subprocess.Popen(['ninja', '-C', out_dir, '-t', 'query', rel_filename],
241 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
242 stdout, _ = p.communicate()
243 if p.returncode:
244 return []
245
246 # The output looks like:
247 # ../../relative/path/to/source.cc:
248 # outputs:
249 # obj/reative/path/to/target.source.o
250 # obj/some/other/target2.source.o
251 # another/target.txt
252 #
253 outputs_text = stdout.partition('\n outputs:\n')[2]
254 output_lines = [line.strip() for line in outputs_text.split('\n')]
255 return [target for target in output_lines
256 if target and (target.endswith('.o') or target.endswith('.obj'))]
257
258
259def GetClangCommandLineForNinjaOutput(out_dir, build_target):
260 """Returns the Clang command line for building |build_target|
261
262 Asks ninja for the list of commands used to build |filename| and returns the
263 final Clang invocation.
264
265 Args:
266 out_dir: (String) Absolute path to ninja build output directory.
267 build_target: (String) A build target understood by ninja
268
269 Returns:
270 (String or None) Clang command line or None if a Clang command line couldn't
271 be determined.
272 """
273 p = subprocess.Popen(['ninja', '-v', '-C', out_dir,
274 '-t', 'commands', build_target],
[email protected]a08a4282013-02-05 23:45:52275 stdout=subprocess.PIPE)
276 stdout, stderr = p.communicate()
277 if p.returncode:
asanka3346ab662015-06-03 00:27:17278 return None
[email protected]a08a4282013-02-05 23:45:52279
asanka234b8d22015-06-26 22:40:23280 # Ninja will return multiple build steps for all dependencies up to
281 # |build_target|. The build step we want is the last Clang invocation, which
282 # is expected to be the one that outputs |build_target|.
[email protected]a08a4282013-02-05 23:45:52283 for line in reversed(stdout.split('\n')):
[email protected]eea749f2013-02-06 19:30:38284 if 'clang' in line:
asanka3346ab662015-06-03 00:27:17285 return line
286 return None
287
288
asanka234b8d22015-06-26 22:40:23289def GetClangCommandLineFromNinjaForSource(out_dir, filename):
290 """Returns a Clang command line used to build |filename|.
291
292 The same source file could be built multiple times using different tool
293 chains. In such cases, this command returns the first Clang invocation. We
294 currently don't prefer one toolchain over another. Hopefully the tool chain
295 corresponding to the Clang command line is compatible with the Clang build
296 used by YCM.
297
298 Args:
299 out_dir: (String) Absolute path to Chromium checkout.
300 filename: (String) Absolute path to source file.
301
302 Returns:
303 (String or None): Command line for Clang invocation using |filename| as a
304 source. Returns None if no such command line could be found.
305 """
306 build_targets = GetNinjaBuildOutputsForSourceFile(out_dir, filename)
307 for build_target in build_targets:
308 command_line = GetClangCommandLineForNinjaOutput(out_dir, build_target)
309 if command_line:
310 return command_line
311 return None
312
313
asanka3346ab662015-06-03 00:27:17314def GetNormalizedClangCommand(command, out_dir):
315 """Gets the normalized Clang binary path if |command| is a Clang command.
316
317 Args:
318 command: (String) Clang command.
319 out_dir: (String) Absolute path the ninja build directory.
320
321 Returns:
322 (String or None)
323 None : if command is not a clang command.
324 Absolute path to clang binary : if |command| is an absolute or relative
325 path to clang. If relative, it is assumed to be relative to |out_dir|.
326 |command|: if command is a name of a binary.
327 """
328 if command.endswith('clang++') or command.endswith('clang'):
329 if os.path.basename(command) == command:
330 return command
331 return os.path.normpath(os.path.join(out_dir, command))
332 return None
333
334
335def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
336 additional_flags):
337 """Extracts relevant command line options from |clang_commandline|
338
339 Args:
340 clang_commandline: (String) Full Clang invocation.
341 out_dir: (String) Absolute path to ninja build directory. Relative paths in
342 the command line are relative to |out_dir|.
343 additional_flags: (List of String) Additional flags to return.
344
345 Returns:
346 ((List of Strings), (List of Strings)) The first item in the tuple is a list
347 of command line flags for this source file. The second item in the tuple is
348 a list of command line flags that define the system include paths. Either or
349 both can be empty.
350 """
351 chrome_flags = [] + additional_flags
352 system_include_flags = []
[email protected]a08a4282013-02-05 23:45:52353
[email protected]e666f182014-01-09 12:21:37354 # Parse flags that are important for YCM's purposes.
asanka3346ab662015-06-03 00:27:17355 clang_tokens = shlex.split(clang_commandline)
asanka01c39d082015-05-18 20:47:11356 for flag in clang_tokens:
[email protected]a08a4282013-02-05 23:45:52357 if flag.startswith('-I'):
358 # Relative paths need to be resolved, because they're relative to the
359 # output dir, not the source.
360 if flag[2] == '/':
361 chrome_flags.append(flag)
362 else:
[email protected]b944e062013-05-08 01:40:55363 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
[email protected]a08a4282013-02-05 23:45:52364 chrome_flags.append('-I' + abs_path)
[email protected]e666f182014-01-09 12:21:37365 elif flag.startswith('-std'):
366 chrome_flags.append(flag)
[email protected]eea749f2013-02-06 19:30:38367 elif flag.startswith('-') and flag[1] in 'DWFfmO':
[email protected]dc569cf92013-07-12 03:21:12368 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
369 # These flags causes libclang (3.3) to crash. Remove it until things
[email protected]06a4f3e2013-07-09 00:16:49370 # are fixed.
371 continue
[email protected]a08a4282013-02-05 23:45:52372 chrome_flags.append(flag)
373
asanka01c39d082015-05-18 20:47:11374 # Assume that the command for invoking clang++ looks like one of the
375 # following:
376 # 1) /path/to/clang/clang++ arguments
377 # 2) /some/wrapper /path/to/clang++ arguments
378 #
asanka3346ab662015-06-03 00:27:17379 # We'll look at the first two tokens on the command line to see if they look
380 # like Clang commands, and if so use it to determine the system include
381 # directory flags.
asanka01c39d082015-05-18 20:47:11382 for command in clang_tokens[0:2]:
asanka3346ab662015-06-03 00:27:17383 normalized_command = GetNormalizedClangCommand(command, out_dir)
384 if normalized_command:
385 system_include_flags += SystemIncludeDirectoryFlags(normalized_command,
386 chrome_flags)
asanka01c39d082015-05-18 20:47:11387 break
388
asanka3346ab662015-06-03 00:27:17389 return (chrome_flags, system_include_flags)
390
391
392def GetClangOptionsFromNinjaForFilename(chrome_root, filename):
393 """Returns the Clang command line options needed for building |filename|.
394
395 Command line options are based on the command used by ninja for building
396 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
397 If a suitable companion file can't be located or if ninja doesn't know about
398 |filename|, then uses default source files in Blink and Chromium for
399 determining the commandline.
400
401 Args:
402 chrome_root: (String) Path to src/.
403 filename: (String) Absolute path to source file being edited.
404
405 Returns:
406 ((List of Strings), (List of Strings)) The first item in the tuple is a list
407 of command line flags for this source file. The second item in the tuple is
408 a list of command line flags that define the system include paths. Either or
409 both can be empty.
410 """
411 if not chrome_root:
412 return ([],[])
413
414 # Generally, everyone benefits from including Chromium's src/, because all of
415 # Chromium's includes are relative to that.
416 additional_flags = ['-I' + os.path.join(chrome_root)]
417
418 # Version of Clang used to compile Chromium can be newer then version of
419 # libclang that YCM uses for completion. So it's possible that YCM's libclang
420 # doesn't know about some used warning options, which causes compilation
421 # warnings (and errors, because of '-Werror');
422 additional_flags.append('-Wno-unknown-warning-option')
423
424 sys.path.append(os.path.join(chrome_root, 'tools', 'vim'))
425 from ninja_output import GetNinjaOutputDirectory
426 out_dir = os.path.realpath(GetNinjaOutputDirectory(chrome_root))
427
asanka234b8d22015-06-26 22:40:23428 clang_line = GetClangCommandLineFromNinjaForSource(
429 out_dir, GetBuildableSourceFile(chrome_root, filename))
asanka3346ab662015-06-03 00:27:17430 if not clang_line:
431 # If ninja didn't know about filename or it's companion files, then try a
432 # default build target. It is possible that the file is new, or build.ninja
433 # is stale.
asanka234b8d22015-06-26 22:40:23434 clang_line = GetClangCommandLineFromNinjaForSource(
435 out_dir, GetDefaultSourceFile(chrome_root, filename))
436
437 if not clang_line:
438 return (additional_flags, [])
asanka3346ab662015-06-03 00:27:17439
440 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
[email protected]a08a4282013-02-05 23:45:52441
442
443def FlagsForFile(filename):
444 """This is the main entry point for YCM. Its interface is fixed.
445
446 Args:
447 filename: (String) Path to source file being edited.
448
449 Returns:
450 (Dictionary)
451 'flags': (List of Strings) Command line flags.
452 'do_cache': (Boolean) True if the result should be cached.
453 """
asanka3346ab662015-06-03 00:27:17454 abs_filename = os.path.abspath(filename)
455 chrome_root = FindChromeSrcFromFilename(abs_filename)
456 (chrome_flags, system_include_flags) = GetClangOptionsFromNinjaForFilename(
457 chrome_root, abs_filename)
458
459 # If either chrome_flags or system_include_flags could not be determined, then
460 # assume that was due to a transient failure. Preventing YCM from caching the
461 # flags allows us to try to determine the flags again.
462 should_cache_flags_for_file = \
463 bool(chrome_flags) and bool(system_include_flags)
464
465 if not system_include_flags:
466 system_include_flags = FallbackSystemIncludeDirectoryFlags()
467 final_flags = _default_flags + chrome_flags + system_include_flags
[email protected]a08a4282013-02-05 23:45:52468
[email protected]a08a4282013-02-05 23:45:52469 return {
470 'flags': final_flags,
asanka3346ab662015-06-03 00:27:17471 'do_cache': should_cache_flags_for_file
[email protected]a08a4282013-02-05 23:45:52472 }