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
in 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
)
275 def GetClangOptionsFromNinjaForFilename(chrome_root
, filename
):
276 """Returns the Clang command line options needed for building |filename|.
278 Command line options are based on the command used by ninja for building
279 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
280 If a suitable companion file can't be located or if ninja doesn't know about
281 |filename|, then uses default source files in Blink and Chromium for
282 determining the commandline.
285 chrome_root: (String) Path to src/.
286 filename: (String) Absolute path to source file being edited.
289 (List of Strings) The list of command line flags for this source file. Can
295 # Generally, everyone benefits from including Chromium's src/, because all of
296 # Chromium's includes are relative to that.
297 additional_flags
= ['-I' + os
.path
.join(chrome_root
)]
299 # Version of Clang used to compile Chromium can be newer then version of
300 # libclang that YCM uses for completion. So it's possible that YCM's libclang
301 # doesn't know about some used warning options, which causes compilation
302 # warnings (and errors, because of '-Werror');
303 additional_flags
.append('-Wno-unknown-warning-option')
305 sys
.path
.append(os
.path
.join(chrome_root
, 'tools', 'vim'))
306 from ninja_output
import GetNinjaOutputDirectory
307 out_dir
= os
.path
.realpath(GetNinjaOutputDirectory(chrome_root
))
309 clang_line
= GetClangCommandLineFromNinjaForSource(
310 out_dir
, GetBuildableSourceFile(chrome_root
, filename
))
312 # If ninja didn't know about filename or it's companion files, then try a
313 # default build target. It is possible that the file is new, or build.ninja
315 clang_line
= GetClangCommandLineFromNinjaForSource(
316 out_dir
, GetDefaultSourceFile(chrome_root
, filename
))
319 return (additional_flags
, [])
321 return GetClangOptionsFromCommandLine(clang_line
, out_dir
, additional_flags
)
324 def FlagsForFile(filename
):
325 """This is the main entry point for YCM. Its interface is fixed.
328 filename: (String) Path to source file being edited.
332 'flags': (List of Strings) Command line flags.
333 'do_cache': (Boolean) True if the result should be cached.
335 abs_filename
= os
.path
.abspath(filename
)
336 chrome_root
= FindChromeSrcFromFilename(abs_filename
)
337 clang_flags
= GetClangOptionsFromNinjaForFilename(chrome_root
, abs_filename
)
339 # If clang_flags could not be determined, then assume that was due to a
340 # transient failure. Preventing YCM from caching the flags allows us to try to
341 # determine the flags again.
342 should_cache_flags_for_file
= bool(clang_flags
)
344 final_flags
= _default_flags
+ clang_flags
347 'flags': final_flags
,
348 'do_cache': should_cache_flags_for_file