[Ozone-DRM] Add unittest to validate the mode used in mirror mode
[chromium-blink-merge.git] / tools / vim / chromium.ycm_extra_conf.py
blob01bc2cec210cb7d6091474226d49b81f365e321f
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 # Flags from YCM's default config.
59 _default_flags = [
60 '-DUSE_CLANG_COMPLETER',
61 '-std=c++11',
62 '-x',
63 'c++',
67 def PathExists(*args):
68 return os.path.exists(os.path.join(*args))
71 def FindChromeSrcFromFilename(filename):
72 """Searches for the root of the Chromium checkout.
74 Simply checks parent directories until it finds .gclient and src/.
76 Args:
77 filename: (String) Path to source file being edited.
79 Returns:
80 (String) Path of 'src/', or None if unable to find.
81 """
82 curdir = os.path.normpath(os.path.dirname(filename))
83 while not (os.path.basename(os.path.realpath(curdir)) == 'src'
84 and PathExists(curdir, 'DEPS')
85 and (PathExists(curdir, '..', '.gclient')
86 or PathExists(curdir, '.git'))):
87 nextdir = os.path.normpath(os.path.join(curdir, '..'))
88 if nextdir == curdir:
89 return None
90 curdir = nextdir
91 return curdir
94 def GetDefaultSourceFile(chrome_root, filename):
95 """Returns the default source file to use as an alternative to |filename|.
97 Compile flags used to build the default source file is assumed to be a
98 close-enough approximation for building |filename|.
100 Args:
101 chrome_root: (String) Absolute path to the root of Chromium checkout.
102 filename: (String) Absolute path to the source file.
104 Returns:
105 (String) Absolute path to substitute source file.
107 blink_root = os.path.join(chrome_root, 'third_party', 'WebKit')
108 if filename.startswith(blink_root):
109 return os.path.join(blink_root, 'Source', 'core', 'Init.cpp')
110 else:
111 return os.path.join(chrome_root, 'base', 'logging.cc')
114 def GetBuildableSourceFile(chrome_root, filename):
115 """Returns a buildable source file corresponding to |filename|.
117 A buildable source file is one which is likely to be passed into clang as a
118 source file during the build. For .h files, returns the closest matching .cc,
119 .cpp or .c file. If no such file is found, returns the same as
120 GetDefaultSourceFile().
122 Args:
123 chrome_root: (String) Absolute path to the root of Chromium checkout.
124 filename: (String) Absolute path to the target source file.
126 Returns:
127 (String) Absolute path to source file.
129 if filename.endswith('.h'):
130 # Header files can't be built. Instead, try to match a header file to its
131 # corresponding source file.
132 alternates = ['.cc', '.cpp', '.c']
133 for alt_extension in alternates:
134 alt_name = filename[:-2] + alt_extension
135 if os.path.exists(alt_name):
136 return alt_name
138 return GetDefaultSourceFile(chrome_root, filename)
140 return filename
143 def GetNinjaBuildOutputsForSourceFile(out_dir, filename):
144 """Returns a list of build outputs for filename.
146 The list is generated by invoking 'ninja -t query' tool to retrieve a list of
147 inputs and outputs of |filename|. This list is then filtered to only include
148 .o and .obj outputs.
150 Args:
151 out_dir: (String) Absolute path to ninja build output directory.
152 filename: (String) Absolute path to source file.
154 Returns:
155 (List of Strings) List of target names. Will return [] if |filename| doesn't
156 yield any .o or .obj outputs.
158 # Ninja needs the path to the source file relative to the output build
159 # directory.
160 rel_filename = os.path.relpath(os.path.realpath(filename), out_dir)
162 p = subprocess.Popen(['ninja', '-C', out_dir, '-t', 'query', rel_filename],
163 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
164 stdout, _ = p.communicate()
165 if p.returncode:
166 return []
168 # The output looks like:
169 # ../../relative/path/to/source.cc:
170 # outputs:
171 # obj/reative/path/to/target.source.o
172 # obj/some/other/target2.source.o
173 # another/target.txt
175 outputs_text = stdout.partition('\n outputs:\n')[2]
176 output_lines = [line.strip() for line in outputs_text.split('\n')]
177 return [target for target in output_lines
178 if target and (target.endswith('.o') or target.endswith('.obj'))]
181 def GetClangCommandLineForNinjaOutput(out_dir, build_target):
182 """Returns the Clang command line for building |build_target|
184 Asks ninja for the list of commands used to build |filename| and returns the
185 final Clang invocation.
187 Args:
188 out_dir: (String) Absolute path to ninja build output directory.
189 build_target: (String) A build target understood by ninja
191 Returns:
192 (String or None) Clang command line or None if a Clang command line couldn't
193 be determined.
195 p = subprocess.Popen(['ninja', '-v', '-C', out_dir,
196 '-t', 'commands', build_target],
197 stdout=subprocess.PIPE)
198 stdout, stderr = p.communicate()
199 if p.returncode:
200 return None
202 # Ninja will return multiple build steps for all dependencies up to
203 # |build_target|. The build step we want is the last Clang invocation, which
204 # is expected to be the one that outputs |build_target|.
205 for line in reversed(stdout.split('\n')):
206 if 'clang' in line:
207 return line
208 return None
211 def GetClangCommandLineFromNinjaForSource(out_dir, filename):
212 """Returns a Clang command line used to build |filename|.
214 The same source file could be built multiple times using different tool
215 chains. In such cases, this command returns the first Clang invocation. We
216 currently don't prefer one toolchain over another. Hopefully the tool chain
217 corresponding to the Clang command line is compatible with the Clang build
218 used by YCM.
220 Args:
221 out_dir: (String) Absolute path to Chromium checkout.
222 filename: (String) Absolute path to source file.
224 Returns:
225 (String or None): Command line for Clang invocation using |filename| as a
226 source. Returns None if no such command line could be found.
228 build_targets = GetNinjaBuildOutputsForSourceFile(out_dir, filename)
229 for build_target in build_targets:
230 command_line = GetClangCommandLineForNinjaOutput(out_dir, build_target)
231 if command_line:
232 return command_line
233 return None
236 def GetClangOptionsFromCommandLine(clang_commandline, out_dir,
237 additional_flags):
238 """Extracts relevant command line options from |clang_commandline|
240 Args:
241 clang_commandline: (String) Full Clang invocation.
242 out_dir: (String) Absolute path to ninja build directory. Relative paths in
243 the command line are relative to |out_dir|.
244 additional_flags: (List of String) Additional flags to return.
246 Returns:
247 (List of Strings) The list of command line flags for this source file. Can
248 be empty.
250 clang_flags = [] + additional_flags
252 # Parse flags that are important for YCM's purposes.
253 clang_tokens = shlex.split(clang_commandline)
254 for flag_index, flag in enumerate(clang_tokens):
255 if flag.startswith('-I'):
256 # Relative paths need to be resolved, because they're relative to the
257 # output dir, not the source.
258 if flag[2] == '/':
259 clang_flags.append(flag)
260 else:
261 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
262 clang_flags.append('-I' + abs_path)
263 elif flag.startswith('-std'):
264 clang_flags.append(flag)
265 elif flag.startswith('-') and flag[1] in 'DWFfmO':
266 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
267 # These flags causes libclang (3.3) to crash. Remove it until things
268 # are fixed.
269 continue
270 clang_flags.append(flag)
271 elif flag == '-isysroot':
272 # On Mac -isysroot <path> is used to find the system headers.
273 # Copy over both flags.
274 if flag_index + 1 < len(clang_tokens):
275 clang_flags.append(flag)
276 clang_flags.append(clang_tokens[flag_index + 1])
277 return clang_flags
280 def GetClangOptionsFromNinjaForFilename(chrome_root, filename):
281 """Returns the Clang command line options needed for building |filename|.
283 Command line options are based on the command used by ninja for building
284 |filename|. If |filename| is a .h file, uses its companion .cc or .cpp file.
285 If a suitable companion file can't be located or if ninja doesn't know about
286 |filename|, then uses default source files in Blink and Chromium for
287 determining the commandline.
289 Args:
290 chrome_root: (String) Path to src/.
291 filename: (String) Absolute path to source file being edited.
293 Returns:
294 (List of Strings) The list of command line flags for this source file. Can
295 be empty.
297 if not chrome_root:
298 return []
300 # Generally, everyone benefits from including Chromium's src/, because all of
301 # Chromium's includes are relative to that.
302 additional_flags = ['-I' + os.path.join(chrome_root)]
304 # Version of Clang used to compile Chromium can be newer then version of
305 # libclang that YCM uses for completion. So it's possible that YCM's libclang
306 # doesn't know about some used warning options, which causes compilation
307 # warnings (and errors, because of '-Werror');
308 additional_flags.append('-Wno-unknown-warning-option')
310 sys.path.append(os.path.join(chrome_root, 'tools', 'vim'))
311 from ninja_output import GetNinjaOutputDirectory
312 out_dir = os.path.realpath(GetNinjaOutputDirectory(chrome_root))
314 clang_line = GetClangCommandLineFromNinjaForSource(
315 out_dir, GetBuildableSourceFile(chrome_root, filename))
316 if not clang_line:
317 # If ninja didn't know about filename or it's companion files, then try a
318 # default build target. It is possible that the file is new, or build.ninja
319 # is stale.
320 clang_line = GetClangCommandLineFromNinjaForSource(
321 out_dir, GetDefaultSourceFile(chrome_root, filename))
323 if not clang_line:
324 return (additional_flags, [])
326 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
329 def FlagsForFile(filename):
330 """This is the main entry point for YCM. Its interface is fixed.
332 Args:
333 filename: (String) Path to source file being edited.
335 Returns:
336 (Dictionary)
337 'flags': (List of Strings) Command line flags.
338 'do_cache': (Boolean) True if the result should be cached.
340 abs_filename = os.path.abspath(filename)
341 chrome_root = FindChromeSrcFromFilename(abs_filename)
342 clang_flags = GetClangOptionsFromNinjaForFilename(chrome_root, abs_filename)
344 # If clang_flags could not be determined, then assume that was due to a
345 # transient failure. Preventing YCM from caching the flags allows us to try to
346 # determine the flags again.
347 should_cache_flags_for_file = bool(clang_flags)
349 final_flags = _default_flags + clang_flags
351 return {
352 'flags': final_flags,
353 'do_cache': should_cache_flags_for_file