import re
import os
import collections
__all__ = ['split_command', 'classify_source', 'compiler_language']
IGNORED_FLAGS = {
'-c': 0,
'-MD': 0,
'-MMD': 0,
'-MG': 0,
'-MP': 0,
'-MF': 1,
'-MT': 1,
'-MQ': 1,
'-static': 0,
'-shared': 0,
'-s': 0,
'-rdynamic': 0,
'-l': 1,
'-L': 1,
'-u': 1,
'-z': 1,
'-T': 1,
'-Xlinker': 1
}
COMPILER_PATTERNS = frozenset([
re.compile(r'^(intercept-|analyze-|)c(c|\+\+)$'),
re.compile(r'^([^-]*-)*[mg](cc|\+\+)(-\d+(\.\d+){0,2})?$'),
re.compile(r'^([^-]*-)*clang(\+\+)?(-\d+(\.\d+){0,2})?$'),
re.compile(r'^llvm-g(cc|\+\+)$'),
])
def split_command(command):
result = collections.namedtuple('Compilation',
['compiler', 'flags', 'files'])
result.compiler = compiler_language(command)
result.flags = []
result.files = []
if not result.compiler:
return None
args = iter(command[1:])
for arg in args:
if arg in {'-E', '-S', '-cc1', '-M', '-MM', '-###'}:
return None
elif arg in IGNORED_FLAGS:
count = IGNORED_FLAGS[arg]
for _ in range(count):
next(args)
elif re.match(r'^-(l|L|Wl,).+', arg):
pass
elif arg in {'-D', '-I'}:
result.flags.extend([arg, next(args)])
elif re.match(r'^[^-].+', arg) and classify_source(arg):
result.files.append(arg)
else:
result.flags.append(arg)
return result if result.files else None
def classify_source(filename, c_compiler=True):
mapping = {
'.c': 'c' if c_compiler else 'c++',
'.i': 'c-cpp-output' if c_compiler else 'c++-cpp-output',
'.ii': 'c++-cpp-output',
'.m': 'objective-c',
'.mi': 'objective-c-cpp-output',
'.mm': 'objective-c++',
'.mii': 'objective-c++-cpp-output',
'.C': 'c++',
'.cc': 'c++',
'.CC': 'c++',
'.cp': 'c++',
'.cpp': 'c++',
'.cxx': 'c++',
'.c++': 'c++',
'.C++': 'c++',
'.txx': 'c++'
}
__, extension = os.path.splitext(os.path.basename(filename))
return mapping.get(extension)
def compiler_language(command):
cplusplus = re.compile(r'^(.+)(\+\+)(-.+|)$')
if command:
executable = os.path.basename(command[0])
if any(pattern.match(executable) for pattern in COMPILER_PATTERNS):
return 'c++' if cplusplus.match(executable) else 'c'
return None