Add ICU message format support
[chromium-blink-merge.git] / tools / vim / chromium.ycm_extra_conf.py
blobeedd1b82c610d0c152685e6c923f4a8d0a690dff
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 in 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)
272 return clang_flags
275 def GetClangOptionsFromNinjaForFilename(chrome_root, filename):
276 """Returns the Clang command line options needed for building |filename|.
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.
284 Args:
285 chrome_root: (String) Path to src/.
286 filename: (String) Absolute path to source file being edited.
288 Returns:
289 (List of Strings) The list of command line flags for this source file. Can
290 be empty.
292 if not chrome_root:
293 return []
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)]
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')
305 sys.path.append(os.path.join(chrome_root, 'tools', 'vim'))
306 from ninja_output import GetNinjaOutputDirectory
307 out_dir = os.path.realpath(GetNinjaOutputDirectory(chrome_root))
309 clang_line = GetClangCommandLineFromNinjaForSource(
310 out_dir, GetBuildableSourceFile(chrome_root, filename))
311 if not clang_line:
312 # If ninja didn't know about filename or it's companion files, then try a
313 # default build target. It is possible that the file is new, or build.ninja
314 # is stale.
315 clang_line = GetClangCommandLineFromNinjaForSource(
316 out_dir, GetDefaultSourceFile(chrome_root, filename))
318 if not clang_line:
319 return (additional_flags, [])
321 return GetClangOptionsFromCommandLine(clang_line, out_dir, additional_flags)
324 def FlagsForFile(filename):
325 """This is the main entry point for YCM. Its interface is fixed.
327 Args:
328 filename: (String) Path to source file being edited.
330 Returns:
331 (Dictionary)
332 'flags': (List of Strings) Command line flags.
333 'do_cache': (Boolean) True if the result should be cached.
335 abs_filename = os.path.abspath(filename)
336 chrome_root = FindChromeSrcFromFilename(abs_filename)
337 clang_flags = GetClangOptionsFromNinjaForFilename(chrome_root, abs_filename)
339 # If clang_flags could not be determined, then assume that was due to a
340 # transient failure. Preventing YCM from caching the flags allows us to try to
341 # determine the flags again.
342 should_cache_flags_for_file = bool(clang_flags)
344 final_flags = _default_flags + clang_flags
346 return {
347 'flags': final_flags,
348 'do_cache': should_cache_flags_for_file