|
|
|
#!/usr/bin/python
|
|
|
|
#
|
|
|
|
# Combine all source and header files in source directory into
|
|
|
|
# a single C file.
|
|
|
|
#
|
|
|
|
# The process is not very simple or clean. This helper is not
|
|
|
|
# a generic or 100% correct in the general case: it just needs
|
|
|
|
# to work for Duktape.
|
|
|
|
#
|
|
|
|
# Overview of the process:
|
|
|
|
#
|
|
|
|
# * Parse all relevant C and H files.
|
|
|
|
#
|
|
|
|
# * Change all non-exposed functions and variables to "static" in
|
|
|
|
# both headers (extern -> static) and in implementation files.
|
|
|
|
#
|
|
|
|
# * Emit internal headers by starting from duk_internal.h (the only
|
|
|
|
# internal header included by Duktape C files) and emulating the
|
|
|
|
# include mechanism recursively.
|
|
|
|
#
|
|
|
|
# * Emit all source files, removing any internal includes (these
|
|
|
|
# should all be duk_internal.h ideally but there are a few remnants).
|
|
|
|
#
|
|
|
|
# At every step, source and header lines are represented with explicit
|
|
|
|
# line objects which keep track of original filename and line. The
|
|
|
|
# output contains #line directives, if necessary, to ensure error
|
|
|
|
# throwing and other diagnostic info will work in a useful manner when
|
|
|
|
# deployed.
|
|
|
|
#
|
|
|
|
# Making the process deterministic is important, so that if users have
|
|
|
|
# diffs that they apply to the combined source, such diffs would apply
|
|
|
|
# for as long as possible.
|
|
|
|
#
|
|
|
|
# Limitations and notes:
|
|
|
|
#
|
|
|
|
# * #defines are not #undef'd at the end of an input file, so defines
|
|
|
|
# may bleed to other files. These need to be fixed in the original
|
|
|
|
# sources.
|
|
|
|
#
|
|
|
|
# * System headers included with a certain define (like _BSD_SOURCE)
|
|
|
|
# are not handled correctly now.
|
|
|
|
#
|
|
|
|
# * External includes are not removed or combined: some of them are
|
|
|
|
# inside #ifdef directives, so it would difficult to do so. Ideally
|
|
|
|
# there would be no external includes in individual files.
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import re
|
|
|
|
|
|
|
|
re_extinc = re.compile(r'^#include <(.*?)>.*$')
|
|
|
|
re_intinc = re.compile(r'^#include \"(duk.*?)\".*$') # accept duktape.h too
|
|
|
|
|
|
|
|
class File:
|
|
|
|
filename_full = None
|
|
|
|
filename = None
|
|
|
|
lines = None
|
|
|
|
|
|
|
|
def __init__(self, filename, lines):
|
|
|
|
self.filename = os.path.basename(filename)
|
|
|
|
self.filename_full = filename
|
|
|
|
self.lines = lines
|
|
|
|
|
|
|
|
class Line:
|
|
|
|
filename_full = None
|
|
|
|
filename = None
|
|
|
|
lineno = None
|
|
|
|
data = None
|
|
|
|
|
|
|
|
def __init__(self, filename, lineno, data):
|
|
|
|
self.filename = os.path.basename(filename)
|
|
|
|
self.filename_full = filename
|
|
|
|
self.lineno = lineno
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
def read(filename):
|
|
|
|
lines = []
|
|
|
|
|
|
|
|
f = None
|
|
|
|
try:
|
|
|
|
f = open(filename, 'rb')
|
|
|
|
lineno = 0
|
|
|
|
for line in f:
|
|
|
|
lineno += 1
|
|
|
|
if len(line) > 0 and line[-1] == '\n':
|
|
|
|
line = line[:-1]
|
|
|
|
lines.append(Line(filename, lineno, line))
|
|
|
|
finally:
|
|
|
|
if f is not None:
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
return File(filename, lines)
|
|
|
|
|
|
|
|
def findFile(files, filename):
|
|
|
|
for i in files:
|
|
|
|
if i.filename == filename:
|
|
|
|
return i
|
|
|
|
return None
|
|
|
|
|
|
|
|
def processIncludes(f):
|
|
|
|
extinc = []
|
|
|
|
intinc = []
|
|
|
|
|
|
|
|
for line in f.lines:
|
|
|
|
if not line.data.startswith('#include'):
|
|
|
|
continue
|
|
|
|
|
|
|
|
m = re_extinc.match(line.data)
|
|
|
|
if m is not None:
|
|
|
|
# external includes are kept; they may even be conditional
|
|
|
|
extinc.append(m.group(1))
|
|
|
|
#line.data = ''
|
|
|
|
continue
|
|
|
|
|
|
|
|
m = re_intinc.match(line.data)
|
|
|
|
if m is not None:
|
|
|
|
intinc.append(m.group(1))
|
|
|
|
#line.data = ''
|
|
|
|
continue
|
|
|
|
|
|
|
|
print(line.data)
|
|
|
|
raise Exception('cannot parse include directive')
|
|
|
|
|
|
|
|
return extinc, intinc
|
|
|
|
|
|
|
|
def processDeclarations(f):
|
|
|
|
for line in f.lines:
|
|
|
|
# FIXME: total placeholder
|
|
|
|
if line.data.startswith('int ') or line.data.startswith('void '):
|
|
|
|
line.data = 'static ' + line.data
|
|
|
|
elif line.data.startswith('extern int') or line.data.startswith('extern void '):
|
|
|
|
line.data = 'static ' + line.data[7:] # replace extern with static
|
|
|
|
|
|
|
|
def createCombined(files, extinc, intinc):
|
|
|
|
res = []
|
|
|
|
|
|
|
|
emit_state = [ None, None ] # curr_filename, curr_lineno
|
|
|
|
|
|
|
|
def emit(line):
|
|
|
|
if isinstance(line, (str, unicode)):
|
|
|
|
res.append(line)
|
|
|
|
emit_state[1] += 1
|
|
|
|
else:
|
|
|
|
if line.filename != emit_state[0] or line.lineno != emit_state[1]:
|
|
|
|
res.append('#line %d "%s"' % (line.lineno, line.filename))
|
|
|
|
res.append(line.data)
|
|
|
|
emit_state[0] = line.filename
|
|
|
|
emit_state[1] = line.lineno + 1
|
|
|
|
|
|
|
|
processed = {}
|
|
|
|
|
|
|
|
# Helper to process internal headers recursively, starting from duk_internal.h
|
|
|
|
def processHeader(f_hdr):
|
|
|
|
#print('Process header: ' + f_hdr.filename)
|
|
|
|
for line in f_hdr.lines:
|
|
|
|
m = re_intinc.match(line.data)
|
|
|
|
if m is None:
|
|
|
|
emit(line)
|
|
|
|
continue
|
|
|
|
incname = m.group(1)
|
|
|
|
if incname in [ 'duktape.h', 'duk_custom.h' ]:
|
|
|
|
# keep a few special headers as is
|
|
|
|
emit(line)
|
|
|
|
continue
|
|
|
|
|
|
|
|
#print('Include: ' + incname)
|
|
|
|
f_inc = findFile(files, incname)
|
|
|
|
assert(f_inc)
|
|
|
|
|
|
|
|
if processed.has_key(f_inc.filename):
|
|
|
|
#print('already included, skip: ' + f_inc.filename)
|
|
|
|
emit('/* already included: %s */' % f_inc.filename)
|
|
|
|
continue
|
|
|
|
processed[f_inc.filename] = True
|
|
|
|
|
|
|
|
# include file in this place, recursively
|
|
|
|
processHeader(f_inc)
|
|
|
|
|
|
|
|
# Process internal headers by starting with duk_internal.h
|
|
|
|
f_dukint = findFile(files, 'duk_internal.h')
|
|
|
|
assert(f_dukint)
|
|
|
|
processHeader(f_dukint)
|
|
|
|
|
|
|
|
# Mark all internal headers processed
|
|
|
|
for f in files:
|
|
|
|
if os.path.splitext(f.filename)[1] != '.h':
|
|
|
|
continue
|
|
|
|
processed[f.filename] = True
|
|
|
|
|
|
|
|
# Then emit remaining files
|
|
|
|
for f in files:
|
|
|
|
if processed.has_key(f.filename):
|
|
|
|
continue
|
|
|
|
|
|
|
|
for line in f.lines:
|
|
|
|
m = re_intinc.match(line.data)
|
|
|
|
if m is None:
|
|
|
|
emit(line)
|
|
|
|
else:
|
|
|
|
incname = m.group(1)
|
|
|
|
emit('/* include removed: %s */' % incname)
|
|
|
|
|
|
|
|
return '\n'.join(res) + '\n'
|
|
|
|
|
|
|
|
def main():
|
|
|
|
outname = sys.argv[2]
|
|
|
|
assert(outname)
|
|
|
|
|
|
|
|
print 'Read input files'
|
|
|
|
files = []
|
|
|
|
filelist = os.listdir(sys.argv[1])
|
|
|
|
filelist.sort() # for consistency
|
|
|
|
for fn in filelist:
|
|
|
|
if os.path.splitext(fn)[1] not in [ '.c', '.h' ]:
|
|
|
|
continue
|
|
|
|
res = read(os.path.join(sys.argv[1], fn))
|
|
|
|
files.append(res)
|
|
|
|
print '%d files read' % len(files)
|
|
|
|
|
|
|
|
print 'Process #include statements'
|
|
|
|
extinc = []
|
|
|
|
intinc = []
|
|
|
|
for f in files:
|
|
|
|
extnew, intnew = processIncludes(f)
|
|
|
|
for i in extnew:
|
|
|
|
if i in extinc:
|
|
|
|
continue
|
|
|
|
extinc.append(i)
|
|
|
|
for i in intnew:
|
|
|
|
if i in intinc:
|
|
|
|
continue
|
|
|
|
intinc.append(i)
|
|
|
|
|
|
|
|
#print('external includes: ' + ', '.join(extinc))
|
|
|
|
#print('internal includes: ' + ', '.join(intinc))
|
|
|
|
|
|
|
|
print 'Process declarations (non-exposed are converted to static)'
|
|
|
|
for f in files:
|
|
|
|
#processDeclarations(f)
|
|
|
|
pass
|
|
|
|
|
|
|
|
print 'Output final file'
|
|
|
|
final = createCombined(files, extinc, intinc)
|
|
|
|
f = open(outname, 'wb')
|
|
|
|
f.write(final)
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
print 'Wrote %d bytes to %s' % (len(final), outname)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
|
|
|
|