mirror of https://github.com/svaarala/duktape.git
Sami Vaarala
11 years ago
2 changed files with 259 additions and 1 deletions
@ -0,0 +1,246 @@ |
|||
#!/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. |
|||
# |
|||
# 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 = [] |
|||
for fn in os.listdir(sys.argv[1]): |
|||
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() |
|||
|
Loading…
Reference in new issue