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