blob: 85ab5bc098af8ddc5a0a42fc53f65dbc6c9a3d17 [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
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'
71else: ARCH = 'sun4'
72
73# Site parametrizations (change to match your site)
74CC = 'cc' # C compiler
75TOP = '/ufs/guido/src' # Parent of all source trees
76PYTHON = j(TOP, 'python') # Top of the Python source tree
77SRC = j(PYTHON, 'src') # Python source directory
78BLD = j(PYTHON, 'build.' + ARCH) # Python build directory
79#BLD = SRC # Use this if you build in SRC
80
81# Other packages (change to match your site)
82DL = j(TOP, 'dl') # Top of the dl source tree
83DL_DLD = j(TOP, 'dl-dld') # The dl-dld source directory
84DLD = j(TOP, 'dld-3.2.3') # The dld source directory
85FORMS = j(TOP, 'forms') # Top of the FORMS source tree
86STDWIN = j(TOP, 'stdwin') # Top of the STDWIN source tree
87READLINE = j(TOP, 'readline.' + ARCH) # Top of the GNU Readline source tree
88SUN_X11 = '/usr/local/X11R5/lib/libX11.a'
89
90# File names (usually no need to change)
91LIBP = j(BLD, 'libpython.a') # Main Python library
92CONFIG = j(SRC, 'config.c') # Configuration source file
93FMAIN = j(SRC, 'frozenmain.c') # Special main source file
94
95# Libraries needed when linking. First tuple item is built-in module
96# for which it is needed (or '*' for always), rest are ld arguments.
97# There is a separate list per architecture.
98libdeps_sgi = [ \
99 ('stdwin', j(STDWIN, 'Build/' + ARCH + '/x11/lib/lib.a')), \
100 ('fl', j(FORMS, 'FORMS/libforms.a'), '-lfm_s'), \
Guido van Rossumdb392b91993-06-05 18:03:53 +0000101 ('*', j(READLINE, 'libreadline.a'), '-ltermcap'), \
Guido van Rossum5146ea31993-04-01 20:45:45 +0000102 ('al', '-laudio'), \
103 ('sv', '-lsvideo', '-lXext'), \
104 ('cd', '-lcdaudio', '-lds'), \
105 ('cl', '-lcl'), \
106 ('imgfile', '-limage', '-lgutil', '-lm'), \
Guido van Rossumdb392b91993-06-05 18:03:53 +0000107 ('mpz', '/ufs/jh/src/gmp-1.2/libgmp.a'), \
108 ('md5', '/ufs/jh/src/md5/md5.o'), \
109 ('*', '-lsun'), \
Guido van Rossum5146ea31993-04-01 20:45:45 +0000110 ('*', j(DL, 'libdl.a'), '-lmld'), \
111 ('*', '-lmpc'), \
112 ('fm', '-lfm_s'), \
Guido van Rossumdb392b91993-06-05 18:03:53 +0000113 ('gl', '-lgl_s', '-lX11_s'), \
114 ('stdwin', '-lX11_s'), \
115 ('*', '-lm'), \
Guido van Rossum5146ea31993-04-01 20:45:45 +0000116 ('*', '-lc_s'), \
117 ]
118libdeps_sun4 = [ \
119 ('*', '-Bstatic'), \
120 ('stdwin', j(STDWIN, 'Build/' + ARCH + '/x11/lib/lib.a')), \
121 ('*', j(READLINE, 'libreadline.a')), \
122 ('*', '-lm'), \
123 ('*', j(DL_DLD,'libdl.a'), j(DLD,'libdld.a')), \
124 ('*', SUN_X11), \
125 ('*', '-ltermcap'), \
126 ('*', '-lc'), \
127 ]
128libdeps = eval('libdeps_' + ARCH)
129
130################################
131# END OF CONFIGURATION SECTION #
132################################
133
134# Exception used when scanfile fails
135NoSuchFile = 'NoSuchFile'
136
137# Global options
138quiet = 0 # -q
139verbose = 0 # -v
140noexec = 0 # -n
141nowrite = 0 # -N
142ofile = 'a.out' # -o file
143
144# Main program -- argument parsing etc.
145def main():
146 global quiet, verbose, noexec, nowrite, ofile
147 try:
148 opts, args = getopt.getopt(sys.argv[1:], 'nNo:qv')
149 except getopt.error, msg:
150 usage(str(msg))
151 sys.exit(2)
152 for o, a in opts:
153 if o == '-n': noexec = 1
154 if o == '-N': nowrite = 1
155 if o == '-o': ofile = a
156 if o == '-q': verbose = 0; quiet = 1
157 if o == '-v': verbose = verbose + 1; quiet = 0
158 if len(args) < 1:
159 usage('please pass at least one file argument')
160 sys.exit(2)
161 process(args[0], args[1:])
162
163# Print usage message to stderr
164def usage(*msgs):
165 sys.stdout = sys.stderr
166 for msg in msgs: print msg
167 print 'Usage: freeze [options] scriptfile [modulefile ...]'
168 print '-n : generate the files but don\'t compile and link'
169 print '-N : don\'t write frozen.c (do compile unless -n given)'
170 print '-o file : binary output file (default a.out)'
171 print '-q : quiet (no messages at all except errors)'
172 print '-v : verbose (lots of extra messages)'
173
174# Process the script file
175def process(filename, addmodules):
176 global noexec
177 #
178 if not quiet: print 'Computing needed modules ...'
179 todo = {}
180 todo['__main__'] = filename
181 for name in addmodules:
182 mod = os.path.basename(name)
183 if mod[-3:] == '.py': mod = mod[:-3]
184 todo[mod] = name
185 try:
186 dict = closure(todo)
187 except NoSuchFile, filename:
188 sys.stderr.write('Can\'t open file %s\n' % filename)
189 sys.exit(1)
190 #
191 mods = dict.keys()
192 mods.sort()
193 #
194 if verbose:
195 print '%-15s %s' % ('Module', 'Filename')
196 for mod in mods:
197 print '%-15s %s' % (`mod`, dict[mod])
198 #
199 if not quiet: print 'Looking for dynamically linked modules ...'
200 dlmodules = []
201 objs = []
202 libs = []
203 for mod in mods:
204 if dict[mod][-2:] == '.o':
205 if verbose: print 'Found', mod, dict[mod]
206 dlmodules.append(mod)
207 objs.append(dict[mod])
208 libsname = dict[mod][:-2] + '.libs'
209 try:
210 f = open(libsname, 'r')
211 except IOError:
212 f = None
213 if f:
214 libtext = f.read()
215 f.close()
216 for lib in string.split(libtext):
217 if lib in libs: libs.remove(lib)
218 libs.append(lib)
219 #
220 if not nowrite:
221 if not quiet: print 'Writing frozen.c ...'
222 writefrozen('frozen.c', dict)
223 else:
224 if not quiet: print 'NOT writing frozen.c ...'
225 #
226 if not dlmodules:
227 config = CONFIG
Guido van Rossumdb392b91993-06-05 18:03:53 +0000228 if not quiet: print 'Using existing', config, '...'
Guido van Rossum5146ea31993-04-01 20:45:45 +0000229 else:
Guido van Rossumdb392b91993-06-05 18:03:53 +0000230 config = 'tmpconfig.c'
Guido van Rossum5146ea31993-04-01 20:45:45 +0000231 if nowrite:
232 if not quiet: print 'NOT writing config.c ...'
233 else:
234 if not quiet:
235 print 'Writing config.c with dl modules ...'
236 f = open(CONFIG, 'r')
237 g = open(config, 'w')
238 m1 = regex.compile('-- ADDMODULE MARKER 1 --')
239 m2 = regex.compile('-- ADDMODULE MARKER 2 --')
240 while 1:
241 line = f.readline()
242 if not line: break
243 g.write(line)
244 if m1.search(line) >= 0:
245 if verbose: print 'Marker 1 ...'
246 for mod in dlmodules:
247 g.write('extern void init' + \
248 mod + '();\n')
249 if m2.search(line) >= 0:
250 if verbose: print 'Marker 2 ...'
251 for mod in dlmodules:
252 g.write('{"' + mod + \
253 '", init' + mod + '},\n')
Guido van Rossumdb392b91993-06-05 18:03:53 +0000254 g.close()
Guido van Rossum5146ea31993-04-01 20:45:45 +0000255 #
Guido van Rossumdb392b91993-06-05 18:03:53 +0000256 if not quiet:
257 if noexec: print 'Generating compilation commands ...'
258 else: print 'Starting compilation ...'
Guido van Rossum5146ea31993-04-01 20:45:45 +0000259 defs = ['-DUSE_FROZEN', '-DPYTHONPATH=\'"."\'']
260 for mod in dict.keys():
261 if dict[mod] == '<builtin>' and mod <> 'sys':
262 defs.append('-DUSE_' + string.upper(mod))
263 #
264 incs = ['-I.', '-I' + SRC]
265 if dict.has_key('stdwin'):
266 incs.append('-I' + j(STDWIN, 'H'))
267 #
268 srcs = [config, FMAIN]
269 #
270 libs.append(LIBP)
271 for item in libdeps:
272 m = item[0]
273 if m == '*' or dict.has_key(m):
274 for l in item[1:]:
275 if l in libs: libs.remove(l)
276 libs.append(l)
277 #
278 sts = 0
279 #
280 cmd = CC + ' -c'
281 cmd = cmd + ' ' + string.join(defs)
282 cmd = cmd + ' ' + string.join(incs)
283 cmd = cmd + ' ' + string.join(srcs)
284 print cmd
285 #
286 if not noexec:
287 sts = os.system(cmd)
288 if sts:
289 print 'Exit status', sts, '-- turning on -n'
290 noexec = 1
291 #
292 for s in srcs:
293 s = os.path.basename(s)
294 if s[-2:] == '.c': s = s[:-2]
295 o = s + '.o'
296 objs.insert(0, o)
297 #
298 cmd = CC
299 cmd = cmd + ' ' + string.join(objs)
300 cmd = cmd + ' ' + string.join(libs)
301 cmd = cmd + ' -o ' + ofile
302 print cmd
303 #
304 if not noexec:
305 sts = os.system(cmd)
306 if sts:
307 print 'Exit status', sts
308 else:
309 print 'Done.'
310 #
Guido van Rossum032d3941994-01-07 10:55:55 +0000311 if not quiet and not noexec and sts == 0:
312 print 'Note: consider this:'; print '\tstrip', ofile
Guido van Rossum5146ea31993-04-01 20:45:45 +0000313 #
314 sys.exit(sts)
315
316
317# Generate code for a given module
318def makecode(filename):
319 if filename[-2:] == '.o':
320 return None
321 try:
322 f = open(filename, 'r')
323 except IOError:
324 return None
325 if verbose: print 'Making code from', filename, '...'
326 text = f.read()
327 code = compile(text, filename, 'exec')
328 f.close()
329 return marshal.dumps(code)
330
331
332# Write the C source file containing the frozen Python code
333def writefrozen(filename, dict):
334 f = open(filename, 'w')
335 codelist = []
336 for mod in dict.keys():
337 codestring = makecode(dict[mod])
338 if codestring is not None:
339 codelist.append((mod, codestring))
340 write = sys.stdout.write
341 save_stdout = sys.stdout
342 try:
343 sys.stdout = f
344 for mod, codestring in codelist:
345 if verbose:
346 write('Writing initializer for %s\n'%mod)
347 print 'static char M_' + mod + '[' + \
348 str(len(codestring)) + '+1] = {'
349 for i in range(0, len(codestring), 16):
350 for c in codestring[i:i+16]:
351 print str(ord(c)) + ',',
352 print
353 print '};'
354 print 'struct frozen {'
355 print ' char *name;'
356 print ' char *code;'
357 print ' int size;'
358 print '} frozen_modules[] = {'
359 for mod, codestring in codelist:
360 print ' {"' + mod + '",',
361 print 'M_' + mod + ',',
362 print str(len(codestring)) + '},'
363 print ' {0, 0, 0} /* sentinel */'
364 print '};'
365 finally:
366 sys.stdout = save_stdout
367 f.close()
368
369
370# Determine the names and filenames of the modules imported by the
371# script, recursively. This is done by scanning for lines containing
372# import statements. (The scanning has only superficial knowledge of
373# Python syntax and no knowledge of semantics, so in theory the result
374# may be incorrect -- however this is quite unlikely if you don't
375# intentionally obscure your Python code.)
376
377# Compute the closure of scanfile() -- special first file because of script
378def closure(todo):
379 done = {}
380 while todo:
381 newtodo = {}
382 for modname in todo.keys():
383 if not done.has_key(modname):
384 filename = todo[modname]
385 if filename is None:
386 filename = findmodule(modname)
387 done[modname] = filename
388 if filename in ('<builtin>', '<unknown>'):
389 continue
390 modules = scanfile(filename)
391 for m in modules:
392 if not done.has_key(m):
393 newtodo[m] = None
394 todo = newtodo
395 return done
396
397# Scan a file looking for import statements
398importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)'
399fromstr = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+'
400isimport = regex.compile(importstr)
401isfrom = regex.compile(fromstr)
402def scanfile(filename):
403 allmodules = {}
404 try:
405 f = open(filename, 'r')
406 except IOError, msg:
407 raise NoSuchFile, filename
408 while 1:
409 line = f.readline()
410 if not line: break # EOF
411 while line[-2:] == '\\\n': # Continuation line
412 line = line[:-2] + ' '
413 line = line + f.readline()
414 if isimport.search(line) >= 0:
415 rawmodules = isimport.group(2)
416 modules = string.splitfields(rawmodules, ',')
417 for i in range(len(modules)):
418 modules[i] = string.strip(modules[i])
419 elif isfrom.search(line) >= 0:
420 modules = [isfrom.group(2)]
421 else:
422 continue
423 for mod in modules:
424 allmodules[mod] = None
425 f.close()
426 return allmodules.keys()
427
428# Find the file containing a module, given its name; None if not found
429builtins = sys.builtin_module_names + ['sys']
430def findmodule(modname):
431 if modname in builtins: return '<builtin>'
432 for dirname in sys.path:
433 dlfullname = os.path.join(dirname, modname + 'module.o')
434 try:
435 f = open(dlfullname, 'r')
436 except IOError:
437 f = None
438 if f:
439 f.close()
440 return dlfullname
441 fullname = os.path.join(dirname, modname + '.py')
442 try:
443 f = open(fullname, 'r')
444 except IOError:
445 continue
446 f.close()
447 return fullname
448 if not quiet:
449 sys.stderr.write('Warning: module %s not found\n' % modname)
450 return '<unknown>'
451
452
453# Call the main program
454main()