Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(36)

Side by Side Diff: tools-webrtc/vim/webrtc.ycm_extra_conf.py

Issue 2725233002: Adding YouCompleteMe config for WebRTC. (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # 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
21 # ln -rs tools-webrtc/vim/webrtc.ycm_extra_conf.py \
22 # ../.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
56
57 import os
58 import os.path
59 import shlex
60 import subprocess
61 import sys
62
63 # Flags from YCM's default config.
64 _default_flags = [
65 '-DUSE_CLANG_COMPLETER',
66 '-std=c++11',
67 '-x',
68 'c++',
69 ]
70
71 _header_alternates = ('.cc', '.cpp', '.c', '.mm', '.m')
72
73 _extension_flags = {
74 '.m': ['-x', 'objective-c'],
75 '.mm': ['-x', 'objective-c++'],
76 }
77
78 def PathExists(*args):
79 return os.path.exists(os.path.join(*args))
80
81
82 def FindWebrtcSrcFromFilename(filename):
83 """Searches for the root of the WebRTC checkout.
84
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 """
93 curdir = os.path.normpath(os.path.dirname(filename))
94 while not (os.path.basename(curdir) == 'src'
95 and PathExists(curdir, 'DEPS')
96 and (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
103
104
105 def GetDefaultSourceFile(webrtc_root, filename):
106 """Returns the default source file to use as an alternative to |filename|.
107
108 Compile flags used to build the default source file is assumed to be a
109 close-enough approximation for building |filename|.
110
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 """
118 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')
121
122
123 def GetNinjaBuildOutputsForSourceFile(out_dir, filename):
124 """Returns a list of build outputs for filename.
125
126 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
127 inputs and outputs of |filename|. This list is then filtered to only include
128 .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:
135 (List of Strings) List of target names. Will return [] if |filename| doesn't
136 yield any .o or .obj outputs.
137 """
138 # Ninja needs the path to the source file relative to the output build
139 # directory.
140 rel_filename = os.path.relpath(filename, out_dir)
141
142 p = subprocess.Popen(['ninja', '-C', out_dir, '-t', 'query', rel_filename],
143 stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
144 universal_newlines=True)
145 stdout, _ = p.communicate()
146 if p.returncode != 0:
147 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')]
158 return [target for target in output_lines
159 if target and (target.endswith('.o') or target.endswith('.obj'))]
160
161
162 def GetClangCommandLineForNinjaOutput(out_dir, build_target):
163 """Returns the Clang command line for building |build_target|
164
165 Asks ninja for the list of commands used to build |filename| and returns the
166 final Clang invocation.
167
168 Args:
169 out_dir: (String) Absolute path to ninja build output directory.
170 build_target: (String) A build target understood by ninja
171
172 Returns:
173 (String or None) Clang command line or None if a Clang command line couldn't
174 be determined.
175 """
176 p = subprocess.Popen(['ninja', '-v', '-C', out_dir,
177 '-t', 'commands', build_target],
178 stdout=subprocess.PIPE, universal_newlines=True)
179 stdout, _ = p.communicate()
180 if p.returncode != 0:
181 return None
182
183 # Ninja will return multiple build steps for all dependencies up to
184 # |build_target|. The build step we want is the last Clang invocation, which
185 # is expected to be the one that outputs |build_target|.
186 for line in reversed(stdout.split('\n')):
187 if 'clang' in line:
188 return line
189 return None
190
191
192 def GetClangCommandLineFromNinjaForSource(out_dir, filename):
193 """Returns a Clang command line used to build |filename|.
194
195 The same source file could be built multiple times using different tool
196 chains. In such cases, this command returns the first Clang invocation. We
197 currently don't prefer one toolchain over another. Hopefully the tool chain
198 corresponding to the Clang command line is compatible with the Clang build
199 used by YCM.
200
201 Args:
202 out_dir: (String) Absolute path to WebRTC checkout.
203 filename: (String) Absolute path to source file.
204
205 Returns:
206 (String or None): Command line for Clang invocation using |filename| as a
207 source. Returns None if no such command line could be found.
208 """
209 build_targets = GetNinjaBuildOutputsForSourceFile(out_dir, filename)
210 for build_target in build_targets:
211 command_line = GetClangCommandLineForNinjaOutput(out_dir, build_target)
212 if command_line:
213 return command_line
214 return None
215
216
217 def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
218 additional_flags):
219 """Extracts relevant command line options from |clang_commandline|
220
221 Args:
222 clang_commandline: (String) Full Clang invocation.
223 out_dir: (String) Absolute path to ninja build directory. Relative paths in
224 the command line are relative to |out_dir|.
225 additional_flags: (List of String) Additional flags to return.
226
227 Returns:
228 (List of Strings) The list of command line flags for this source file. Can
229 be empty.
230 """
231 clang_flags = [] + additional_flags
232
233 # Parse flags that are important for YCM's purposes.
234 clang_tokens = shlex.split(clang_commandline)
235 for flag_index, flag in enumerate(clang_tokens):
236 if flag.startswith('-I'):
237 # Relative paths need to be resolved, because they're relative to the
238 # output dir, not the source.
239 if flag[2] == '/':
240 clang_flags.append(flag)
241 else:
242 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
243 clang_flags.append('-I' + abs_path)
244 elif flag.startswith('-std'):
245 clang_flags.append(flag)
246 elif flag.startswith('-') and flag[1] in 'DWFfmO':
247 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
248 # These flags causes libclang (3.3) to crash. Remove it until things
249 # are fixed.
250 continue
251 clang_flags.append(flag)
252 elif flag == '-isysroot':
253 # On Mac -isysroot <path> is used to find the system headers.
254 # Copy over both flags.
255 if flag_index + 1 < len(clang_tokens):
256 clang_flags.append(flag)
257 clang_flags.append(clang_tokens[flag_index + 1])
258 elif flag.startswith('--sysroot='):
259 # On Linux we use a sysroot image.
260 sysroot_path = flag.lstrip('--sysroot=')
261 if sysroot_path.startswith('/'):
262 clang_flags.append(flag)
263 else:
264 abs_path = os.path.normpath(os.path.join(out_dir, sysroot_path))
265 clang_flags.append('--sysroot=' + abs_path)
266 return clang_flags
267
268
269 def GetClangOptionsFromNinjaForFilename(webrtc_root, filename):
270 """Returns the Clang command line options needed for building |filename|.
271
272 Command line options are based on the command used by ninja for building
273 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
274 If a suitable companion file can't be located or if ninja doesn't know about
275 |filename|, then uses default source files in WebRTC for determining the
276 commandline.
277
278 Args:
279 webrtc_root: (String) Path to src/.
280 filename: (String) Absolute path to source file being edited.
281
282 Returns:
283 (List of Strings) The list of command line flags for this source file. Can
284 be empty.
285 """
286 if not webrtc_root:
287 return []
288
289 # Generally, everyone benefits from including WebRTC's src/, because all of
290 # WebRTC's includes are relative to that.
291 additional_flags = ['-I' + os.path.join(webrtc_root)]
292
293 # Version of Clang used to compile WebRTC can be newer then version of
294 # libclang that YCM uses for completion. So it's possible that YCM's libclang
295 # doesn't know about some used warning options, which causes compilation
296 # warnings (and errors, because of '-Werror');
297 additional_flags.append('-Wno-unknown-warning-option')
298
299 sys.path.append(os.path.join(webrtc_root, 'tools', 'vim'))
300 from ninja_output import GetNinjaOutputDirectory
301 out_dir = GetNinjaOutputDirectory(webrtc_root)
302
303 basename, extension = os.path.splitext(filename)
304 if extension == '.h':
305 candidates = [basename + ext for ext in _header_alternates]
306 else:
307 candidates = [filename]
308
309 clang_line = None
310 buildable_extension = extension
311 for candidate in candidates:
312 clang_line = GetClangCommandLineFromNinjaForSource(out_dir, candidate)
313 if clang_line:
314 buildable_extension = os.path.splitext(candidate)[1]
315 break
316
317 additional_flags += _extension_flags.get(buildable_extension, [])
318
319 if not clang_line:
320 # If ninja didn't know about filename or it's companion files, then try a
321 # default build target. It is possible that the file is new, or build.ninja
322 # is stale.
323 clang_line = GetClangCommandLineFromNinjaForSource(
324 out_dir, GetDefaultSourceFile(webrtc_root, filename))
325
326 if not clang_line:
327 return additional_flags
328
329 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
330
331
332 def FlagsForFile(filename):
333 """This is the main entry point for YCM. Its interface is fixed.
334
335 Args:
336 filename: (String) Path to source file being edited.
337
338 Returns:
339 (Dictionary)
340 'flags': (List of Strings) Command line flags.
341 'do_cache': (Boolean) True if the result should be cached.
342 """
343 abs_filename = os.path.abspath(filename)
344 webrtc_root = FindWebrtcSrcFromFilename(abs_filename)
345 clang_flags = GetClangOptionsFromNinjaForFilename(webrtc_root, abs_filename)
346
347 # If clang_flags could not be determined, then assume that was due to a
348 # transient failure. Preventing YCM from caching the flags allows us to try to
349 # determine the flags again.
350 should_cache_flags_for_file = bool(clang_flags)
351
352 final_flags = _default_flags + clang_flags
353
354 return {
355 'flags': final_flags,
356 'do_cache': should_cache_flags_for_file
357 }
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698