Bug 904572 - Add support for generating clang compilation database; r=glandium,r=gps

This commit is contained in:
Ehsan Akhgari 2015-09-25 09:37:34 +09:00 committed by Mike Hommey
parent 80df6c3cee
commit 90545a7bf0
7 changed files with 193 additions and 41 deletions

View File

@ -228,7 +228,7 @@ define([MOZ_BUILD_BACKEND],
BUILD_BACKENDS="RecursiveMake"
MOZ_ARG_ENABLE_STRING(build-backend,
[ --enable-build-backend={AndroidEclipse,CppEclipse,VisualStudio,FasterMake}
[ --enable-build-backend={AndroidEclipse,CppEclipse,VisualStudio,FasterMake,CompileDB}
Enable additional build backends],
[ BUILD_BACKENDS="RecursiveMake `echo $enableval | sed 's/,/ /g'`"])

View File

@ -131,6 +131,7 @@ MACH_MODULES = [
'python/mozbuild/mozbuild/mach_commands.py',
'python/mozbuild/mozbuild/backend/mach_commands.py',
'python/mozbuild/mozbuild/compilation/codecomplete.py',
'python/mozbuild/mozbuild/compilation/database.py',
'python/mozbuild/mozbuild/frontend/mach_commands.py',
'services/common/tests/mach_commands.py',
'testing/luciddream/mach_commands.py',

View File

@ -26,12 +26,9 @@ class Introspection(MachCommandBase):
help='Source file to display compilation flags for')
def compileflags(self, what):
from mozbuild.util import resolve_target_to_make
import shlex
from mozbuild.compilation import util
top_make = os.path.join(self.topobjdir, 'Makefile')
if not os.path.exists(top_make):
print('Your tree has not been built yet. Please run '
'|mach build| with no arguments.')
if not util.check_top_objdir(self.topobjdir):
return 1
path_arg = self._wrap_path_argument(what)
@ -42,23 +39,7 @@ class Introspection(MachCommandBase):
if make_dir is None and make_target is None:
return 1
build_vars = {}
def on_line(line):
elements = [s.strip() for s in line.split('=', 1)]
if len(elements) != 2:
return
build_vars[elements[0]] = elements[1]
try:
old_logger = self.log_manager.replace_terminal_handler(None)
self._run_make(directory=make_dir, target='showbuild', log=False,
print_directory=False, allow_parallel=False, silent=True,
line_handler=on_line)
finally:
self.log_manager.replace_terminal_handler(old_logger)
build_vars = util.get_build_vars(make_dir, self)
if what.endswith('.c'):
name = 'COMPILE_CFLAGS'
@ -68,20 +49,5 @@ class Introspection(MachCommandBase):
if name not in build_vars:
return
flags = ['-isystem', '-I', '-include', '-MF']
new_args = []
path = os.path.join(self.topobjdir, make_dir)
for arg in shlex.split(build_vars[name]):
if new_args and new_args[-1] in flags:
arg = os.path.normpath(os.path.join(path, arg))
else:
flag = [(f, arg[len(f):]) for f in flags + ['--sysroot=']
if arg.startswith(f)]
if flag:
flag, val = flag[0]
if val:
arg = flag + os.path.normpath(os.path.join(path, val))
new_args.append(arg)
print(' '.join(new_args))
print(util.get_flags(self.topobjdir, make_dir, build_vars, name))

View File

@ -0,0 +1,119 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
# This modules provides functionality for dealing with code completion.
import os
from mozbuild.base import MozbuildObject
from mozbuild.compilation import util
from mozbuild.backend.common import CommonBackend
from mozbuild.frontend.data import (
Sources,
HostSources,
UnifiedSources,
GeneratedSources,
)
from mach.config import ConfigSettings
from mach.logging import LoggingManager
class CompileDBBackend(CommonBackend):
def _init(self):
CommonBackend._init(self)
if not util.check_top_objdir(self.environment.topobjdir):
raise Exception()
# The database we're going to dump out to.
self._db = []
# The cache for per-directory flags
self._flags = {}
log_manager = LoggingManager()
self._cmd = MozbuildObject(self.environment.topsrcdir, ConfigSettings(),
log_manager, self.environment.topobjdir)
def consume_object(self, obj):
if isinstance(obj, UnifiedSources):
# For unified sources, only include the unified source file.
# Note that unified sources are never used for host sources.
for f in obj.unified_source_mapping:
flags = self._get_dir_flags(obj.objdir)
self._build_db_line(obj, self.environment, f[0],
obj.canonical_suffix, flags, False)
elif isinstance(obj, Sources) or isinstance(obj, HostSources) or \
isinstance(obj, GeneratedSources):
# For other sources, include each source file.
for f in obj.files:
flags = self._get_dir_flags(obj.objdir)
self._build_db_line(obj, self.environment, f,
obj.canonical_suffix, flags,
isinstance(obj, HostSources))
return True
def consume_finished(self):
import json
# Output the database (a JSON file) to objdir/compile_commands.json
outputfile = os.path.join(self.environment.topobjdir, 'compile_commands.json')
with self._write_file(outputfile) as jsonout:
json.dump(self._db, jsonout, indent=0)
def _get_dir_flags(self, directory):
if directory in self._flags:
return self._flags[directory]
from mozbuild.util import resolve_target_to_make
make_dir, make_target = resolve_target_to_make(self.environment.topobjdir, directory)
if make_dir is None and make_target is None:
raise Exception('Cannot figure out the make dir and target for ' + directory)
build_vars = util.get_build_vars(directory, self._cmd)
# We only care about the following build variables.
for name in ('COMPILE_CFLAGS', 'COMPILE_CXXFLAGS',
'COMPILE_CMFLAGS', 'COMPILE_CMMFLAGS'):
if name not in build_vars:
continue
build_vars[name] = util.get_flags(self.environment.topobjdir, directory,
build_vars, name)
self._flags[directory] = build_vars
return self._flags[directory]
def _build_db_line(self, obj, cenv, filename, canonical_suffix, flags, ishost):
# Distinguish between host and target files.
prefix = 'HOST_' if ishost else ''
if canonical_suffix == '.c':
compiler = cenv.substs[prefix + 'CC']
cflags = flags['COMPILE_CFLAGS']
# Add the Objective-C flags if needed.
if filename.endswith('.m'):
cflags += ' ' + flags['COMPILE_CMFLAGS']
elif canonical_suffix == '.cpp':
compiler = cenv.substs[prefix + 'CXX']
cflags = flags['COMPILE_CXXFLAGS']
# Add the Objective-C++ flags if needed.
if filename.endswith('.mm'):
cflags += ' ' + flags['COMPILE_CMMFLAGS']
else:
return
cmd = ' '.join([
compiler,
'-o', '/dev/null', '-c',
cflags,
filename,
])
self._db.append({
'directory': obj.objdir,
'command': cmd,
'file': filename
})

View File

@ -0,0 +1,63 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import os
import shlex
def check_top_objdir(topobjdir):
top_make = os.path.join(topobjdir, 'Makefile')
if not os.path.exists(top_make):
print('Your tree has not been built yet. Please run '
'|mach build| with no arguments.')
return False
return True
def get_build_vars(directory, cmd):
build_vars = {}
def on_line(line):
elements = [s.strip() for s in line.split('=', 1)]
if len(elements) != 2:
return
build_vars[elements[0]] = elements[1]
try:
old_logger = cmd.log_manager.replace_terminal_handler(None)
cmd._run_make(directory=directory, target='showbuild', log=False,
print_directory=False, allow_parallel=False, silent=True,
line_handler=on_line)
finally:
cmd.log_manager.replace_terminal_handler(old_logger)
return build_vars
def get_flags(topobjdir, make_dir, build_vars, name):
flags = ['-isystem', '-I', '-include', '-MF']
new_args = []
path = os.path.join(topobjdir, make_dir)
# Take case to handle things such as the following correctly:
# * -DMOZ_APP_VERSION='"40.0a1"'
# * -DR_PLATFORM_INT_TYPES='<stdint.h>'
# * -DAPP_ID='{ec8030f7-c20a-464f-9b0e-13a3a9e97384}
# * -D__UNUSED__='__attribute__((unused))'
lex = shlex.shlex(build_vars[name])
lex.quotes = '"'
lex.wordchars += '+/\'"-=.*{}()[]<>'
for arg in list(lex):
if new_args and new_args[-1] in flags:
arg = os.path.normpath(os.path.join(path, arg))
else:
flag = [(f, arg[len(f):]) for f in flags + ['--sysroot=']
if arg.startswith(f)]
if flag:
flag, val = flag[0]
if val:
arg = flag + os.path.normpath(os.path.join(path, val))
new_args.append(arg)
return ' '.join(new_args)

View File

@ -108,7 +108,7 @@ def config_status(topobjdir='.', topsrcdir='.',
help='print diffs of changed files.')
parser.add_argument('-b', '--backend', nargs='+',
choices=['RecursiveMake', 'AndroidEclipse', 'CppEclipse',
'VisualStudio', 'FasterMake'],
'VisualStudio', 'FasterMake', 'CompileDB'],
default=default_backends,
help='what backend to build (default: %s).' %
' '.join(default_backends))
@ -145,6 +145,9 @@ def config_status(topobjdir='.', topsrcdir='.',
elif backend == 'FasterMake':
from mozbuild.backend.fastermake import FasterMakeBackend
backends_cls.append(FasterMakeBackend)
elif backend == 'CompileDB':
from mozbuild.compilation.database import CompileDBBackend
backends_cls.append(CompileDBBackend)
else:
backends_cls.append(RecursiveMakeBackend)

View File

@ -560,7 +560,7 @@ class Build(MachCommandBase):
# conditions, but that is for another day.
@CommandArgument('-b', '--backend', nargs='+',
choices=['RecursiveMake', 'AndroidEclipse', 'CppEclipse',
'VisualStudio', 'FasterMake'],
'VisualStudio', 'FasterMake', 'CompileDB'],
help='Which backend to build.')
def build_backend(self, backend, diff=False):
python = self.virtualenv_manager.python_path