Created
February 28, 2014 14:51
-
-
Save jjgod/9272444 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import subprocess | |
import sys | |
# A minimal memoizing decorator. It'll blow up if the args aren't immutable, | |
# among other "problems". | |
class memoize(object): | |
def __init__(self, func): | |
self.func = func | |
self.cache = {} | |
def __call__(self, *args): | |
try: | |
return self.cache[args] | |
except KeyError: | |
result = self.func(*args) | |
self.cache[args] = result | |
return result | |
@memoize | |
def RelativePath(path, relative_to): | |
# Assuming both |path| and |relative_to| are relative to the current | |
# directory, returns a relative path that identifies path relative to | |
# relative_to. | |
# Convert to normalized (and therefore absolute paths). | |
path = os.path.realpath(path) | |
relative_to = os.path.realpath(relative_to) | |
# On Windows, we can't create a relative path to a different drive, so just | |
# use the absolute path. | |
if sys.platform == 'win32': | |
if (os.path.splitdrive(path)[0].lower() != | |
os.path.splitdrive(relative_to)[0].lower()): | |
return path | |
# Split the paths into components. | |
path_split = path.split(os.path.sep) | |
relative_to_split = relative_to.split(os.path.sep) | |
# Determine how much of the prefix the two paths share. | |
prefix_len = len(os.path.commonprefix([path_split, relative_to_split])) | |
# Put enough ".." components to back up out of relative_to to the common | |
# prefix, and then append the part of path_split after the common prefix. | |
relative_split = [os.path.pardir] * (len(relative_to_split) - prefix_len) + \ | |
path_split[prefix_len:] | |
if len(relative_split) == 0: | |
# The paths were the same. | |
return '' | |
# Turn it back into a string and we're done. | |
return os.path.join(*relative_split) | |
def FindRepoRoot(path): | |
"""Get the absolute path to the repository of the current script.""" | |
root_dir = os.path.dirname(os.path.abspath(path)) | |
while (root_dir != os.path.dirname(root_dir) and | |
not os.path.exists(os.path.join(root_dir, ".git"))): | |
root_dir = os.path.dirname(root_dir) | |
return root_dir | |
def FindChromeSrcFromFilename(filename): | |
"""Searches for the root of the Chromium checkout. | |
Simply checks parent directories until it finds .gclient and src/. | |
Args: | |
filename: (String) Path to source file being edited. | |
Returns: | |
(String) Path of 'src/', or None if unable to find. | |
""" | |
return os.path.join(FindRepoRoot(filename), 'chromium', 'src') | |
# Largely copied from ninja-build.vim (guess_configuration) | |
def GetNinjaOutputDirectory(chrome_root): | |
"""Returns either <chrome_root>/out/Release or <chrome_root>/out/Debug. | |
The configuration chosen is the one most recently generated/built.""" | |
root = os.path.join(chrome_root, 'out') | |
debug_path = os.path.join(root, 'Debug') | |
release_path = os.path.join(root, 'Release') | |
def is_release_15s_newer(test_path): | |
try: | |
debug_mtime = os.path.getmtime(os.path.join(debug_path, test_path)) | |
except os.error: | |
debug_mtime = 0 | |
try: | |
rel_mtime = os.path.getmtime(os.path.join(release_path, test_path)) | |
except os.error: | |
rel_mtime = 0 | |
return rel_mtime - debug_mtime >= 15 | |
if is_release_15s_newer('build.ninja') or is_release_15s_newer('protoc'): | |
return release_path | |
return debug_path | |
def GetClangCommandFromNinjaForFilename(chrome_root, filename): | |
"""Returns the command line to build |filename|. | |
Asks ninja how it would build the source file. If the specified file is a | |
header, tries to find its companion source file first. | |
Args: | |
chrome_root: (String) Path to src/. | |
filename: (String) Path to source file being edited. | |
Returns: | |
(List of Strings) Command line arguments for clang. | |
""" | |
if not chrome_root: | |
return [] | |
# Generally, everyone benefits from including Chromium's src/, because all of | |
# Chromium's includes are relative to that. | |
chrome_flags = ['-I' + os.path.join(chrome_root)] | |
# Header files can't be built. Instead, try to match a header file to its | |
# corresponding source file. | |
if filename.endswith('.h'): | |
alternates = ['.cc', '.cpp'] | |
for alt_extension in alternates: | |
alt_name = filename[:-2] + alt_extension | |
if os.path.exists(alt_name): | |
filename = alt_name | |
break | |
else: | |
# If this is a standalone .h file with no source, the best we can do is | |
# try to use the default flags. | |
return chrome_flags | |
out_dir = GetNinjaOutputDirectory(chrome_root) | |
# Ninja needs the path to the source file from the output build directory. | |
rel_filename = RelativePath(filename, out_dir) | |
print rel_filename | |
# Ask ninja how it would build our source file. | |
p = subprocess.Popen(['ninja', '-v', '-C', out_dir, '-t', | |
'commands', rel_filename + '^'], | |
stdout=subprocess.PIPE) | |
stdout, stderr = p.communicate() | |
if p.returncode: | |
return chrome_flags | |
# Ninja might execute several commands to build something. We want the last | |
# clang command. | |
clang_line = None | |
for line in reversed(stdout.split('\n')): | |
if 'clang' in line: | |
clang_line = line | |
break | |
else: | |
return chrome_flags | |
# Parse flags that are important for YCM's purposes. | |
for flag in clang_line.split(' '): | |
if flag.startswith('-I'): | |
# Relative paths need to be resolved, because they're relative to the | |
# output dir, not the source. | |
if flag[2] == '/': | |
chrome_flags.append(flag) | |
else: | |
abs_path = os.path.normpath(os.path.join(out_dir, flag[2:])) | |
chrome_flags.append('-I' + abs_path) | |
elif flag.startswith('-std'): | |
chrome_flags.append(flag) | |
elif flag.startswith('-') and flag[1] in 'DWFfmO': | |
if flag == '-Wno-deprecated-register' or flag == '-Wno-header-guard': | |
# These flags causes libclang (3.3) to crash. Remove it until things | |
# are fixed. | |
continue | |
chrome_flags.append(flag) | |
return chrome_flags | |
if __name__ == '__main__': | |
filename = sys.argv[1] | |
chrome_root = FindChromeSrcFromFilename(filename) | |
chrome_flags = GetClangCommandFromNinjaForFilename(chrome_root, | |
filename) | |
print chrome_flags |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment