-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcombine_src.py
252 lines (213 loc) · 6.37 KB
/
combine_src.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#!/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()