blob: 6329b3971b4ae52c52d6cac892f4747d33b7d0f5 [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#
12# 2. Point to this config file in your .vimrc:
13# let g:ycm_global_ycm_extra_conf =
14# '<chrome_depot>/src/tools/vim/chromium.ycm_extra_conf.py'
15#
16# 3. Profit
17#
18#
19# Usage notes:
20#
21# * You must use ninja & clang to build Chromium.
22#
23# * You must have run gyp_chromium and built Chromium recently.
24#
25#
26# Hacking notes:
27#
28# * The purpose of this script is to construct an accurate enough command line
29# for YCM to pass to clang so it can build and extract the symbols.
30#
31# * Right now, we only pull the -I and -D flags. That seems to be sufficient
32# for everything I've used it for.
33#
34# * That whole ninja & clang thing? We could support other configs if someone
35# were willing to write the correct commands and a parser.
36#
37# * This has only been tested on gPrecise.
38
39
40import os
41import subprocess
42
[email protected]eea749f2013-02-06 19:30:3843
[email protected]a08a4282013-02-05 23:45:5244# Flags from YCM's default config.
45flags = [
[email protected]a08a4282013-02-05 23:45:5246'-DUSE_CLANG_COMPLETER',
47'-std=c++11',
48'-x',
49'c++',
50]
51
52
[email protected]1cab75682013-04-24 19:50:4953def PathExists(*args):
54 return os.path.exists(os.path.join(*args))
55
56
[email protected]a08a4282013-02-05 23:45:5257def FindChromeSrcFromFilename(filename):
58 """Searches for the root of the Chromium checkout.
59
60 Simply checks parent directories until it finds .gclient and src/.
61
62 Args:
63 filename: (String) Path to source file being edited.
64
65 Returns:
66 (String) Path of 'src/', or None if unable to find.
67 """
68 curdir = os.path.normpath(os.path.dirname(filename))
[email protected]7dcc77a2013-05-17 14:01:5269 while not (PathExists(curdir, 'src') and PathExists(curdir, 'src', 'DEPS')
[email protected]1cab75682013-04-24 19:50:4970 and (PathExists(curdir, '.gclient')
71 or PathExists(curdir, 'src', '.git'))):
[email protected]a08a4282013-02-05 23:45:5272 nextdir = os.path.normpath(os.path.join(curdir, '..'))
73 if nextdir == curdir:
74 return None
75 curdir = nextdir
76 return os.path.join(curdir, 'src')
77
78
[email protected]b944e062013-05-08 01:40:5579# Largely copied from ninja-build.vim (guess_configuration)
80def GetNinjaOutputDirectory(chrome_root):
81 """Returns either <chrome_root>/out/Release or <chrome_root>/out/Debug.
82
83 The configuration chosen is the one most recently generated/built."""
84 root = os.path.join(chrome_root, 'out')
85 debug_path = os.path.join(root, 'Debug')
86 release_path = os.path.join(root, 'Release')
87
88 def is_release_15s_newer(test_path):
89 try:
90 debug_mtime = os.path.getmtime(os.path.join(debug_path, test_path))
91 except os.error:
92 debug_mtime = 0
93 try:
94 rel_mtime = os.path.getmtime(os.path.join(release_path, test_path))
95 except os.error:
96 rel_mtime = 0
97 return rel_mtime - debug_mtime >= 15
98
99 if is_release_15s_newer('build.ninja') or is_release_15s_newer('protoc'):
100 return release_path
101 return debug_path
102
103
[email protected]a08a4282013-02-05 23:45:52104def GetClangCommandFromNinjaForFilename(chrome_root, filename):
105 """Returns the command line to build |filename|.
106
[email protected]eea749f2013-02-06 19:30:38107 Asks ninja how it would build the source file. If the specified file is a
108 header, tries to find its companion source file first.
[email protected]a08a4282013-02-05 23:45:52109
110 Args:
111 chrome_root: (String) Path to src/.
112 filename: (String) Path to source file being edited.
113
114 Returns:
115 (List of Strings) Command line arguments for clang.
116 """
117 if not chrome_root:
118 return []
119
[email protected]eea749f2013-02-06 19:30:38120 # Generally, everyone benefits from including Chromium's src/, because all of
121 # Chromium's includes are relative to that.
122 chrome_flags = ['-I' + os.path.join(chrome_root)]
123
124 # Header files can't be built. Instead, try to match a header file to its
125 # corresponding source file.
126 if filename.endswith('.h'):
127 alternates = ['.cc', '.cpp']
128 for alt_extension in alternates:
129 alt_name = filename[:-2] + alt_extension
130 if os.path.exists(alt_name):
131 filename = alt_name
132 break
133 else:
134 # If this is a standalone .h file with no source, the best we can do is
135 # try to use the default flags.
136 return chrome_flags
137
[email protected]a08a4282013-02-05 23:45:52138 # Ninja needs the path to the source file from the output build directory.
139 # Cut off the common part and /.
140 subdir_filename = filename[len(chrome_root)+1:]
141 rel_filename = os.path.join('..', '..', subdir_filename)
142
[email protected]b944e062013-05-08 01:40:55143 out_dir = GetNinjaOutputDirectory(chrome_root)
144
[email protected]a08a4282013-02-05 23:45:52145 # Ask ninja how it would build our source file.
[email protected]b944e062013-05-08 01:40:55146 p = subprocess.Popen(['ninja', '-v', '-C', out_dir, '-t',
[email protected]a08a4282013-02-05 23:45:52147 'commands', rel_filename + '^'],
148 stdout=subprocess.PIPE)
149 stdout, stderr = p.communicate()
150 if p.returncode:
[email protected]eea749f2013-02-06 19:30:38151 return chrome_flags
[email protected]a08a4282013-02-05 23:45:52152
153 # Ninja might execute several commands to build something. We want the last
154 # clang command.
155 clang_line = None
156 for line in reversed(stdout.split('\n')):
[email protected]eea749f2013-02-06 19:30:38157 if 'clang' in line:
[email protected]a08a4282013-02-05 23:45:52158 clang_line = line
159 break
[email protected]eea749f2013-02-06 19:30:38160 else:
161 return chrome_flags
[email protected]a08a4282013-02-05 23:45:52162
[email protected]e666f182014-01-09 12:21:37163 # Parse flags that are important for YCM's purposes.
[email protected]a08a4282013-02-05 23:45:52164 for flag in clang_line.split(' '):
165 if flag.startswith('-I'):
166 # Relative paths need to be resolved, because they're relative to the
167 # output dir, not the source.
168 if flag[2] == '/':
169 chrome_flags.append(flag)
170 else:
[email protected]b944e062013-05-08 01:40:55171 abs_path = os.path.normpath(os.path.join(out_dir, flag[2:]))
[email protected]a08a4282013-02-05 23:45:52172 chrome_flags.append('-I' + abs_path)
[email protected]e666f182014-01-09 12:21:37173 elif flag.startswith('-std'):
174 chrome_flags.append(flag)
[email protected]eea749f2013-02-06 19:30:38175 elif flag.startswith('-') and flag[1] in 'DWFfmO':
[email protected]dc569cf92013-07-12 03:21:12176 if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard':
177 # These flags causes libclang (3.3) to crash. Remove it until things
[email protected]06a4f3e2013-07-09 00:16:49178 # are fixed.
179 continue
[email protected]a08a4282013-02-05 23:45:52180 chrome_flags.append(flag)
181
[email protected]a08a4282013-02-05 23:45:52182 return chrome_flags
183
184
185def FlagsForFile(filename):
186 """This is the main entry point for YCM. Its interface is fixed.
187
188 Args:
189 filename: (String) Path to source file being edited.
190
191 Returns:
192 (Dictionary)
193 'flags': (List of Strings) Command line flags.
194 'do_cache': (Boolean) True if the result should be cached.
195 """
196 chrome_root = FindChromeSrcFromFilename(filename)
197 chrome_flags = GetClangCommandFromNinjaForFilename(chrome_root,
198 filename)
199 final_flags = flags + chrome_flags
200
[email protected]a08a4282013-02-05 23:45:52201 return {
202 'flags': final_flags,
203 'do_cache': True
204 }