blob: e5ff5310d897e3dc9b6c9cf5b5cdcbd724d65290 [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#
Victor Costan3c2db7d2017-08-17 20:28:1434# * You must have built Chromium recently.
[email protected]a08a4282013-02-05 23:45:5235#
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#
Sidney San Martín326281f2017-06-09 17:01:2848# * This has only been tested on Linux and macOS.
[email protected]a08a4282013-02-05 23:45:5249
[email protected]a08a4282013-02-05 23:45:5250import os
dzhioev37accc22014-08-29 23:08:2151import os.path
johnme8d6edba2015-01-21 14:26:2852import re
asanka01c39d082015-05-18 20:47:1153import shlex
[email protected]a08a4282013-02-05 23:45:5254import subprocess
rousland026bc92014-10-29 17:54:0155import sys
[email protected]a08a4282013-02-05 23:45:5256
57# Flags from YCM's default config.
asanka01c39d082015-05-18 20:47:1158_default_flags = [
asankac68cb712017-03-11 17:53:5359 '-DUSE_CLANG_COMPLETER',
Victor Costan3c2db7d2017-08-17 20:28:1460 '-std=c++14',
asankac68cb712017-03-11 17:53:5361 '-x',
62 'c++',
[email protected]a08a4282013-02-05 23:45:5263]
64
sdyd6a69822016-11-07 19:52:3465_header_alternates = ('.cc', '.cpp', '.c', '.mm', '.m')
66
sdyde05ce92016-09-09 22:19:1467_extension_flags = {
asankac68cb712017-03-11 17:53:5368 '.m': ['-x', 'objective-c'],
69 '.mm': ['-x', 'objective-c++'],
sdyde05ce92016-09-09 22:19:1470}
asanka3346ab662015-06-03 00:27:1771
asankac68cb712017-03-11 17:53:5372
[email protected]1cab75682013-04-24 19:50:4973def PathExists(*args):
74 return os.path.exists(os.path.join(*args))
75
76
[email protected]a08a4282013-02-05 23:45:5277def FindChromeSrcFromFilename(filename):
78 """Searches for the root of the Chromium checkout.
79
80 Simply checks parent directories until it finds .gclient and src/.
81
82 Args:
83 filename: (String) Path to source file being edited.
84
85 Returns:
86 (String) Path of 'src/', or None if unable to find.
87 """
88 curdir = os.path.normpath(os.path.dirname(filename))
asankac68cb712017-03-11 17:53:5389 while not (
90 os.path.basename(curdir) == 'src' and PathExists(curdir, 'DEPS') and
91 (PathExists(curdir, '..', '.gclient') or PathExists(curdir, '.git'))):
[email protected]a08a4282013-02-05 23:45:5292 nextdir = os.path.normpath(os.path.join(curdir, '..'))
93 if nextdir == curdir:
94 return None
95 curdir = nextdir
johnme956993f2015-01-21 14:29:4396 return curdir
[email protected]a08a4282013-02-05 23:45:5297
98
asanka234b8d22015-06-26 22:40:2399def GetDefaultSourceFile(chrome_root, filename):
100 """Returns the default source file to use as an alternative to |filename|.
[email protected]a08a4282013-02-05 23:45:52101
asanka234b8d22015-06-26 22:40:23102 Compile flags used to build the default source file is assumed to be a
103 close-enough approximation for building |filename|.
[email protected]a08a4282013-02-05 23:45:52104
105 Args:
asanka3346ab662015-06-03 00:27:17106 chrome_root: (String) Absolute path to the root of Chromium checkout.
asanka234b8d22015-06-26 22:40:23107 filename: (String) Absolute path to the source file.
[email protected]a08a4282013-02-05 23:45:52108
109 Returns:
asanka234b8d22015-06-26 22:40:23110 (String) Absolute path to substitute source file.
[email protected]a08a4282013-02-05 23:45:52111 """
[email protected]3025ebc42014-07-08 15:37:22112 blink_root = os.path.join(chrome_root, 'third_party', 'WebKit')
asanka3346ab662015-06-03 00:27:17113 if filename.startswith(blink_root):
Victor Costan3c2db7d2017-08-17 20:28:14114 return os.path.join(blink_root, 'Source', 'core', 'CoreInitializer.cpp')
asanka3346ab662015-06-03 00:27:17115 else:
asanka7dc195b12016-06-02 15:11:09116 if 'test.' in filename:
117 return os.path.join(chrome_root, 'base', 'logging_unittest.cc')
asanka3346ab662015-06-03 00:27:17118 return os.path.join(chrome_root, 'base', 'logging.cc')
[email protected]3025ebc42014-07-08 15:37:22119
asanka3346ab662015-06-03 00:27:17120
asanka234b8d22015-06-26 22:40:23121def GetNinjaBuildOutputsForSourceFile(out_dir, filename):
122 """Returns a list of build outputs for filename.
asanka3346ab662015-06-03 00:27:17123
asanka234b8d22015-06-26 22:40:23124 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
125 inputs and outputs of |filename|. This list is then filtered to only include
126 .o and .obj outputs.
asanka3346ab662015-06-03 00:27:17127
128 Args:
129 out_dir: (String) Absolute path to ninja build output directory.
130 filename: (String) Absolute path to source file.
131
132 Returns:
asanka234b8d22015-06-26 22:40:23133 (List of Strings) List of target names. Will return [] if |filename| doesn't
134 yield any .o or .obj outputs.
asanka3346ab662015-06-03 00:27:17135 """
dzhioev37accc22014-08-29 23:08:21136 # Ninja needs the path to the source file relative to the output build
137 # directory.
sdy50d51772016-09-09 21:36:50138 rel_filename = os.path.relpath(filename, out_dir)
dzhioev37accc22014-08-29 23:08:21139
asankac68cb712017-03-11 17:53:53140 p = subprocess.Popen(
141 ['ninja', '-C', out_dir, '-t', 'query', rel_filename],
142 stdout=subprocess.PIPE,
143 stderr=subprocess.STDOUT,
144 universal_newlines=True)
asanka234b8d22015-06-26 22:40:23145 stdout, _ = p.communicate()
pastarmovj7e3be85f2016-06-07 17:53:58146 if p.returncode != 0:
asanka234b8d22015-06-26 22:40:23147 return []
148
149 # The output looks like:
150 # ../../relative/path/to/source.cc:
151 # outputs:
152 # obj/reative/path/to/target.source.o
153 # obj/some/other/target2.source.o
154 # another/target.txt
155 #
156 outputs_text = stdout.partition('\n outputs:\n')[2]
157 output_lines = [line.strip() for line in outputs_text.split('\n')]
asankac68cb712017-03-11 17:53:53158 return [
159 target for target in output_lines
160 if target and (target.endswith('.o') or target.endswith('.obj'))
161 ]
asanka234b8d22015-06-26 22:40:23162
163
164def GetClangCommandLineForNinjaOutput(out_dir, build_target):
165 """Returns the Clang command line for building |build_target|
166
167 Asks ninja for the list of commands used to build |filename| and returns the
168 final Clang invocation.
169
170 Args:
171 out_dir: (String) Absolute path to ninja build output directory.
172 build_target: (String) A build target understood by ninja
173
174 Returns:
175 (String or None) Clang command line or None if a Clang command line couldn't
176 be determined.
177 """
asankac68cb712017-03-11 17:53:53178 p = subprocess.Popen(
179 ['ninja', '-v', '-C', out_dir, '-t', 'commands', build_target],
180 stdout=subprocess.PIPE,
181 universal_newlines=True)
[email protected]a08a4282013-02-05 23:45:52182 stdout, stderr = p.communicate()
pastarmovj7e3be85f2016-06-07 17:53:58183 if p.returncode != 0:
asanka3346ab662015-06-03 00:27:17184 return None
[email protected]a08a4282013-02-05 23:45:52185
asanka234b8d22015-06-26 22:40:23186 # Ninja will return multiple build steps for all dependencies up to
187 # |build_target|. The build step we want is the last Clang invocation, which
188 # is expected to be the one that outputs |build_target|.
[email protected]a08a4282013-02-05 23:45:52189 for line in reversed(stdout.split('\n')):
[email protected]eea749f2013-02-06 19:30:38190 if 'clang' in line:
asanka3346ab662015-06-03 00:27:17191 return line
192 return None
193
194
asanka234b8d22015-06-26 22:40:23195def GetClangCommandLineFromNinjaForSource(out_dir, filename):
196 """Returns a Clang command line used to build |filename|.
197
198 The same source file could be built multiple times using different tool
199 chains. In such cases, this command returns the first Clang invocation. We
200 currently don't prefer one toolchain over another. Hopefully the tool chain
201 corresponding to the Clang command line is compatible with the Clang build
202 used by YCM.
203
204 Args:
205 out_dir: (String) Absolute path to Chromium checkout.
206 filename: (String) Absolute path to source file.
207
208 Returns:
209 (String or None): Command line for Clang invocation using |filename| as a
210 source. Returns None if no such command line could be found.
211 """
212 build_targets = GetNinjaBuildOutputsForSourceFile(out_dir, filename)
213 for build_target in build_targets:
214 command_line = GetClangCommandLineForNinjaOutput(out_dir, build_target)
215 if command_line:
216 return command_line
217 return None
218
219
asanka3346ab662015-06-03 00:27:17220def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
221 additional_flags):
222 """Extracts relevant command line options from |clang_commandline|
223
224 Args:
225 clang_commandline: (String) Full Clang invocation.
226 out_dir: (String) Absolute path to ninja build directory. Relative paths in
227 the command line are relative to |out_dir|.
228 additional_flags: (List of String) Additional flags to return.
229
230 Returns:
asanka00a5f592015-07-20 19:37:01231 (List of Strings) The list of command line flags for this source file. Can
232 be empty.
asanka3346ab662015-06-03 00:27:17233 """
asanka00a5f592015-07-20 19:37:01234 clang_flags = [] + additional_flags
[email protected]a08a4282013-02-05 23:45:52235
Sidney San Martín326281f2017-06-09 17:01:28236 def abspath(path):
237 return os.path.normpath(os.path.join(out_dir, path))
238
[email protected]e666f182014-01-09 12:21:37239 # Parse flags that are important for YCM's purposes.
asanka3346ab662015-06-03 00:27:17240 clang_tokens = shlex.split(clang_commandline)
Asanka Herathcc05fe72017-08-31 19:01:42241 include_pattern = re.compile(r'^(-I|-isystem)(.+)$')
eromanf184b3a2015-08-13 19:12:38242 for flag_index, flag in enumerate(clang_tokens):
Jeremy Roman807200c2017-08-17 18:51:58243 include_match = include_pattern.match(flag)
244 if include_match:
[email protected]a08a4282013-02-05 23:45:52245 # Relative paths need to be resolved, because they're relative to the
246 # output dir, not the source.
Jeremy Roman807200c2017-08-17 18:51:58247 path = abspath(include_match.group(2))
248 clang_flags.append(include_match.group(1) + path)
249 elif flag.startswith('-std') or flag == '-nostdinc++':
asanka00a5f592015-07-20 19:37:01250 clang_flags.append(flag)
Vitaliy Kharin3f33f812018-10-17 13:51:29251 elif flag.startswith('-march=arm'):
252 # Value armv7-a of this flag causes a parsing error with a message
253 # "ClangParseError: Failed to parse the translation unit."
254 continue
[email protected]eea749f2013-02-06 19:30:38255 elif flag.startswith('-') and flag[1] in 'DWFfmO':
[email protected]dc569cf92013-07-12 03:21:12256 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
257 # These flags causes libclang (3.3) to crash. Remove it until things
[email protected]06a4f3e2013-07-09 00:16:49258 # are fixed.
259 continue
asanka00a5f592015-07-20 19:37:01260 clang_flags.append(flag)
Asanka Herathcc05fe72017-08-31 19:01:42261 elif flag == '-isysroot' or flag == '-isystem' or flag == '-I':
eromanf184b3a2015-08-13 19:12:38262 if flag_index + 1 < len(clang_tokens):
263 clang_flags.append(flag)
Sidney San Martín326281f2017-06-09 17:01:28264 clang_flags.append(abspath(clang_tokens[flag_index + 1]))
sunnyps3a2e1e82016-02-12 18:35:57265 elif flag.startswith('--sysroot='):
266 # On Linux we use a sysroot image.
267 sysroot_path = flag.lstrip('--sysroot=')
268 if sysroot_path.startswith('/'):
269 clang_flags.append(flag)
270 else:
Sidney San Martín326281f2017-06-09 17:01:28271 clang_flags.append('--sysroot=' + abspath(sysroot_path))
asanka00a5f592015-07-20 19:37:01272 return clang_flags
asanka3346ab662015-06-03 00:27:17273
274
275def GetClangOptionsFromNinjaForFilename(chrome_root, filename):
276 """Returns the Clang command line options needed for building |filename|.
277
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.
283
284 Args:
285 chrome_root: (String) Path to src/.
286 filename: (String) Absolute path to source file being edited.
287
288 Returns:
asanka00a5f592015-07-20 19:37:01289 (List of Strings) The list of command line flags for this source file. Can
290 be empty.
asanka3346ab662015-06-03 00:27:17291 """
292 if not chrome_root:
asanka00a5f592015-07-20 19:37:01293 return []
asanka3346ab662015-06-03 00:27:17294
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)]
298
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')
304
305 sys.path.append(os.path.join(chrome_root, 'tools', 'vim'))
306 from ninja_output import GetNinjaOutputDirectory
sdy50d51772016-09-09 21:36:50307 out_dir = GetNinjaOutputDirectory(chrome_root)
asanka3346ab662015-06-03 00:27:17308
sdyd6a69822016-11-07 19:52:34309 basename, extension = os.path.splitext(filename)
310 if extension == '.h':
311 candidates = [basename + ext for ext in _header_alternates]
312 else:
313 candidates = [filename]
314
315 clang_line = None
316 buildable_extension = extension
317 for candidate in candidates:
318 clang_line = GetClangCommandLineFromNinjaForSource(out_dir, candidate)
319 if clang_line:
320 buildable_extension = os.path.splitext(candidate)[1]
321 break
322
323 additional_flags += _extension_flags.get(buildable_extension, [])
324
asanka3346ab662015-06-03 00:27:17325 if not clang_line:
326 # If ninja didn't know about filename or it's companion files, then try a
327 # default build target. It is possible that the file is new, or build.ninja
328 # is stale.
asankac68cb712017-03-11 17:53:53329 clang_line = GetClangCommandLineFromNinjaForSource(out_dir,
330 GetDefaultSourceFile(
331 chrome_root,
332 filename))
asanka234b8d22015-06-26 22:40:23333
334 if not clang_line:
pastarmovj7e3be85f2016-06-07 17:53:58335 return additional_flags
asanka3346ab662015-06-03 00:27:17336
337 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
[email protected]a08a4282013-02-05 23:45:52338
339
Eric Roman803a136c2020-07-13 17:53:22340# FlagsForFile entrypoint is deprecated in YCM and has replaced by
341# Settings.
[email protected]a08a4282013-02-05 23:45:52342def FlagsForFile(filename):
Eric Roman803a136c2020-07-13 17:53:22343 """This is the old entry point for YCM. Its interface is fixed.
[email protected]a08a4282013-02-05 23:45:52344
345 Args:
346 filename: (String) Path to source file being edited.
347
348 Returns:
349 (Dictionary)
350 'flags': (List of Strings) Command line flags.
351 'do_cache': (Boolean) True if the result should be cached.
352 """
Eric Roman803a136c2020-07-13 17:53:22353 return Settings(filename=filename)
354
355
356def Settings(**kwargs):
357 filename = kwargs['filename']
asanka3346ab662015-06-03 00:27:17358 abs_filename = os.path.abspath(filename)
359 chrome_root = FindChromeSrcFromFilename(abs_filename)
asanka00a5f592015-07-20 19:37:01360 clang_flags = GetClangOptionsFromNinjaForFilename(chrome_root, abs_filename)
asanka3346ab662015-06-03 00:27:17361
asanka00a5f592015-07-20 19:37:01362 # If clang_flags could not be determined, then assume that was due to a
363 # transient failure. Preventing YCM from caching the flags allows us to try to
364 # determine the flags again.
365 should_cache_flags_for_file = bool(clang_flags)
asanka3346ab662015-06-03 00:27:17366
sdyd6a69822016-11-07 19:52:34367 final_flags = _default_flags + clang_flags
[email protected]a08a4282013-02-05 23:45:52368
asankac68cb712017-03-11 17:53:53369 return {'flags': final_flags, 'do_cache': should_cache_flags_for_file}