Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1913 | jaeger | 1 | import os |
2 | import sys |
||
3 | |||
4 | VARS = {} |
||
5 | TOPDIR = os.path.abspath(os.path.dirname(__file__)) |
||
6 | TEST = False |
||
7 | CLEAN = False |
||
8 | BOOT = False |
||
9 | CORE = ['tokenize','parse','encode','py2bc'] |
||
10 | MODULES = [] |
||
11 | |||
12 | def main(): |
||
13 | chksize() |
||
14 | if len(sys.argv) < 2: |
||
15 | print HELP |
||
16 | return |
||
17 | |||
18 | global TEST,CLEAN,BOOT |
||
19 | TEST = 'test' in sys.argv |
||
20 | CLEAN = 'clean' in sys.argv |
||
21 | BOOT = 'boot' in sys.argv |
||
22 | CLEAN = CLEAN or BOOT |
||
23 | TEST = TEST or BOOT |
||
24 | |||
25 | get_libs() |
||
26 | build_mymain() |
||
27 | |||
28 | cmd = sys.argv[1] |
||
29 | if cmd == 'linux': |
||
30 | vars_linux() |
||
31 | build_gcc() |
||
32 | elif cmd == 'mingw': |
||
33 | vars_windows() |
||
34 | build_gcc() |
||
35 | elif cmd == 'vs': |
||
36 | build_vs() |
||
37 | elif cmd == '64k': |
||
38 | build_64k() |
||
39 | elif cmd == 'blob': |
||
40 | build_blob() |
||
41 | else: |
||
42 | print 'invalid command' |
||
43 | |||
44 | HELP = """ |
||
45 | python setup.py command [options] [modules] |
||
46 | |||
47 | Commands: |
||
48 | linux - build tinypy for linux |
||
49 | mingw - build tinypy for mingw under windows |
||
50 | vs - build tinypy using Visual Studio 2005 / 2008 |
||
51 | |||
52 | 64k - build a 64k version of the tinypy source |
||
53 | blob - build a single tinypy.c and tinypy.h |
||
54 | |||
55 | build - build CPython module *** |
||
56 | install - install CPython module *** |
||
57 | |||
58 | Options: |
||
59 | test - run tests during build |
||
60 | clean - rebuild all .tpc during build |
||
61 | boot - fully bootstrap and test tinypy |
||
62 | |||
63 | Modules: |
||
64 | math - build math module |
||
65 | random - build random module * |
||
66 | pygame - build pygame module ** |
||
67 | marshal - build marshal module *** |
||
68 | jit - build jit module *** |
||
69 | re - build re module *** |
||
70 | |||
71 | * coming soon!! |
||
72 | ** proof-of-concept included |
||
73 | *** vaporware |
||
74 | """ |
||
75 | |||
76 | def vars_linux(): |
||
77 | VARS['$RM'] = 'rm -f' |
||
78 | VARS['$VM'] = './vm' |
||
79 | VARS['$TINYPY'] = './tinypy' |
||
80 | VARS['$SYS'] = '-linux' |
||
81 | VARS['$FLAGS'] = '' |
||
82 | |||
83 | VARS['$WFLAGS'] = '-std=c89 -Wall -Wc++-compat' |
||
84 | #-Wwrite-strings - i think this is included in -Wc++-compat |
||
85 | |||
86 | if 'pygame' in MODULES: |
||
87 | VARS['$FLAGS'] += ' `sdl-config --cflags --libs` ' |
||
88 | |||
89 | def vars_windows(): |
||
90 | VARS['$RM'] = 'del' |
||
91 | VARS['$VM'] = 'vm' |
||
92 | VARS['$TINYPY'] = 'tinypy' |
||
93 | VARS['$FLAGS'] = '-lmingw32' |
||
94 | VARS['$WFLAGS'] = '-Wwrite-strings -Wall' |
||
95 | VARS['$SYS'] = '-mingw32' |
||
96 | |||
97 | if 'pygame' in MODULES: |
||
98 | VARS['$FLAGS'] += ' -Ic:\\mingw\\include\\SDL -lSDLmain -lSDL ' |
||
99 | |||
100 | def do_cmd(cmd): |
||
101 | for k,v in VARS.items(): |
||
102 | cmd = cmd.replace(k,v) |
||
103 | if '$' in cmd: |
||
104 | print 'vars_error',cmd |
||
105 | sys.exit(-1) |
||
106 | |||
107 | print cmd |
||
108 | r = os.system(cmd) |
||
109 | if r: |
||
110 | print 'exit_status',r |
||
111 | sys.exit(r) |
||
112 | |||
113 | def do_chdir(dest): |
||
114 | print 'cd',dest |
||
115 | os.chdir(dest) |
||
116 | |||
117 | def build_bc(opt=False): |
||
118 | out = [] |
||
119 | for mod in CORE: |
||
120 | out.append("""unsigned char tp_%s[] = {"""%mod) |
||
121 | fname = mod+".tpc" |
||
122 | data = open(fname,'rb').read() |
||
123 | cols = 16 |
||
124 | for n in xrange(0,len(data),cols): |
||
125 | out.append(",".join([str(ord(v)) for v in data[n:n+cols]])+',') |
||
126 | out.append("""};""") |
||
127 | out.append("") |
||
128 | f = open('bc.c','wb') |
||
129 | f.write('\n'.join(out)) |
||
130 | f.close() |
||
131 | |||
132 | def open_tinypy(fname,*args): |
||
133 | return open(os.path.join(TOPDIR,'tinypy',fname),*args) |
||
134 | |||
135 | def build_blob(): |
||
136 | mods = CORE[:] |
||
137 | do_chdir(os.path.join(TOPDIR,'tinypy')) |
||
138 | for mod in mods: do_cmd('python py2bc.py %s.py %s.tpc'%(mod,mod)) |
||
139 | do_chdir(os.path.join(TOPDIR)) |
||
140 | |||
141 | out = [] |
||
142 | out.append("/*") |
||
143 | out.extend([v.rstrip() for v in open(os.path.join(TOPDIR,'LICENSE.txt'),'r')]) |
||
144 | out.append("*/") |
||
145 | out.append("") |
||
146 | |||
147 | out.append("#ifndef TINYPY_H") |
||
148 | out.append("#define TINYPY_H") |
||
149 | out.extend([v.rstrip() for v in open_tinypy('tp.h','r')]) |
||
150 | for fname in ['list.c','dict.c','misc.c','string.c','builtins.c', |
||
151 | 'gc.c','ops.c','vm.c','tp.c']: |
||
152 | for line in open_tinypy(fname,'r'): |
||
153 | line = line.rstrip() |
||
154 | if not len(line): continue |
||
155 | if line[0] == '/': continue |
||
156 | if line[0] == ' ': continue |
||
157 | if line[0] == '\t': continue |
||
158 | if line[-1] != '{': continue |
||
159 | if 'enum' in line: continue |
||
160 | if '=' in line: continue |
||
161 | if '#' in line: continue |
||
162 | line = line.replace('{',';') |
||
163 | |||
164 | # Do not include prototypes already defined earlier, or gcc will |
||
165 | # warn about doubled prototypes in user code. |
||
166 | if '(' in line: |
||
167 | line2 = line[:line.find('(') + 1] |
||
168 | got_already = False |
||
169 | for already in out: |
||
170 | if already.startswith(line2): |
||
171 | got_already = True |
||
172 | break |
||
173 | if got_already: continue |
||
174 | out.append(line) |
||
175 | out.append("#endif") |
||
176 | out.append('') |
||
177 | dest = os.path.join(TOPDIR,'build','tinypy.h') |
||
178 | print 'writing %s'%dest |
||
179 | f = open(dest,'w') |
||
180 | f.write('\n'.join(out)) |
||
181 | f.close() |
||
182 | |||
183 | # we leave all the tinypy.h stuff at the top so that |
||
184 | # if someone wants to include tinypy.c they don't have to have |
||
185 | # tinypy.h cluttering up their folder |
||
186 | |||
187 | for mod in CORE: |
||
188 | out.append("""extern unsigned char tp_%s[];"""%mod) |
||
189 | |||
190 | for fname in ['list.c','dict.c','misc.c','string.c','builtins.c', |
||
191 | 'gc.c','ops.c','vm.c','tp.c','bc.c']: |
||
192 | for line in open_tinypy(fname,'r'): |
||
193 | line = line.rstrip() |
||
194 | if line.find('#include "') != -1: continue |
||
195 | out.append(line) |
||
196 | out.append('') |
||
197 | dest = os.path.join(TOPDIR,'build','tinypy.c') |
||
198 | print 'writing %s'%dest |
||
199 | f = open(dest,'w') |
||
200 | f.write('\n'.join(out)) |
||
201 | f.close() |
||
202 | |||
203 | def py2bc(cmd,mod): |
||
204 | src = '%s.py'%mod |
||
205 | dest = '%s.tpc'%mod |
||
206 | if CLEAN or not os.path.exists(dest) or os.stat(src).st_mtime > os.stat(dest).st_mtime: |
||
207 | cmd = cmd.replace('$SRC',src) |
||
208 | cmd = cmd.replace('$DEST',dest) |
||
209 | do_cmd(cmd) |
||
210 | else: |
||
211 | print '#',dest,'is up to date' |
||
212 | |||
213 | def build_gcc(): |
||
214 | mods = CORE[:] |
||
215 | do_chdir(os.path.join(TOPDIR,'tinypy')) |
||
216 | if TEST: |
||
217 | mods.append('tests') |
||
218 | do_cmd("gcc $WFLAGS -g vmmain.c $FLAGS -lm -o vm") |
||
219 | do_cmd('python tests.py $SYS') |
||
220 | for mod in mods: |
||
221 | py2bc('python py2bc.py $SRC $DEST',mod) |
||
222 | else: |
||
223 | for mod in mods: |
||
224 | py2bc('python py2bc.py $SRC $DEST -nopos',mod) |
||
225 | if BOOT: |
||
226 | do_cmd('$VM tests.tpc $SYS') |
||
227 | for mod in mods: py2bc('$VM py2bc.tpc $SRC $DEST',mod) |
||
228 | build_bc() |
||
229 | do_cmd("gcc $WFLAGS -g tpmain.c $FLAGS -lm -o tinypy") |
||
230 | #second pass - builts optimized binaries and stuff |
||
231 | if BOOT: |
||
232 | do_cmd('$TINYPY tests.py $SYS') |
||
233 | for mod in mods: py2bc('$TINYPY py2bc.py $SRC $DEST -nopos',mod) |
||
234 | build_bc(True) |
||
235 | if BOOT: |
||
236 | do_cmd("gcc $WFLAGS -O2 tpmain.c $FLAGS -lm -o tinypy") |
||
237 | do_cmd('$TINYPY tests.py $SYS') |
||
238 | print("# OK - we'll try -O3 for extra speed ...") |
||
239 | do_cmd("gcc $WFLAGS -O3 tpmain.c $FLAGS -lm -o tinypy") |
||
240 | do_cmd('$TINYPY tests.py $SYS') |
||
241 | do_cmd("gcc $WFLAGS -O3 mymain.c $FLAGS -lm -o ../build/tinypy") |
||
242 | do_chdir('..') |
||
243 | if TEST: |
||
244 | test_mods(os.path.join('.','build','tinypy')+' $TESTS') |
||
245 | print("# OK") |
||
246 | |||
247 | def get_libs(): |
||
248 | modules = os.listdir('modules') |
||
249 | for m in modules[:]: |
||
250 | if m not in sys.argv: modules.remove(m) |
||
251 | global MODULES |
||
252 | MODULES = modules |
||
253 | |||
254 | def build_mymain(): |
||
255 | src = os.path.join(TOPDIR,'tinypy','tpmain.c') |
||
256 | out = open(src,'r').read() |
||
257 | dest = os.path.join(TOPDIR,'tinypy','mymain.c') |
||
258 | |||
259 | vs = [] |
||
260 | for m in MODULES: |
||
261 | vs.append('#include "../modules/%s/init.c"'%m) |
||
262 | out = out.replace('/* INCLUDE */','\n'.join(vs)) |
||
263 | |||
264 | vs = [] |
||
265 | for m in MODULES: |
||
266 | vs.append('%s_init(tp);'%m) |
||
267 | out = out.replace('/* INIT */','\n'.join(vs)) |
||
268 | |||
269 | f = open(dest,'w') |
||
270 | f.write(out) |
||
271 | f.close() |
||
272 | return True |
||
273 | |||
274 | def test_mods(cmd): |
||
275 | for m in MODULES: |
||
276 | tests = os.path.join('modules',m,'tests.py') |
||
277 | if not os.path.exists(tests): continue |
||
278 | cmd = cmd.replace('$TESTS',tests) |
||
279 | do_cmd(cmd) |
||
280 | |||
281 | def build_vs(): |
||
282 | # How to compile on windows with Visual Studio: |
||
283 | # Call the batch script that sets environement variables for Visual Studio and |
||
284 | # then run this script. |
||
285 | # For VS 2005 the script is: |
||
286 | # "C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat" |
||
287 | # For VS 2008: "C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat" |
||
288 | # Doesn't compile with vc6 (no variadic macros) |
||
289 | # Note: /MD option causes to dynamically link with msvcrt80.dll. This dramatically |
||
290 | # reduces size (for vm.exe 159k => 49k). Downside is that msvcrt80.dll must be |
||
291 | # present on the system (and not all windows machine have it). You can either re-distribute |
||
292 | # msvcrt80.dll or statically link with C runtime by changing /MD to /MT. |
||
293 | mods = CORE[:]; mods.append('tests') |
||
294 | os.chdir(os.path.join(TOPDIR,'tinypy')) |
||
295 | do_cmd('cl vmmain.c /D "inline=" /Od /Zi /MD /Fdvm.pdb /Fmvm.map /Fevm.exe') |
||
296 | do_cmd('python tests.py -win') |
||
297 | for mod in mods: do_cmd('python py2bc.py %s.py %s.tpc'%(mod,mod)) |
||
298 | do_cmd('vm.exe tests.tpc -win') |
||
299 | for mod in mods: do_cmd('vm.exe py2bc.tpc %s.py %s.tpc'%(mod,mod)) |
||
300 | build_bc() |
||
301 | do_cmd('cl /Od tpmain.c /D "inline=" /Zi /MD /Fdtinypy.pdb /Fmtinypy.map /Fetinypy.exe') |
||
302 | #second pass - builts optimized binaries and stuff |
||
303 | do_cmd('tinypy.exe tests.py -win') |
||
304 | for mod in mods: do_cmd('tinypy.exe py2bc.py %s.py %s.tpc -nopos'%(mod,mod)) |
||
305 | build_bc(True) |
||
306 | do_cmd('cl /Os vmmain.c /D "inline=__inline" /D "NDEBUG" /Gy /GL /Zi /MD /Fdvm.pdb /Fmvm.map /Fevm.exe /link /opt:ref /opt:icf') |
||
307 | do_cmd('cl /Os tpmain.c /D "inline=__inline" /D "NDEBUG" /Gy /GL /Zi /MD /Fdtinypy.pdb /Fmtinypy.map /Fetinypy.exe /link /opt:ref,icf /OPT:NOWIN98') |
||
308 | do_cmd("tinypy.exe tests.py -win") |
||
309 | do_cmd("dir *.exe") |
||
310 | |||
311 | def shrink(fname): |
||
312 | f = open(fname,'r'); lines = f.readlines(); f.close() |
||
313 | out = [] |
||
314 | fixes = [ |
||
315 | 'vm','gc','params','STR', |
||
316 | 'int','float','return','free','delete','init', |
||
317 | 'abs','round','system','pow','div','raise','hash','index','printf','main'] |
||
318 | passing = False |
||
319 | for line in lines: |
||
320 | #quit if we've already converted |
||
321 | if '\t' in line: return ''.join(lines) |
||
322 | |||
323 | #change " " into "\t" and remove blank lines |
||
324 | if len(line.strip()) == 0: continue |
||
325 | line = line.rstrip() |
||
326 | l1,l2 = len(line),len(line.lstrip()) |
||
327 | line = "\t"*((l1-l2)/4)+line.lstrip() |
||
328 | |||
329 | #remove comments |
||
330 | if '.c' in fname or '.h' in fname: |
||
331 | #start block comment |
||
332 | if line.strip()[:2] == '/*': |
||
333 | passing = True; |
||
334 | #end block comment |
||
335 | if line.strip()[-2:] == '*/': |
||
336 | passing = False; |
||
337 | continue |
||
338 | #skip lines inside block comments |
||
339 | if passing: |
||
340 | continue |
||
341 | if '.py' in fname: |
||
342 | if line.strip()[:1] == '#': continue |
||
343 | |||
344 | #remove the "namespace penalty" from tinypy ... |
||
345 | for name in fixes: |
||
346 | line = line.replace('TP_'+name,'t'+name) |
||
347 | line = line.replace('tp_'+name,'t'+name) |
||
348 | line = line.replace('TP_','') |
||
349 | line = line.replace('tp_','') |
||
350 | |||
351 | out.append(line) |
||
352 | return '\n'.join(out)+'\n' |
||
353 | |||
354 | def chksize(): |
||
355 | t1,t2 = 0,0 |
||
356 | for fname in [ |
||
357 | 'tokenize.py','parse.py','encode.py','py2bc.py', |
||
358 | 'tp.h','list.c','dict.c','misc.c','string.c','builtins.c', |
||
359 | 'gc.c','ops.c','vm.c','tp.c','tpmain.c', |
||
360 | ]: |
||
361 | fname = os.path.join(TOPDIR,'tinypy',fname) |
||
362 | f = open(fname,'r'); t1 += len(f.read()); f.close() |
||
363 | txt = shrink(fname) |
||
364 | t2 += len(txt) |
||
365 | print "#",t1,t2,t2-65536 |
||
366 | return t2 |
||
367 | |||
368 | def build_64k(): |
||
369 | for fname in [ |
||
370 | 'tokenize.py','parse.py','encode.py','py2bc.py', |
||
371 | 'tp.h','list.c','dict.c','misc.c','string.c','builtins.c', |
||
372 | 'gc.c','ops.c','vm.c','tp.c','tpmain.c', |
||
373 | ]: |
||
374 | src = os.path.join(TOPDIR,'tinypy',fname) |
||
375 | dest = os.path.join(TOPDIR,'build',fname) |
||
376 | txt = shrink(src) |
||
377 | f = open(dest,'w') |
||
378 | f.write(txt) |
||
379 | f.close() |
||
380 | print '%s saved to %s'%(src,dest) |
||
381 | |||
382 | if __name__ == '__main__': |
||
383 | main()> |