Skip to content

Instantly share code, notes, and snippets.

@kblomqvist
Last active December 20, 2017 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kblomqvist/d5ebe28d2604420d5d08553b4c575dbf to your computer and use it in GitHub Desktop.
Save kblomqvist/d5ebe28d2604420d5d08553b4c575dbf to your computer and use it in GitHub Desktop.
"""
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