blob: 3467ed4fca45eee8366562a05407979171a46694 [file] [log] [blame]
Guido van Rossum5146ea31993-04-01 20:45:45 +00001#! /ufs/guido/bin/sgi/python
2
3# Given a Python script, create a binary that runs the script.
4# The binary is 100% independent of Python libraries and binaries.
5# It will not contain any Python source code -- only "compiled" Python
6# (as initialized static variables containing marshalled code objects).
7# It even does the right thing for dynamically loaded modules!
8# The module search path of the binary is set to the current directory.
9#
10# Some problems remain:
11# - It's highly non-portable, since it knows about paths and libraries
12# (there's a customization section though).
13# - You need to have the Python source tree lying around as well as
14# the "libpython.a" used to generate the Python binary.
15# - For scripts that use many modules it generates absurdly large
16# files (frozen.c and config.o as well as the final binary),
17# and is consequently rather slow.
18#
19# Caveats:
20# - The search for modules sometimes finds modules that are never
21# actually imported since the code importing them is never executed.
22# - If an imported module isn't found, you get a warning but the
23# process of freezing continues. The binary will fail if it
24# actually tries to import one of these modules.
25# - This often happens with the module 'mac', which module 'os' tries
26# to import (to determine whether it is running on a Macintosh).
27# You can ignore the warning about this.
28# - If the program dynamically reads or generates Python code and
29# executes it, this code may reference built-in or library modules
30# that aren't present in the frozen binary, and this will fail.
31# - Your program may be using external data files, e.g. compiled
32# forms definitions (*.fd). These aren't incorporated. Since
33# sys.path in the resulting binary only contains '.', if your
34# program searches its data files along sys.path (as the 'flp'
35# modules does to find its forms definitions), you may need to
36# change the program to extend the search path or instruct its users
37# to set the environment variable PYTHONPATH to point to your data
38# files.
39#
40# Usage hints:
41# - If you have a bunch of scripts that you want to freeze, instead
42# of freezing each of them separately, you might consider writing
43# a tiny main script that looks at sys.argv[0] and then imports
44# the corresponding module. You can then make links to the
45# frozen binary named after the various scripts you support.
46# Pass the additional scripts as arguments after the main script.
47# A minimal script to do this is the following.
48# import sys, posixpath
49# exec('import ' + posixpath.basename(sys.argv[0]) + '\n')
50
51
52import os
53import sys
54import regex
55import getopt
56import regsub
57import string
58import marshal
59
60# Function to join two pathnames with a slash in between
61j = os.path.join
62
63##################################
64# START OF CONFIGURATION SECTION #
65##################################
66
67# Attempt to guess machine architecture
68if os.path.exists('/usr/lib/libgl_s'): ARCH = 'sgi'
69else: ARCH = 'sun4'
70
71# Site parametrizations (change to match your site)
72CC = 'cc' # C compiler
73TOP = '/ufs/guido/src' # Parent of all source trees
74PYTHON = j(TOP, 'python') # Top of the Python source tree
75SRC = j(PYTHON, 'src') # Python source directory
76BLD = j(PYTHON, 'build.' + ARCH) # Python build directory
77#BLD = SRC # Use this if you build in SRC
78
79# Other packages (change to match your site)
80DL = j(TOP, 'dl') # Top of the dl source tree
81DL_DLD = j(TOP, 'dl-dld') # The dl-dld source directory
82DLD = j(TOP, 'dld-3.2.3') # The dld source directory
83FORMS = j(TOP, 'forms') # Top of the FORMS source tree
84STDWIN = j(TOP, 'stdwin') # Top of the STDWIN source tree
85READLINE = j(TOP, 'readline.' + ARCH) # Top of the GNU Readline source tree
86SUN_X11 = '/usr/local/X11R5/lib/libX11.a'
87
88# File names (usually no need to change)
89LIBP = j(BLD, 'libpython.a') # Main Python library
90CONFIG = j(SRC, 'config.c') # Configuration source file
91FMAIN = j(SRC, 'frozenmain.c') # Special main source file
92
93# Libraries needed when linking. First tuple item is built-in module
94# for which it is needed (or '*' for always), rest are ld arguments.
95# There is a separate list per architecture.
96libdeps_sgi = [ \
97 ('stdwin', j(STDWIN, 'Build/' + ARCH + '/x11/lib/lib.a')), \
98 ('fl', j(FORMS, 'FORMS/libforms.a'), '-lfm_s'), \
99 ('*', j(READLINE, 'libreadline.a')), \
100 ('*', '-lm'), \
101 ('*', '-lsun'), \
102 ('al', '-laudio'), \
103 ('sv', '-lsvideo', '-lXext'), \
104 ('cd', '-lcdaudio', '-lds'), \
105 ('cl', '-lcl'), \
106 ('imgfile', '-limage', '-lgutil', '-lm'), \
107 ('*', j(DL, 'libdl.a'), '-lmld'), \
108 ('*', '-lmpc'), \
109 ('fm', '-lfm_s'), \
110 ('gl', '-lgl_s'), \
111 ('*', '-lX11_s'), \
112 ('*', '-ltermcap'), \
113 ('*', '-lc_s'), \
114 ]
115libdeps_sun4 = [ \
116 ('*', '-Bstatic'), \
117 ('stdwin', j(STDWIN, 'Build/' + ARCH + '/x11/lib/lib.a')), \
118 ('*', j(READLINE, 'libreadline.a')), \
119 ('*', '-lm'), \
120 ('*', j(DL_DLD,'libdl.a'), j(DLD,'libdld.a')), \
121 ('*', SUN_X11), \
122 ('*', '-ltermcap'), \
123 ('*', '-lc'), \
124 ]
125libdeps = eval('libdeps_' + ARCH)
126
127################################
128# END OF CONFIGURATION SECTION #
129################################
130
131# Exception used when scanfile fails
132NoSuchFile = 'NoSuchFile'
133
134# Global options
135quiet = 0 # -q
136verbose = 0 # -v
137noexec = 0 # -n
138nowrite = 0 # -N
139ofile = 'a.out' # -o file
140
141# Main program -- argument parsing etc.
142def main():
143 global quiet, verbose, noexec, nowrite, ofile
144 try:
145 opts, args = getopt.getopt(sys.argv[1:], 'nNo:qv')
146 except getopt.error, msg:
147 usage(str(msg))
148 sys.exit(2)
149 for o, a in opts:
150 if o == '-n': noexec = 1
151 if o == '-N': nowrite = 1
152 if o == '-o': ofile = a
153 if o == '-q': verbose = 0; quiet = 1
154 if o == '-v': verbose = verbose + 1; quiet = 0
155 if len(args) < 1:
156 usage('please pass at least one file argument')
157 sys.exit(2)
158 process(args[0], args[1:])
159
160# Print usage message to stderr
161def usage(*msgs):
162 sys.stdout = sys.stderr
163 for msg in msgs: print msg
164 print 'Usage: freeze [options] scriptfile [modulefile ...]'
165 print '-n : generate the files but don\'t compile and link'
166 print '-N : don\'t write frozen.c (do compile unless -n given)'
167 print '-o file : binary output file (default a.out)'
168 print '-q : quiet (no messages at all except errors)'
169 print '-v : verbose (lots of extra messages)'
170
171# Process the script file
172def process(filename, addmodules):
173 global noexec
174 #
175 if not quiet: print 'Computing needed modules ...'
176 todo = {}
177 todo['__main__'] = filename
178 for name in addmodules:
179 mod = os.path.basename(name)
180 if mod[-3:] == '.py': mod = mod[:-3]
181 todo[mod] = name
182 try:
183 dict = closure(todo)
184 except NoSuchFile, filename:
185 sys.stderr.write('Can\'t open file %s\n' % filename)
186 sys.exit(1)
187 #
188 mods = dict.keys()
189 mods.sort()
190 #
191 if verbose:
192 print '%-15s %s' % ('Module', 'Filename')
193 for mod in mods:
194 print '%-15s %s' % (`mod`, dict[mod])
195 #
196 if not quiet: print 'Looking for dynamically linked modules ...'
197 dlmodules = []
198 objs = []
199 libs = []
200 for mod in mods:
201 if dict[mod][-2:] == '.o':
202 if verbose: print 'Found', mod, dict[mod]
203 dlmodules.append(mod)
204 objs.append(dict[mod])
205 libsname = dict[mod][:-2] + '.libs'
206 try:
207 f = open(libsname, 'r')
208 except IOError:
209 f = None
210 if f:
211 libtext = f.read()
212 f.close()
213 for lib in string.split(libtext):
214 if lib in libs: libs.remove(lib)
215 libs.append(lib)
216 #
217 if not nowrite:
218 if not quiet: print 'Writing frozen.c ...'
219 writefrozen('frozen.c', dict)
220 else:
221 if not quiet: print 'NOT writing frozen.c ...'
222 #
223 if not dlmodules:
224 config = CONFIG
225 if not quiet: print 'Using', config, '...'
226 else:
227 config = 'config.c'
228 if nowrite:
229 if not quiet: print 'NOT writing config.c ...'
230 else:
231 if not quiet:
232 print 'Writing config.c with dl modules ...'
233 f = open(CONFIG, 'r')
234 g = open(config, 'w')
235 m1 = regex.compile('-- ADDMODULE MARKER 1 --')
236 m2 = regex.compile('-- ADDMODULE MARKER 2 --')
237 while 1:
238 line = f.readline()
239 if not line: break
240 g.write(line)
241 if m1.search(line) >= 0:
242 if verbose: print 'Marker 1 ...'
243 for mod in dlmodules:
244 g.write('extern void init' + \
245 mod + '();\n')
246 if m2.search(line) >= 0:
247 if verbose: print 'Marker 2 ...'
248 for mod in dlmodules:
249 g.write('{"' + mod + \
250 '", init' + mod + '},\n')
251 #
252 if not quiet: print 'Readying for compilation ...'
253 defs = ['-DUSE_FROZEN', '-DPYTHONPATH=\'"."\'']
254 for mod in dict.keys():
255 if dict[mod] == '<builtin>' and mod <> 'sys':
256 defs.append('-DUSE_' + string.upper(mod))
257 #
258 incs = ['-I.', '-I' + SRC]
259 if dict.has_key('stdwin'):
260 incs.append('-I' + j(STDWIN, 'H'))
261 #
262 srcs = [config, FMAIN]
263 #
264 libs.append(LIBP)
265 for item in libdeps:
266 m = item[0]
267 if m == '*' or dict.has_key(m):
268 for l in item[1:]:
269 if l in libs: libs.remove(l)
270 libs.append(l)
271 #
272 sts = 0
273 #
274 cmd = CC + ' -c'
275 cmd = cmd + ' ' + string.join(defs)
276 cmd = cmd + ' ' + string.join(incs)
277 cmd = cmd + ' ' + string.join(srcs)
278 print cmd
279 #
280 if not noexec:
281 sts = os.system(cmd)
282 if sts:
283 print 'Exit status', sts, '-- turning on -n'
284 noexec = 1
285 #
286 for s in srcs:
287 s = os.path.basename(s)
288 if s[-2:] == '.c': s = s[:-2]
289 o = s + '.o'
290 objs.insert(0, o)
291 #
292 cmd = CC
293 cmd = cmd + ' ' + string.join(objs)
294 cmd = cmd + ' ' + string.join(libs)
295 cmd = cmd + ' -o ' + ofile
296 print cmd
297 #
298 if not noexec:
299 sts = os.system(cmd)
300 if sts:
301 print 'Exit status', sts
302 else:
303 print 'Done.'
304 #
305 if not quiet: print 'Note: consider this:'; print 'strip', ofile
306 #
307 sys.exit(sts)
308
309
310# Generate code for a given module
311def makecode(filename):
312 if filename[-2:] == '.o':
313 return None
314 try:
315 f = open(filename, 'r')
316 except IOError:
317 return None
318 if verbose: print 'Making code from', filename, '...'
319 text = f.read()
320 code = compile(text, filename, 'exec')
321 f.close()
322 return marshal.dumps(code)
323
324
325# Write the C source file containing the frozen Python code
326def writefrozen(filename, dict):
327 f = open(filename, 'w')
328 codelist = []
329 for mod in dict.keys():
330 codestring = makecode(dict[mod])
331 if codestring is not None:
332 codelist.append((mod, codestring))
333 write = sys.stdout.write
334 save_stdout = sys.stdout
335 try:
336 sys.stdout = f
337 for mod, codestring in codelist:
338 if verbose:
339 write('Writing initializer for %s\n'%mod)
340 print 'static char M_' + mod + '[' + \
341 str(len(codestring)) + '+1] = {'
342 for i in range(0, len(codestring), 16):
343 for c in codestring[i:i+16]:
344 print str(ord(c)) + ',',
345 print
346 print '};'
347 print 'struct frozen {'
348 print ' char *name;'
349 print ' char *code;'
350 print ' int size;'
351 print '} frozen_modules[] = {'
352 for mod, codestring in codelist:
353 print ' {"' + mod + '",',
354 print 'M_' + mod + ',',
355 print str(len(codestring)) + '},'
356 print ' {0, 0, 0} /* sentinel */'
357 print '};'
358 finally:
359 sys.stdout = save_stdout
360 f.close()
361
362
363# Determine the names and filenames of the modules imported by the
364# script, recursively. This is done by scanning for lines containing
365# import statements. (The scanning has only superficial knowledge of
366# Python syntax and no knowledge of semantics, so in theory the result
367# may be incorrect -- however this is quite unlikely if you don't
368# intentionally obscure your Python code.)
369
370# Compute the closure of scanfile() -- special first file because of script
371def closure(todo):
372 done = {}
373 while todo:
374 newtodo = {}
375 for modname in todo.keys():
376 if not done.has_key(modname):
377 filename = todo[modname]
378 if filename is None:
379 filename = findmodule(modname)
380 done[modname] = filename
381 if filename in ('<builtin>', '<unknown>'):
382 continue
383 modules = scanfile(filename)
384 for m in modules:
385 if not done.has_key(m):
386 newtodo[m] = None
387 todo = newtodo
388 return done
389
390# Scan a file looking for import statements
391importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)'
392fromstr = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+'
393isimport = regex.compile(importstr)
394isfrom = regex.compile(fromstr)
395def scanfile(filename):
396 allmodules = {}
397 try:
398 f = open(filename, 'r')
399 except IOError, msg:
400 raise NoSuchFile, filename
401 while 1:
402 line = f.readline()
403 if not line: break # EOF
404 while line[-2:] == '\\\n': # Continuation line
405 line = line[:-2] + ' '
406 line = line + f.readline()
407 if isimport.search(line) >= 0:
408 rawmodules = isimport.group(2)
409 modules = string.splitfields(rawmodules, ',')
410 for i in range(len(modules)):
411 modules[i] = string.strip(modules[i])
412 elif isfrom.search(line) >= 0:
413 modules = [isfrom.group(2)]
414 else:
415 continue
416 for mod in modules:
417 allmodules[mod] = None
418 f.close()
419 return allmodules.keys()
420
421# Find the file containing a module, given its name; None if not found
422builtins = sys.builtin_module_names + ['sys']
423def findmodule(modname):
424 if modname in builtins: return '<builtin>'
425 for dirname in sys.path:
426 dlfullname = os.path.join(dirname, modname + 'module.o')
427 try:
428 f = open(dlfullname, 'r')
429 except IOError:
430 f = None
431 if f:
432 f.close()
433 return dlfullname
434 fullname = os.path.join(dirname, modname + '.py')
435 try:
436 f = open(fullname, 'r')
437 except IOError:
438 continue
439 f.close()
440 return fullname
441 if not quiet:
442 sys.stderr.write('Warning: module %s not found\n' % modname)
443 return '<unknown>'
444
445
446# Call the main program
447main()