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 # Flags from YCM's default config.
60 '-DUSE_CLANG_COMPLETER',
67 def PathExists(*args
):
68 return os
.path
.exists(os
.path
.join(*args
))
71 def FindChromeSrcFromFilename(filename
):
72 """Searches for the root of the Chromium checkout.
74 Simply checks parent directories until it finds .gclient and src/.
77 filename: (String) Path to source file being edited.
80 (String) Path of 'src/', or None if unable to find.
82 curdir
= os
.path
.normpath(os
.path
.dirname(filename
))
83 while not (os
.path
.basename(os
.path
.realpath(curdir
)) == 'src'
84 and PathExists(curdir
, 'DEPS')
85 and (PathExists(curdir
, '..', '.gclient')
86 or PathExists(curdir
, '.git'))):
87 nextdir
= os
.path
.normpath(os
.path
.join(curdir
, '..'))
94 def GetDefaultSourceFile(chrome_root
, filename
):
95 """Returns the default source file to use as an alternative to |filename|.
97 Compile flags used to build the default source file is assumed to be a
98 close-enough approximation for building |filename|.
101 chrome_root: (String) Absolute path to the root of Chromium checkout.
102 filename: (String) Absolute path to the source file.
105 (String) Absolute path to substitute source file.
107 blink_root
= os
.path
.join(chrome_root
, 'third_party', 'WebKit')
108 if filename
.startswith(blink_root
):
109 return os
.path
.join(blink_root
, 'Source', 'core', 'Init.cpp')
111 return os
.path
.join(chrome_root
, 'base', 'logging.cc')
114 def GetBuildableSourceFile(chrome_root
, filename
):
115 """Returns a buildable source file corresponding to |filename|.
117 A buildable source file is one which is likely to be passed into clang as a
118 source file during the build. For .h files, returns the closest matching .cc,
119 .cpp or .c file. If no such file is found, returns the same as
120 GetDefaultSourceFile().
123 chrome_root: (String) Absolute path to the root of Chromium checkout.
124 filename: (String) Absolute path to the target source file.
127 (String) Absolute path to source file.
129 if filename
.endswith('.h'):
130 # Header files can't be built. Instead, try to match a header file to its
131 # corresponding source file.
132 alternates
= ['.cc', '.cpp', '.c']
133 for alt_extension
in alternates
:
134 alt_name
= filename
[:-2] + alt_extension
135 if os
.path
.exists(alt_name
):
138 return GetDefaultSourceFile(chrome_root
, filename
)
143 def GetNinjaBuildOutputsForSourceFile(out_dir
, filename
):
144 """Returns a list of build outputs for filename.
146 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
147 inputs and outputs of |filename|. This list is then filtered to only include
151 out_dir: (String) Absolute path to ninja build output directory.
152 filename: (String) Absolute path to source file.
155 (List of Strings) List of target names. Will return [] if |filename| doesn't
156 yield any .o or .obj outputs.
158 # Ninja needs the path to the source file relative to the output build
160 rel_filename
= os
.path
.relpath(os
.path
.realpath(filename
), out_dir
)
162 p
= subprocess
.Popen(['ninja', '-C', out_dir
, '-t', 'query', rel_filename
],
163 stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
)
164 stdout
, _
= p
.communicate()
168 # The output looks like:
169 # ../../relative/path/to/source.cc:
171 # obj/reative/path/to/target.source.o
172 # obj/some/other/target2.source.o
175 outputs_text
= stdout
.partition('\n outputs:\n')[2]
176 output_lines
= [line
.strip() for line
in outputs_text
.split('\n')]
177 return [target
for target
in output_lines
178 if target
and (target
.endswith('.o') or target
.endswith('.obj'))]
181 def GetClangCommandLineForNinjaOutput(out_dir
, build_target
):
182 """Returns the Clang command line for building |build_target|
184 Asks ninja for the list of commands used to build |filename| and returns the
185 final Clang invocation.
188 out_dir: (String) Absolute path to ninja build output directory.
189 build_target: (String) A build target understood by ninja
192 (String or None) Clang command line or None if a Clang command line couldn't
195 p
= subprocess
.Popen(['ninja', '-v', '-C', out_dir
,
196 '-t', 'commands', build_target
],
197 stdout
=subprocess
.PIPE
)
198 stdout
, stderr
= p
.communicate()
202 # Ninja will return multiple build steps for all dependencies up to
203 # |build_target|. The build step we want is the last Clang invocation, which
204 # is expected to be the one that outputs |build_target|.
205 for line
in reversed(stdout
.split('\n')):
211 def GetClangCommandLineFromNinjaForSource(out_dir
, filename
):
212 """Returns a Clang command line used to build |filename|.
214 The same source file could be built multiple times using different tool
215 chains. In such cases, this command returns the first Clang invocation. We
216 currently don't prefer one toolchain over another. Hopefully the tool chain
217 corresponding to the Clang command line is compatible with the Clang build
221 out_dir: (String) Absolute path to Chromium checkout.
222 filename: (String) Absolute path to source file.
225 (String or None): Command line for Clang invocation using |filename| as a
226 source. Returns None if no such command line could be found.
228 build_targets
= GetNinjaBuildOutputsForSourceFile(out_dir
, filename
)
229 for build_target
in build_targets
:
230 command_line
= GetClangCommandLineForNinjaOutput(out_dir
, build_target
)
236 def GetClangOptionsFromCommandLine(clang_commandline
, out_dir
,
238 """Extracts relevant command line options from |clang_commandline|
241 clang_commandline: (String) Full Clang invocation.
242 out_dir: (String) Absolute path to ninja build directory. Relative paths in
243 the command line are relative to |out_dir|.
244 additional_flags: (List of String) Additional flags to return.
247 (List of Strings) The list of command line flags for this source file. Can
250 clang_flags
= [] + additional_flags
252 # Parse flags that are important for YCM's purposes.
253 clang_tokens
= shlex
.split(clang_commandline
)
254 for flag_index
, flag
in enumerate(clang_tokens
):
255 if flag
.startswith('-I'):
256 # Relative paths need to be resolved, because they're relative to the
257 # output dir, not the source.
259 clang_flags
.append(flag
)
261 abs_path
= os
.path
.normpath(os
.path
.join(out_dir
, flag
[2:]))
262 clang_flags
.append('-I' + abs_path
)
263 elif flag
.startswith('-std'):
264 clang_flags
.append(flag
)
265 elif flag
.startswith('-') and flag
[1] in 'DWFfmO':
266 if flag
== '-Wno-deprecated-register' or flag
== '-Wno-header-guard':
267 # These flags causes libclang (3.3) to crash. Remove it until things
270 clang_flags
.append(flag
)
271 elif flag
== '-isysroot':
272 # On Mac -isysroot <path> is used to find the system headers.
273 # Copy over both flags.
274 if flag_index
+ 1 < len(clang_tokens
):
275 clang_flags
.append(flag
)
276 clang_flags
.append(clang_tokens
[flag_index
+ 1])
280 def GetClangOptionsFromNinjaForFilename(chrome_root
, filename
):
281 """Returns the Clang command line options needed for building |filename|.
283 Command line options are based on the command used by ninja for building
284 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
285 If a suitable companion file can't be located or if ninja doesn't know about
286 |filename|, then uses default source files in Blink and Chromium for
287 determining the commandline.
290 chrome_root: (String) Path to src/.
291 filename: (String) Absolute path to source file being edited.
294 (List of Strings) The list of command line flags for this source file. Can
300 # Generally, everyone benefits from including Chromium's src/, because all of
301 # Chromium's includes are relative to that.
302 additional_flags
= ['-I' + os
.path
.join(chrome_root
)]
304 # Version of Clang used to compile Chromium can be newer then version of
305 # libclang that YCM uses for completion. So it's possible that YCM's libclang
306 # doesn't know about some used warning options, which causes compilation
307 # warnings (and errors, because of '-Werror');
308 additional_flags
.append('-Wno-unknown-warning-option')
310 sys
.path
.append(os
.path
.join(chrome_root
, 'tools', 'vim'))
311 from ninja_output
import GetNinjaOutputDirectory
312 out_dir
= os
.path
.realpath(GetNinjaOutputDirectory(chrome_root
))
314 clang_line
= GetClangCommandLineFromNinjaForSource(
315 out_dir
, GetBuildableSourceFile(chrome_root
, filename
))
317 # If ninja didn't know about filename or it's companion files, then try a
318 # default build target. It is possible that the file is new, or build.ninja
320 clang_line
= GetClangCommandLineFromNinjaForSource(
321 out_dir
, GetDefaultSourceFile(chrome_root
, filename
))
324 return (additional_flags
, [])
326 return GetClangOptionsFromCommandLine(clang_line
, out_dir
, additional_flags
)
329 def FlagsForFile(filename
):
330 """This is the main entry point for YCM. Its interface is fixed.
333 filename: (String) Path to source file being edited.
337 'flags': (List of Strings) Command line flags.
338 'do_cache': (Boolean) True if the result should be cached.
340 abs_filename
= os
.path
.abspath(filename
)
341 chrome_root
= FindChromeSrcFromFilename(abs_filename
)
342 clang_flags
= GetClangOptionsFromNinjaForFilename(chrome_root
, abs_filename
)
344 # If clang_flags could not be determined, then assume that was due to a
345 # transient failure. Preventing YCM from caching the flags allows us to try to
346 # determine the flags again.
347 should_cache_flags_for_file
= bool(clang_flags
)
349 final_flags
= _default_flags
+ clang_flags
352 'flags': final_flags
,
353 'do_cache': should_cache_flags_for_file