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.
5 # Autocompletion config for YouCompleteMe in Chromium.
9 # 1. Install YCM [https://github.com/Valloric/YouCompleteMe]
10 # (Googlers should check out [go/ycm])
12 # 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).
16 # ln -rs tools/vim/chromium.ycm_extra_conf.py ../.ycm_extra_conf.py
18 # 3. (optional) Whitelist the .ycm_extra_conf.py from step #2 by adding the
19 # following to your .vimrc:
21 # let g:ycm_extra_conf_globlist=['<path to .ycm_extra_conf.py>']
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.
32 # * You must use ninja & clang to build Chromium.
34 # * You must have run gyp_chromium and built Chromium recently.
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.
42 # * Right now, we only pull the -I and -D flags. That seems to be sufficient
43 # for everything I've used it for.
45 # * That whole ninja & clang thing? We could support other configs if someone
46 # were willing to write the correct commands and a parser.
48 # * This has only been tested on gPrecise.
58 # 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.
65 # '/home/username/my-llvm/bin/clang++': ['-isystem',
66 # '/home/username/my-llvm/include', '-isystem', '/usr/include']
67 _clang_system_include_map
= {}
70 # Flags from YCM's default config.
72 '-DUSE_CLANG_COMPLETER',
79 def FallbackSystemIncludeDirectoryFlags():
80 """Returns a best guess list of system include directory flags for Clang.
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
88 (List of Strings) Compiler flags that specify the system include paths.
90 if _clang_system_include_map
:
91 return _clang_system_include_map
.itervalues().next()
95 def SystemIncludeDirectoryFlags(clang_binary
, clang_flags
):
96 """Determines compile flags for specifying system include directories.
98 Use as a workaround for https://github.com/Valloric/YouCompleteMe/issues/303
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.
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.
111 (List of Strings) Compile flags to append.
114 if clang_binary
in _clang_system_include_map
:
115 return _clang_system_include_map
[clang_binary
]
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', '-']
122 with
open(os
.devnull
, 'rb') as DEVNULL
:
123 output
= subprocess
.check_output([clang_binary
] + all_clang_flags
,
124 stdin
=DEVNULL
, stderr
=subprocess
.STDOUT
)
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
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():
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
145 def PathExists(*args
):
146 return os
.path
.exists(os
.path
.join(*args
))
149 def FindChromeSrcFromFilename(filename
):
150 """Searches for the root of the Chromium checkout.
152 Simply checks parent directories until it finds .gclient and src/.
155 filename: (String) Path to source file being edited.
158 (String) Path of 'src/', or None if unable to find.
160 curdir
= os
.path
.normpath(os
.path
.dirname(filename
))
161 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'))):
165 nextdir
= os
.path
.normpath(os
.path
.join(curdir
, '..'))
166 if nextdir
== curdir
:
172 def GetDefaultSourceFile(chrome_root
, filename
):
173 """Returns the default source file to use as an alternative to |filename|.
175 Compile flags used to build the default source file is assumed to be a
176 close-enough approximation for building |filename|.
179 chrome_root: (String) Absolute path to the root of Chromium checkout.
180 filename: (String) Absolute path to the source file.
183 (String) Absolute path to substitute source file.
185 blink_root
= os
.path
.join(chrome_root
, 'third_party', 'WebKit')
186 if filename
.startswith(blink_root
):
187 return os
.path
.join(blink_root
, 'Source', 'core', 'Init.cpp')
189 return os
.path
.join(chrome_root
, 'base', 'logging.cc')
192 def GetBuildableSourceFile(chrome_root
, filename
):
193 """Returns a buildable source file corresponding to |filename|.
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().
201 chrome_root: (String) Absolute path to the root of Chromium checkout.
202 filename: (String) Absolute path to the target source file.
205 (String) Absolute path to source file.
207 if filename
.endswith('.h'):
208 # Header files can't be built. Instead, try to match a header file to its
209 # corresponding source file.
210 alternates
= ['.cc', '.cpp', '.c']
211 for alt_extension
in alternates
:
212 alt_name
= filename
[:-2] + alt_extension
213 if os
.path
.exists(alt_name
):
216 return GetDefaultSourceFile(chrome_root
, filename
)
221 def GetNinjaBuildOutputsForSourceFile(out_dir
, filename
):
222 """Returns a list of build outputs for filename.
224 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
229 out_dir: (String) Absolute path to ninja build output directory.
230 filename: (String) Absolute path to source file.
233 (List of Strings) List of target names. Will return [] if |filename| doesn't
234 yield any .o or .obj outputs.
236 # Ninja needs the path to the source file relative to the output build
238 rel_filename
= os
.path
.relpath(os
.path
.realpath(filename
), out_dir
)
240 p
= subprocess
.Popen(['ninja', '-C', out_dir
, '-t', 'query', rel_filename
],
241 stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
)
242 stdout
, _
= p
.communicate()
246 # The output looks like:
247 # ../../relative/path/to/source.cc:
249 # obj/reative/path/to/target.source.o
250 # obj/some/other/target2.source.o
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'))]
259 def GetClangCommandLineForNinjaOutput(out_dir
, build_target
):
260 """Returns the Clang command line for building |build_target|
262 Asks ninja for the list of commands used to build |filename| and returns the
263 final Clang invocation.
266 out_dir: (String) Absolute path to ninja build output directory.
267 build_target: (String) A build target understood by ninja
270 (String or None) Clang command line or None if a Clang command line couldn't
273 p
= subprocess
.Popen(['ninja', '-v', '-C', out_dir
,
274 '-t', 'commands', build_target
],
275 stdout
=subprocess
.PIPE
)
276 stdout
, stderr
= p
.communicate()
280 # 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|.
283 for line
in reversed(stdout
.split('\n')):
289 def GetClangCommandLineFromNinjaForSource(out_dir
, filename
):
290 """Returns a Clang command line used to build |filename|.
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
299 out_dir: (String) Absolute path to Chromium checkout.
300 filename: (String) Absolute path to source file.
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.
306 build_targets
= GetNinjaBuildOutputsForSourceFile(out_dir
, filename
)
307 for build_target
in build_targets
:
308 command_line
= GetClangCommandLineForNinjaOutput(out_dir
, build_target
)
314 def GetNormalizedClangCommand(command
, out_dir
):
315 """Gets the normalized Clang binary path if |command| is a Clang command.
318 command: (String) Clang command.
319 out_dir: (String) Absolute path the ninja build directory.
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.
328 if command
.endswith('clang++') or command
.endswith('clang'):
329 if os
.path
.basename(command
) == command
:
331 return os
.path
.normpath(os
.path
.join(out_dir
, command
))
335 def GetClangOptionsFromCommandLine(clang_commandline
, out_dir
,
337 """Extracts relevant command line options from |clang_commandline|
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.
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
351 chrome_flags
= [] + additional_flags
352 system_include_flags
= []
354 # Parse flags that are important for YCM's purposes.
355 clang_tokens
= shlex
.split(clang_commandline
)
356 for flag
in clang_tokens
:
357 if flag
.startswith('-I'):
358 # Relative paths need to be resolved, because they're relative to the
359 # output dir, not the source.
361 chrome_flags
.append(flag
)
363 abs_path
= os
.path
.normpath(os
.path
.join(out_dir
, flag
[2:]))
364 chrome_flags
.append('-I' + abs_path
)
365 elif flag
.startswith('-std'):
366 chrome_flags
.append(flag
)
367 elif flag
.startswith('-') and flag
[1] in 'DWFfmO':
368 if flag
== '-Wno-deprecated-register' or flag
== '-Wno-header-guard':
369 # These flags causes libclang (3.3) to crash. Remove it until things
372 chrome_flags
.append(flag
)
374 # Assume that the command for invoking clang++ looks like one of the
376 # 1) /path/to/clang/clang++ arguments
377 # 2) /some/wrapper /path/to/clang++ arguments
379 # 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
382 for command
in clang_tokens
[0:2]:
383 normalized_command
= GetNormalizedClangCommand(command
, out_dir
)
384 if normalized_command
:
385 system_include_flags
+= SystemIncludeDirectoryFlags(normalized_command
,
389 return (chrome_flags
, system_include_flags
)
392 def GetClangOptionsFromNinjaForFilename(chrome_root
, filename
):
393 """Returns the Clang command line options needed for building |filename|.
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.
402 chrome_root: (String) Path to src/.
403 filename: (String) Absolute path to source file being edited.
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
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
)]
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')
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
))
428 clang_line
= GetClangCommandLineFromNinjaForSource(
429 out_dir
, GetBuildableSourceFile(chrome_root
, filename
))
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
434 clang_line
= GetClangCommandLineFromNinjaForSource(
435 out_dir
, GetDefaultSourceFile(chrome_root
, filename
))
438 return (additional_flags
, [])
440 return GetClangOptionsFromCommandLine(clang_line
, out_dir
, additional_flags
)
443 def FlagsForFile(filename
):
444 """This is the main entry point for YCM. Its interface is fixed.
447 filename: (String) Path to source file being edited.
451 'flags': (List of Strings) Command line flags.
452 'do_cache': (Boolean) True if the result should be cached.
454 abs_filename
= os
.path
.abspath(filename
)
455 chrome_root
= FindChromeSrcFromFilename(abs_filename
)
456 (chrome_flags
, system_include_flags
) = GetClangOptionsFromNinjaForFilename(
457 chrome_root
, abs_filename
)
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
)
465 if not system_include_flags
:
466 system_include_flags
= FallbackSystemIncludeDirectoryFlags()
467 final_flags
= _default_flags
+ chrome_flags
+ system_include_flags
470 'flags': final_flags
,
471 'do_cache': should_cache_flags_for_file