Fix SDCH compile error on Mac GN bots.
[chromium-blink-merge.git] / tools / vim / chromium.ycm_extra_conf.py
blob214500014274ce4edea8bcff5b05b265296456be
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.
7 # USAGE:
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).
15 # cd src
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.
27 # 4. Profit
30 # Usage notes:
32 # * You must use ninja & clang to build Chromium.
34 # * You must have run gyp_chromium and built Chromium recently.
37 # Hacking notes:
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.
51 import os
52 import os.path
53 import re
54 import shlex
55 import subprocess
56 import sys
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.
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 = {}
70 # Flags from YCM's default config.
71 _default_flags = [
72 '-DUSE_CLANG_COMPLETER',
73 '-std=c++11',
74 '-x',
75 'c++',
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
85 nothing.
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 []
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.
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.
110 Returns:
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', '-']
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
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/.
154 Args:
155 filename: (String) Path to source file being edited.
157 Returns:
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:
167 return None
168 curdir = nextdir
169 return 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|.
178 Args:
179 chrome_root: (String) Absolute path to the root of Chromium checkout.
180 filename: (String) Absolute path to the source file.
182 Returns:
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')
188 else:
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().
200 Args:
201 chrome_root: (String) Absolute path to the root of Chromium checkout.
202 filename: (String) Absolute path to the target source file.
204 Returns:
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):
214 return alt_name
216 return GetDefaultSourceFile(chrome_root, filename)
218 return 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
226 .o and .obj outputs.
228 Args:
229 out_dir: (String) Absolute path to ninja build output directory.
230 filename: (String) Absolute path to source file.
232 Returns:
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
237 # directory.
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()
243 if p.returncode:
244 return []
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
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.
265 Args:
266 out_dir: (String) Absolute path to ninja build output directory.
267 build_target: (String) A build target understood by ninja
269 Returns:
270 (String or None) Clang command line or None if a Clang command line couldn't
271 be determined.
273 p = subprocess.Popen(['ninja', '-v', '-C', out_dir,
274 '-t', 'commands', build_target],
275 stdout=subprocess.PIPE)
276 stdout, stderr = p.communicate()
277 if p.returncode:
278 return None
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')):
284 if 'clang' in line:
285 return line
286 return None
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
296 used by YCM.
298 Args:
299 out_dir: (String) Absolute path to Chromium checkout.
300 filename: (String) Absolute path to source file.
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.
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
314 def GetNormalizedClangCommand(command, out_dir):
315 """Gets the normalized Clang binary path if |command| is a Clang command.
317 Args:
318 command: (String) Clang command.
319 out_dir: (String) Absolute path the ninja build directory.
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.
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
335 def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
336 additional_flags):
337 """Extracts relevant command line options from |clang_commandline|
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.
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.
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.
360 if flag[2] == '/':
361 chrome_flags.append(flag)
362 else:
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
370 # are fixed.
371 continue
372 chrome_flags.append(flag)
374 # 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
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
381 # directory flags.
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,
386 chrome_flags)
387 break
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.
401 Args:
402 chrome_root: (String) Path to src/.
403 filename: (String) Absolute path to source file being edited.
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.
411 if not chrome_root:
412 return ([],[])
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))
430 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.
434 clang_line = GetClangCommandLineFromNinjaForSource(
435 out_dir, GetDefaultSourceFile(chrome_root, filename))
437 if not clang_line:
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.
446 Args:
447 filename: (String) Path to source file being edited.
449 Returns:
450 (Dictionary)
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
469 return {
470 'flags': final_flags,
471 'do_cache': should_cache_flags_for_file