blob: 12a09ed92465d20b5ee97bb8b2e0bcf0acfeaad3 [file] [log] [blame]
deadbeefdc9200e2017-03-02 21:48:39 -08001# Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
2#
3# Use of this source code is governed by a BSD-style license
4# that can be found in the LICENSE file in the root of the source
5# tree. An additional intellectual property rights grant can be found
6# in the file PATENTS. All contributing project authors may
7# be found in the AUTHORS file in the root of the source tree.
8
9# Autocompletion config for YouCompleteMe in WebRTC. This is just copied from
10# tools/vim in chromium with very minor modifications.
11#
12# USAGE:
13#
14# 1. Install YCM [https://github.com/Valloric/YouCompleteMe]
15# (Googlers should check out [go/ycm])
16#
17# 2. Create a symbolic link to this file called .ycm_extra_conf.py in the
18# directory above your WebRTC checkout (i.e. next to your .gclient file).
19#
20# cd src
Henrik Kjellander90fd7d82017-05-09 08:30:10 +020021# ln -rs tools_webrtc/vim/webrtc.ycm_extra_conf.py \
deadbeefdc9200e2017-03-02 21:48:39 -080022# ../.ycm_extra_conf.py
23#
24# 3. (optional) Whitelist the .ycm_extra_conf.py from step #2 by adding the
25# following to your .vimrc:
26#
27# let g:ycm_extra_conf_globlist=['<path to .ycm_extra_conf.py>']
28#
29# You can also add other .ycm_extra_conf.py files you want to use to this
30# list to prevent excessive prompting each time you visit a directory
31# covered by a config file.
32#
33# 4. Profit
34#
35#
36# Usage notes:
37#
38# * You must use ninja & clang to build WebRTC.
39#
40# * You must have run "gn gen" and built WebRTC recently.
41#
42#
43# Hacking notes:
44#
45# * The purpose of this script is to construct an accurate enough command line
46# for YCM to pass to clang so it can build and extract the symbols.
47#
48# * Right now, we only pull the -I and -D flags. That seems to be sufficient
49# for everything I've used it for.
50#
51# * That whole ninja & clang thing? We could support other configs if someone
52# were willing to write the correct commands and a parser.
53#
54# * This has only been tested on gPrecise.
55
deadbeefdc9200e2017-03-02 21:48:39 -080056import os
57import os.path
58import shlex
59import subprocess
60import sys
61
62# Flags from YCM's default config.
kjellanderdd460e22017-04-12 12:06:13 -070063_DEFAULT_FLAGS = [
Mirko Bonadei8cc66952020-10-30 10:13:45 +010064 '-DUSE_CLANG_COMPLETER',
65 '-std=c++11',
66 '-x',
67 'c++',
deadbeefdc9200e2017-03-02 21:48:39 -080068]
69
kjellanderdd460e22017-04-12 12:06:13 -070070_HEADER_ALTERNATES = ('.cc', '.cpp', '.c', '.mm', '.m')
deadbeefdc9200e2017-03-02 21:48:39 -080071
kjellanderdd460e22017-04-12 12:06:13 -070072_EXTENSION_FLAGS = {
Mirko Bonadei8cc66952020-10-30 10:13:45 +010073 '.m': ['-x', 'objective-c'],
74 '.mm': ['-x', 'objective-c++'],
deadbeefdc9200e2017-03-02 21:48:39 -080075}
76
Mirko Bonadei8cc66952020-10-30 10:13:45 +010077
deadbeefdc9200e2017-03-02 21:48:39 -080078def PathExists(*args):
Mirko Bonadei8cc66952020-10-30 10:13:45 +010079 return os.path.exists(os.path.join(*args))
deadbeefdc9200e2017-03-02 21:48:39 -080080
81
82def FindWebrtcSrcFromFilename(filename):
Mirko Bonadei8cc66952020-10-30 10:13:45 +010083 """Searches for the root of the WebRTC checkout.
deadbeefdc9200e2017-03-02 21:48:39 -080084
85 Simply checks parent directories until it finds .gclient and src/.
86
87 Args:
88 filename: (String) Path to source file being edited.
89
90 Returns:
91 (String) Path of 'src/', or None if unable to find.
92 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +010093 curdir = os.path.normpath(os.path.dirname(filename))
94 while not (os.path.basename(curdir) == 'src'
95 and PathExists(curdir, 'DEPS') and
96 (PathExists(curdir, '..', '.gclient')
97 or PathExists(curdir, '.git'))):
98 nextdir = os.path.normpath(os.path.join(curdir, '..'))
99 if nextdir == curdir:
100 return None
101 curdir = nextdir
102 return curdir
deadbeefdc9200e2017-03-02 21:48:39 -0800103
104
105def GetDefaultSourceFile(webrtc_root, filename):
Artem Titovfad54cb2021-07-27 12:58:56 +0200106 """Returns the default source file to use as an alternative to `filename`.
deadbeefdc9200e2017-03-02 21:48:39 -0800107
108 Compile flags used to build the default source file is assumed to be a
Artem Titovfad54cb2021-07-27 12:58:56 +0200109 close-enough approximation for building `filename`.
deadbeefdc9200e2017-03-02 21:48:39 -0800110
111 Args:
112 webrtc_root: (String) Absolute path to the root of WebRTC checkout.
113 filename: (String) Absolute path to the source file.
114
115 Returns:
116 (String) Absolute path to substitute source file.
117 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100118 if 'test.' in filename:
119 return os.path.join(webrtc_root, 'base', 'logging_unittest.cc')
120 return os.path.join(webrtc_root, 'base', 'logging.cc')
deadbeefdc9200e2017-03-02 21:48:39 -0800121
122
123def GetNinjaBuildOutputsForSourceFile(out_dir, filename):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100124 """Returns a list of build outputs for filename.
deadbeefdc9200e2017-03-02 21:48:39 -0800125
126 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
Artem Titovfad54cb2021-07-27 12:58:56 +0200127 inputs and outputs of `filename`. This list is then filtered to only include
deadbeefdc9200e2017-03-02 21:48:39 -0800128 .o and .obj outputs.
129
130 Args:
131 out_dir: (String) Absolute path to ninja build output directory.
132 filename: (String) Absolute path to source file.
133
134 Returns:
Artem Titovfad54cb2021-07-27 12:58:56 +0200135 (List of Strings) List of target names. Will return [] if `filename` doesn't
deadbeefdc9200e2017-03-02 21:48:39 -0800136 yield any .o or .obj outputs.
137 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100138 # Ninja needs the path to the source file relative to the output build
139 # directory.
140 rel_filename = os.path.relpath(filename, out_dir)
deadbeefdc9200e2017-03-02 21:48:39 -0800141
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100142 p = subprocess.Popen(['ninja', '-C', out_dir, '-t', 'query', rel_filename],
143 stdout=subprocess.PIPE,
144 stderr=subprocess.STDOUT,
145 universal_newlines=True)
146 stdout, _ = p.communicate()
147 if p.returncode != 0:
148 return []
deadbeefdc9200e2017-03-02 21:48:39 -0800149
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100150 # The output looks like:
151 # ../../relative/path/to/source.cc:
152 # outputs:
153 # obj/reative/path/to/target.source.o
154 # obj/some/other/target2.source.o
155 # another/target.txt
156 #
157 outputs_text = stdout.partition('\n outputs:\n')[2]
158 output_lines = [line.strip() for line in outputs_text.split('\n')]
159 return [
160 target for target in output_lines
161 if target and (target.endswith('.o') or target.endswith('.obj'))
162 ]
deadbeefdc9200e2017-03-02 21:48:39 -0800163
164
165def GetClangCommandLineForNinjaOutput(out_dir, build_target):
Artem Titovfad54cb2021-07-27 12:58:56 +0200166 """Returns the Clang command line for building `build_target`
deadbeefdc9200e2017-03-02 21:48:39 -0800167
Artem Titovfad54cb2021-07-27 12:58:56 +0200168 Asks ninja for the list of commands used to build `filename` and returns the
deadbeefdc9200e2017-03-02 21:48:39 -0800169 final Clang invocation.
170
171 Args:
172 out_dir: (String) Absolute path to ninja build output directory.
173 build_target: (String) A build target understood by ninja
174
175 Returns:
176 (String or None) Clang command line or None if a Clang command line couldn't
177 be determined.
178 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100179 p = subprocess.Popen(
180 ['ninja', '-v', '-C', out_dir, '-t', 'commands', build_target],
181 stdout=subprocess.PIPE,
182 universal_newlines=True)
183 stdout, _ = p.communicate()
184 if p.returncode != 0:
185 return None
deadbeefdc9200e2017-03-02 21:48:39 -0800186
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100187 # Ninja will return multiple build steps for all dependencies up to
Artem Titovfad54cb2021-07-27 12:58:56 +0200188 # `build_target`. The build step we want is the last Clang invocation, which
189 # is expected to be the one that outputs `build_target`.
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100190 for line in reversed(stdout.split('\n')):
191 if 'clang' in line:
192 return line
193 return None
deadbeefdc9200e2017-03-02 21:48:39 -0800194
195
196def GetClangCommandLineFromNinjaForSource(out_dir, filename):
Artem Titovfad54cb2021-07-27 12:58:56 +0200197 """Returns a Clang command line used to build `filename`.
deadbeefdc9200e2017-03-02 21:48:39 -0800198
199 The same source file could be built multiple times using different tool
200 chains. In such cases, this command returns the first Clang invocation. We
201 currently don't prefer one toolchain over another. Hopefully the tool chain
202 corresponding to the Clang command line is compatible with the Clang build
203 used by YCM.
204
205 Args:
206 out_dir: (String) Absolute path to WebRTC checkout.
207 filename: (String) Absolute path to source file.
208
209 Returns:
Artem Titovfad54cb2021-07-27 12:58:56 +0200210 (String or None): Command line for Clang invocation using `filename` as a
deadbeefdc9200e2017-03-02 21:48:39 -0800211 source. Returns None if no such command line could be found.
212 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100213 build_targets = GetNinjaBuildOutputsForSourceFile(out_dir, filename)
214 for build_target in build_targets:
215 command_line = GetClangCommandLineForNinjaOutput(out_dir, build_target)
216 if command_line:
217 return command_line
218 return None
deadbeefdc9200e2017-03-02 21:48:39 -0800219
220
221def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
222 additional_flags):
Artem Titovfad54cb2021-07-27 12:58:56 +0200223 """Extracts relevant command line options from `clang_commandline`
deadbeefdc9200e2017-03-02 21:48:39 -0800224
225 Args:
226 clang_commandline: (String) Full Clang invocation.
227 out_dir: (String) Absolute path to ninja build directory. Relative paths in
Artem Titovfad54cb2021-07-27 12:58:56 +0200228 the command line are relative to `out_dir`.
deadbeefdc9200e2017-03-02 21:48:39 -0800229 additional_flags: (List of String) Additional flags to return.
230
231 Returns:
232 (List of Strings) The list of command line flags for this source file. Can
233 be empty.
234 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100235 clang_flags = [] + additional_flags
deadbeefdc9200e2017-03-02 21:48:39 -0800236
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100237 # Parse flags that are important for YCM's purposes.
238 clang_tokens = shlex.split(clang_commandline)
239 for flag_index, flag in enumerate(clang_tokens):
240 if flag.startswith('-I'):
Artem Titovfad54cb2021-07-27 12:58:56 +0200241 # Relative paths need to be resolved, because they're relative to
242 # the output dir, not the source.
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100243 if flag[2] == '/':
244 clang_flags.append(flag)
245 else:
246 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
247 clang_flags.append('-I' + abs_path)
248 elif flag.startswith('-std'):
249 clang_flags.append(flag)
250 elif flag.startswith('-') and flag[1] in 'DWFfmO':
Artem Titovfad54cb2021-07-27 12:58:56 +0200251 if (flag == '-Wno-deprecated-register' or
252 flag == '-Wno-header-guard'):
253 # These flags causes libclang (3.3) to crash. Remove it until
254 # things are fixed.
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100255 continue
256 clang_flags.append(flag)
257 elif flag == '-isysroot':
258 # On Mac -isysroot <path> is used to find the system headers.
259 # Copy over both flags.
260 if flag_index + 1 < len(clang_tokens):
261 clang_flags.append(flag)
262 clang_flags.append(clang_tokens[flag_index + 1])
263 elif flag.startswith('--sysroot='):
264 # On Linux we use a sysroot image.
265 sysroot_path = flag.lstrip('--sysroot=')
266 if sysroot_path.startswith('/'):
267 clang_flags.append(flag)
268 else:
269 abs_path = os.path.normpath(os.path.join(
270 out_dir, sysroot_path))
271 clang_flags.append('--sysroot=' + abs_path)
272 return clang_flags
deadbeefdc9200e2017-03-02 21:48:39 -0800273
274
275def GetClangOptionsFromNinjaForFilename(webrtc_root, filename):
Artem Titovfad54cb2021-07-27 12:58:56 +0200276 """Returns the Clang command line options needed for building `filename`.
deadbeefdc9200e2017-03-02 21:48:39 -0800277
278 Command line options are based on the command used by ninja for building
Artem Titovfad54cb2021-07-27 12:58:56 +0200279 `filename`. If `filename` is a .h file, uses its companion .cc or .cpp file.
deadbeefdc9200e2017-03-02 21:48:39 -0800280 If a suitable companion file can't be located or if ninja doesn't know about
Artem Titovfad54cb2021-07-27 12:58:56 +0200281 `filename`, then uses default source files in WebRTC for determining the
deadbeefdc9200e2017-03-02 21:48:39 -0800282 commandline.
283
284 Args:
285 webrtc_root: (String) Path to src/.
286 filename: (String) Absolute path to source file being edited.
287
288 Returns:
289 (List of Strings) The list of command line flags for this source file. Can
290 be empty.
291 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100292 if not webrtc_root:
293 return []
deadbeefdc9200e2017-03-02 21:48:39 -0800294
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100295 # Generally, everyone benefits from including WebRTC's src/, because all of
296 # WebRTC's includes are relative to that.
297 additional_flags = ['-I' + os.path.join(webrtc_root)]
deadbeefdc9200e2017-03-02 21:48:39 -0800298
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100299 # Version of Clang used to compile WebRTC can be newer then version of
Artem Titovfad54cb2021-07-27 12:58:56 +0200300 # libclang that YCM uses for completion. So it's possible that YCM's
301 # libclang doesn't know about some used warning options, which causes
302 # compilation warnings (and errors, because of '-Werror');
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100303 additional_flags.append('-Wno-unknown-warning-option')
deadbeefdc9200e2017-03-02 21:48:39 -0800304
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100305 sys.path.append(os.path.join(webrtc_root, 'tools', 'vim'))
306 from ninja_output import GetNinjaOutputDirectory
307 out_dir = GetNinjaOutputDirectory(webrtc_root)
deadbeefdc9200e2017-03-02 21:48:39 -0800308
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100309 basename, extension = os.path.splitext(filename)
310 if extension == '.h':
311 candidates = [basename + ext for ext in _HEADER_ALTERNATES]
312 else:
313 candidates = [filename]
deadbeefdc9200e2017-03-02 21:48:39 -0800314
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100315 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
deadbeefdc9200e2017-03-02 21:48:39 -0800322
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100323 additional_flags += _EXTENSION_FLAGS.get(buildable_extension, [])
deadbeefdc9200e2017-03-02 21:48:39 -0800324
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100325 if not clang_line:
Artem Titovfad54cb2021-07-27 12:58:56 +0200326 # If ninja didn't know about filename or it's companion files, then try
327 # a default build target. It is possible that the file is new, or
328 # build.ninja is stale.
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100329 clang_line = GetClangCommandLineFromNinjaForSource(
330 out_dir, GetDefaultSourceFile(webrtc_root, filename))
deadbeefdc9200e2017-03-02 21:48:39 -0800331
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100332 if not clang_line:
333 return additional_flags
deadbeefdc9200e2017-03-02 21:48:39 -0800334
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100335 return GetClangOptionsFromCommandLine(clang_line, out_dir,
336 additional_flags)
deadbeefdc9200e2017-03-02 21:48:39 -0800337
338
339def FlagsForFile(filename):
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100340 """This is the main entry point for YCM. Its interface is fixed.
deadbeefdc9200e2017-03-02 21:48:39 -0800341
342 Args:
343 filename: (String) Path to source file being edited.
344
345 Returns:
346 (Dictionary)
347 'flags': (List of Strings) Command line flags.
348 'do_cache': (Boolean) True if the result should be cached.
349 """
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100350 abs_filename = os.path.abspath(filename)
351 webrtc_root = FindWebrtcSrcFromFilename(abs_filename)
352 clang_flags = GetClangOptionsFromNinjaForFilename(webrtc_root,
353 abs_filename)
deadbeefdc9200e2017-03-02 21:48:39 -0800354
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100355 # If clang_flags could not be determined, then assume that was due to a
Artem Titovfad54cb2021-07-27 12:58:56 +0200356 # transient failure. Preventing YCM from caching the flags allows us to
357 # try to determine the flags again.
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100358 should_cache_flags_for_file = bool(clang_flags)
deadbeefdc9200e2017-03-02 21:48:39 -0800359
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100360 final_flags = _DEFAULT_FLAGS + clang_flags
deadbeefdc9200e2017-03-02 21:48:39 -0800361
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100362 return {'flags': final_flags, 'do_cache': should_cache_flags_for_file}