Last active
December 20, 2017 18:51
-
-
Save kblomqvist/d5ebe28d2604420d5d08553b4c575dbf to your computer and use it in GitHub Desktop.
This file contains hidden or 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
""" | |
Usage: python scons2ecc.py > compile_commands.json | |
The MIT License (MIT) | |
Copyright (c) 2017 Kim Blomqvist | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
THE SOFTWARE. | |
""" | |
import re | |
import json | |
import os.path | |
import subprocess | |
SUPPORTED_STD_PREFIX = ('c', 'gnu', 'c++', 'gnu++') | |
SUPPORTED_STD_SUFFIX = ('11', '14', '1z') | |
def std2lang(std): | |
""" Returns language option from std """ | |
assert std[:-2] in SUPPORTED_STD_PREFIX | |
assert std[-2:] in SUPPORTED_STD_SUFFIX | |
return 'c++' if '++' in std else 'c' | |
def to_predefined_macro(name, definition=None): | |
""" Converts name and definition into a form of predefined macro """ | |
if definition == None: | |
return "-D'{}'".format(name) | |
else: | |
return "-D'{}={}'".format(name, definition) | |
def sys_includes(std, cc='gcc'): | |
""" Yields system include paths """ | |
lang = std2lang(std) | |
cmd = (cc, '-x' + lang, '-std=' + std, '-Wp,-v', '-E', '-') | |
try: | |
cp = subprocess.run(cmd, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.STDOUT, | |
stdin=subprocess.DEVNULL | |
) | |
except FileNotFoundError: | |
raise Exception('{} not found in PATH.'.format(cc)) | |
pick = False | |
stdout = cp.stdout.decode() | |
for line in stdout.splitlines(): | |
if '#include <...> search starts here:' in line: | |
pick = True | |
continue | |
if 'End of search list.' in line: | |
break | |
if pick: | |
path = r'{}'.format(line.strip()) | |
path = os.path.normpath(path) | |
yield path.replace('\\', '/') | |
def sys_macros(std, cc='gcc'): | |
""" Yields system macros in form of tuple: (identifier, replacement) """ | |
lang = std2lang(std) | |
cmd = (cc, '-x' + lang, '-std=' + std, '-dM', '-E', '-') | |
try: | |
cp = subprocess.run(cmd, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.STDOUT, | |
stdin=subprocess.DEVNULL | |
) | |
except FileNotFoundError: | |
raise Exception('{} not found in PATH.'.format(cc)) | |
stdout = cp.stdout.decode() | |
for line in stdout.splitlines(): | |
identifier = None | |
replacement = None | |
try: | |
m = re.search(r'#define ([\w()]+) (.+)', line) | |
identifier = m.group(1) | |
replacement = m.group(2) | |
except AttributeError: | |
try: | |
m = re.search(r'#define (\w+)', line) | |
identifier = m.group(1) | |
except AttributeError: | |
pass | |
if identifier: | |
yield (identifier, replacement) | |
def version(cc='gcc'): | |
""" Returns version in string: x.y.z """ | |
try: | |
cmd = (cc, '-dumpversion') | |
cp = subprocess.run(cmd, stdout=subprocess.PIPE) | |
except FileNotFoundError: | |
raise Exception('{} not found in PATH.'.format(cc)) | |
return cp.stdout.decode().strip() | |
def scons_dump(sconstruct_file, raw=False): | |
if os.name == 'nt': # Windows | |
cmd = ('scons.bat', '-s', '-f', '-') | |
else: | |
cmd = ('scons', '-s', '-f', '-') | |
with open(sconstruct_file, 'r') as f: | |
stdin = f.read() | |
# My SCons wish list: | |
# | |
# 1) env.Dump() should convert objects like Node.FS.Dir to str | |
# so that Dump could be used here directly. | |
# | |
# 2) It would help if env.Dump() can serialize into JSON, e.g. | |
# Util.CLVar could serialize into list etc. | |
# | |
# 3) Node.FS.Dir should print with abspath instead of just path. | |
# | |
# 4) Internally convert CPPPATH items given in string to Node.FS.Dir, | |
# and gracefully handle the # character for the user's convenience. | |
# | |
# 5) Normalize the list of CPPDEFINES. | |
# - Why tuple presentation is converted to list? | |
# CPPDEFINES=[('B', 2), 'A'] converts to [['B', 2], 'A'] | |
# | |
# Should be [('B', 2), ('A', None)] | |
# | |
# - Why dict presentation is not folded into list? | |
# CPPDEFINES={'B':2, 'A':None} remains as {'B':2, 'A':None} | |
# | |
# Should be [('B', 2), ('A', None)] | |
# | |
# 6) gcc -include/-imacro "/home/user/proj/settings.h" should be supported | |
# and SCons should monitor the file. Note that Node.FS.Dir.abspath | |
# should be used in CLI option. | |
# | |
# 7) $VARIABLEs should be converted, e.g. in case of CXX = "$CC". | |
# | |
stdin += """ | |
try: | |
DUMP = env.Dictionary() | |
except NameError: | |
DUMP = DefaultEnvironment().Dictionary() | |
DUMPOUT = dict(CPPPATH=[]) | |
for key in ('CC', 'CXX', 'CPPDEFPREFIX', 'INCPREFIX'): | |
DUMPOUT[key] = str(DUMP[key]) if key in DUMP else '' | |
for key in ('CFLAGS', 'CPPFLAGS', 'CCFLAGS'): | |
DUMPOUT[key] = list(DUMP[key]) if key in DUMP else [] | |
DUMPOUT['CPPDEFINES'] = DUMP['CPPDEFINES'] | |
if 'CPPPATH' in DUMP: | |
for item in DUMP['CPPPATH']: | |
if not isinstance(item, str): | |
item = item.abspath | |
if item.startswith('#'): | |
item = item[1:] | |
DUMPOUT['CPPPATH'].append(item) | |
import json | |
print(json.dumps(DUMPOUT)) | |
Exit(0) | |
""" | |
try: | |
cp = subprocess.run(cmd, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.STDOUT, | |
input=stdin.encode() | |
) | |
except FileNotFoundError: | |
raise Exception('{} not found in PATH.'.format(cmd[0])) | |
try: | |
import json | |
dump = json.loads(cp.stdout.decode()) | |
except json.decoder.JSONDecodeError: | |
print(cp.stdout.decode()) | |
raise | |
if raw: | |
return dump | |
C_STD = 'gnu11' | |
CPP_STD = 'gnu++14' | |
CC = dump['CC'] | |
CXX = dump['CXX'] | |
CPPPATH = dump['CPPPATH'] | |
CPPDEFINES = [] | |
for flag in dump['CFLAGS']: | |
if flag.startswith('-std='): | |
C_STD = flag[5:] | |
if flag.startswith('-I'): | |
CPPPATH.append(flag[2:]) | |
if flag.startswith('-D'): | |
flag = flag[2:].split('=') | |
CPPDEFINES.append(tuple(flag)) | |
for flag in dump['CPPFLAGS']: | |
if flag.startswith('-std='): | |
CPP_STD = flag[5:] | |
if flag.startswith('-I'): | |
CPPPATH.append(flag[2:]) | |
if flag.startswith('-D'): | |
flag = flag[2:].split('=') | |
CPPDEFINES.append(tuple(flag)) | |
for flag in dump['CPPDEFINES']: | |
if isinstance(flag, list): | |
CPPDEFINES.append(tuple(flag)) | |
if isinstance(flag, dict): | |
CPPDEFINES.extend([(k, v) for k, v in flag.items()]) | |
return (CC, CXX, C_STD, CPP_STD, CPPPATH, CPPDEFINES) | |
# Script starts here | |
CC, CXX, C_STD, CPP_STD, CPPPATH, CPPDEFINES = scons_dump('SConstruct') | |
sys_c_paths = sys_includes(C_STD, cc=CC) | |
sys_c_defines = sys_macros(C_STD, cc=CC) | |
sys_cpp_paths = sys_includes(CPP_STD, cc=CXX) | |
sys_cpp_defines = sys_macros(CPP_STD, cc=CXX) | |
c_flags = ['-std=' + C_STD] | |
c_flags += ['-I' + pth for pth in sys_c_paths] | |
c_flags += [to_predefined_macro(*d) for d in sys_c_defines] | |
cpp_flags = ['-std=' + CPP_STD] | |
cpp_flags += ['-I' + pth for pth in sys_cpp_paths] | |
cpp_flags += [to_predefined_macro(*d) for d in sys_cpp_defines] | |
common_flags = ['-I' + pth for pth in CPPPATH] | |
common_flags += [to_predefined_macro(*d) for d in CPPDEFINES] | |
ecc = dict(common_flags=common_flags, c_flags=c_flags, cpp_flags=cpp_flags) | |
dump = json.dumps(ecc, separators=(',', ': '), indent=2) | |
print(dump) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment