blob: 5715e294d164b6f66f44bb8996549fc0d016a99c [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
asanka3346ab662015-06-03 00:27:17172def GetDefaultCppFile(chrome_root, filename):
173 """Returns the default target file to use for |filename|.
[email protected]a08a4282013-02-05 23:45:52174
asanka3346ab662015-06-03 00:27:17175 The default target is some source file that is known to exist and loosely
176 related to |filename|. Compile flags used to build the default target is
177 assumed to be a close-enough approximation for building |filename|.
[email protected]a08a4282013-02-05 23:45:52178
179 Args:
asanka3346ab662015-06-03 00:27:17180 chrome_root: (String) Absolute path to the root of Chromium checkout.
181 filename: (String) Absolute path to the target source file.
[email protected]a08a4282013-02-05 23:45:52182
183 Returns:
asanka3346ab662015-06-03 00:27:17184 (String) Absolute path to substitute target file.
[email protected]a08a4282013-02-05 23:45:52185 """
[email protected]3025ebc42014-07-08 15:37:22186 blink_root = os.path.join(chrome_root, 'third_party', 'WebKit')
asanka3346ab662015-06-03 00:27:17187 if filename.startswith(blink_root):
188 return os.path.join(blink_root, 'Source', 'core', 'Init.cpp')
189 else:
190 return os.path.join(chrome_root, 'base', 'logging.cc')
[email protected]3025ebc42014-07-08 15:37:22191
asanka3346ab662015-06-03 00:27:17192
193def GetBuildTargetForSourceFile(chrome_root, filename):
194 """Returns a build target corresponding to |filename|.
195
196 Args:
197 chrome_root: (String) Absolute path to the root of Chromium checkout.
198 filename: (String) Absolute path to the target source file.
199
200 Returns:
201 (String) Absolute path to build target.
202 """
[email protected]eea749f2013-02-06 19:30:38203 if filename.endswith('.h'):
asanka3346ab662015-06-03 00:27:17204 # Header files can't be built. Instead, try to match a header file to its
205 # corresponding source file.
206 alternates = ['.cc', '.cpp', '.c']
[email protected]eea749f2013-02-06 19:30:38207 for alt_extension in alternates:
208 alt_name = filename[:-2] + alt_extension
209 if os.path.exists(alt_name):
asanka3346ab662015-06-03 00:27:17210 return alt_name
[email protected]eea749f2013-02-06 19:30:38211
asanka3346ab662015-06-03 00:27:17212 # Failing that, build a default file instead and assume that the resulting
213 # commandline options are valid for the .h file.
214 return GetDefaultCppFile(chrome_root, filename)
[email protected]b944e062013-05-08 01:40:55215
asanka3346ab662015-06-03 00:27:17216 return filename
217
218
219def GetClangCommandLineFromNinjaForFilename(out_dir, filename):
220 """Returns the Clang command line for building |filename|
221
222 Asks ninja for the list of commands used to build |filename| and returns the
223 final Clang invocation.
224
225 Args:
226 out_dir: (String) Absolute path to ninja build output directory.
227 filename: (String) Absolute path to source file.
228
229 Returns:
230 (String) Clang command line or None if command line couldn't be determined.
231 """
dzhioev37accc22014-08-29 23:08:21232 # Ninja needs the path to the source file relative to the output build
233 # directory.
dzhioev95e4bfd2014-09-05 14:39:53234 rel_filename = os.path.relpath(os.path.realpath(filename), out_dir)
dzhioev37accc22014-08-29 23:08:21235
[email protected]a08a4282013-02-05 23:45:52236 # Ask ninja how it would build our source file.
[email protected]b944e062013-05-08 01:40:55237 p = subprocess.Popen(['ninja', '-v', '-C', out_dir, '-t',
[email protected]a08a4282013-02-05 23:45:52238 'commands', rel_filename + '^'],
239 stdout=subprocess.PIPE)
240 stdout, stderr = p.communicate()
241 if p.returncode:
asanka3346ab662015-06-03 00:27:17242 return None
[email protected]a08a4282013-02-05 23:45:52243
244 # Ninja might execute several commands to build something. We want the last
245 # clang command.
[email protected]a08a4282013-02-05 23:45:52246 for line in reversed(stdout.split('\n')):
[email protected]eea749f2013-02-06 19:30:38247 if 'clang' in line:
asanka3346ab662015-06-03 00:27:17248 return line
249 return None
250
251
252def GetNormalizedClangCommand(command, out_dir):
253 """Gets the normalized Clang binary path if |command| is a Clang command.
254
255 Args:
256 command: (String) Clang command.
257 out_dir: (String) Absolute path the ninja build directory.
258
259 Returns:
260 (String or None)
261 None : if command is not a clang command.
262 Absolute path to clang binary : if |command| is an absolute or relative
263 path to clang. If relative, it is assumed to be relative to |out_dir|.
264 |command|: if command is a name of a binary.
265 """
266 if command.endswith('clang++') or command.endswith('clang'):
267 if os.path.basename(command) == command:
268 return command
269 return os.path.normpath(os.path.join(out_dir, command))
270 return None
271
272
273def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
274 additional_flags):
275 """Extracts relevant command line options from |clang_commandline|
276
277 Args:
278 clang_commandline: (String) Full Clang invocation.
279 out_dir: (String) Absolute path to ninja build directory. Relative paths in
280 the command line are relative to |out_dir|.
281 additional_flags: (List of String) Additional flags to return.
282
283 Returns:
284 ((List of Strings), (List of Strings)) The first item in the tuple is a list
285 of command line flags for this source file. The second item in the tuple is
286 a list of command line flags that define the system include paths. Either or
287 both can be empty.
288 """
289 chrome_flags = [] + additional_flags
290 system_include_flags = []
[email protected]a08a4282013-02-05 23:45:52291
[email protected]e666f182014-01-09 12:21:37292 # Parse flags that are important for YCM's purposes.
asanka3346ab662015-06-03 00:27:17293 clang_tokens = shlex.split(clang_commandline)
asanka01c39d082015-05-18 20:47:11294 for flag in clang_tokens:
[email protected]a08a4282013-02-05 23:45:52295 if flag.startswith('-I'):
296 # Relative paths need to be resolved, because they're relative to the
297 # output dir, not the source.
298 if flag[2] == '/':
299 chrome_flags.append(flag)
300 else:
[email protected]b944e062013-05-08 01:40:55301 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
[email protected]a08a4282013-02-05 23:45:52302 chrome_flags.append('-I' + abs_path)
[email protected]e666f182014-01-09 12:21:37303 elif flag.startswith('-std'):
304 chrome_flags.append(flag)
[email protected]eea749f2013-02-06 19:30:38305 elif flag.startswith('-') and flag[1] in 'DWFfmO':
[email protected]dc569cf92013-07-12 03:21:12306 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
307 # These flags causes libclang (3.3) to crash. Remove it until things
[email protected]06a4f3e2013-07-09 00:16:49308 # are fixed.
309 continue
[email protected]a08a4282013-02-05 23:45:52310 chrome_flags.append(flag)
311
asanka01c39d082015-05-18 20:47:11312 # Assume that the command for invoking clang++ looks like one of the
313 # following:
314 # 1) /path/to/clang/clang++ arguments
315 # 2) /some/wrapper /path/to/clang++ arguments
316 #
asanka3346ab662015-06-03 00:27:17317 # We'll look at the first two tokens on the command line to see if they look
318 # like Clang commands, and if so use it to determine the system include
319 # directory flags.
asanka01c39d082015-05-18 20:47:11320 for command in clang_tokens[0:2]:
asanka3346ab662015-06-03 00:27:17321 normalized_command = GetNormalizedClangCommand(command, out_dir)
322 if normalized_command:
323 system_include_flags += SystemIncludeDirectoryFlags(normalized_command,
324 chrome_flags)
asanka01c39d082015-05-18 20:47:11325 break
326
asanka3346ab662015-06-03 00:27:17327 return (chrome_flags, system_include_flags)
328
329
330def GetClangOptionsFromNinjaForFilename(chrome_root, filename):
331 """Returns the Clang command line options needed for building |filename|.
332
333 Command line options are based on the command used by ninja for building
334 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
335 If a suitable companion file can't be located or if ninja doesn't know about
336 |filename|, then uses default source files in Blink and Chromium for
337 determining the commandline.
338
339 Args:
340 chrome_root: (String) Path to src/.
341 filename: (String) Absolute path to source file being edited.
342
343 Returns:
344 ((List of Strings), (List of Strings)) The first item in the tuple is a list
345 of command line flags for this source file. The second item in the tuple is
346 a list of command line flags that define the system include paths. Either or
347 both can be empty.
348 """
349 if not chrome_root:
350 return ([],[])
351
352 # Generally, everyone benefits from including Chromium's src/, because all of
353 # Chromium's includes are relative to that.
354 additional_flags = ['-I' + os.path.join(chrome_root)]
355
356 # Version of Clang used to compile Chromium can be newer then version of
357 # libclang that YCM uses for completion. So it's possible that YCM's libclang
358 # doesn't know about some used warning options, which causes compilation
359 # warnings (and errors, because of '-Werror');
360 additional_flags.append('-Wno-unknown-warning-option')
361
362 sys.path.append(os.path.join(chrome_root, 'tools', 'vim'))
363 from ninja_output import GetNinjaOutputDirectory
364 out_dir = os.path.realpath(GetNinjaOutputDirectory(chrome_root))
365
366 clang_line = GetClangCommandLineFromNinjaForFilename(
367 out_dir, GetBuildTargetForSourceFile(chrome_root, filename))
368 if not clang_line:
369 # If ninja didn't know about filename or it's companion files, then try a
370 # default build target. It is possible that the file is new, or build.ninja
371 # is stale.
372 clang_line = GetClangCommandLineFromNinjaForFilename(
373 out_dir, GetDefaultCppFile(chrome_root, filename))
374
375 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
[email protected]a08a4282013-02-05 23:45:52376
377
378def FlagsForFile(filename):
379 """This is the main entry point for YCM. Its interface is fixed.
380
381 Args:
382 filename: (String) Path to source file being edited.
383
384 Returns:
385 (Dictionary)
386 'flags': (List of Strings) Command line flags.
387 'do_cache': (Boolean) True if the result should be cached.
388 """
asanka3346ab662015-06-03 00:27:17389 abs_filename = os.path.abspath(filename)
390 chrome_root = FindChromeSrcFromFilename(abs_filename)
391 (chrome_flags, system_include_flags) = GetClangOptionsFromNinjaForFilename(
392 chrome_root, abs_filename)
393
394 # If either chrome_flags or system_include_flags could not be determined, then
395 # assume that was due to a transient failure. Preventing YCM from caching the
396 # flags allows us to try to determine the flags again.
397 should_cache_flags_for_file = \
398 bool(chrome_flags) and bool(system_include_flags)
399
400 if not system_include_flags:
401 system_include_flags = FallbackSystemIncludeDirectoryFlags()
402 final_flags = _default_flags + chrome_flags + system_include_flags
[email protected]a08a4282013-02-05 23:45:52403
[email protected]a08a4282013-02-05 23:45:52404 return {
405 'flags': final_flags,
asanka3346ab662015-06-03 00:27:17406 'do_cache': should_cache_flags_for_file
[email protected]a08a4282013-02-05 23:45:52407 }