blob: c17c33f83d21529bb0fecd76f147e013d8b9ae1b [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 #
311 if not quiet: print 'Note: consider this:'; print 'strip', ofile
312 #
313 sys.exit(sts)
314
315
316# Generate code for a given module
317def makecode(filename):
318 if filename[-2:] == '.o':
319 return None
320 try:
321 f = open(filename, 'r')
322 except IOError:
323 return None
324 if verbose: print 'Making code from', filename, '...'
325 text = f.read()
326 code = compile(text, filename, 'exec')
327 f.close()
328 return marshal.dumps(code)
329
330
331# Write the C source file containing the frozen Python code
332def writefrozen(filename, dict):
333 f = open(filename, 'w')
334 codelist = []
335 for mod in dict.keys():
336 codestring = makecode(dict[mod])
337 if codestring is not None:
338 codelist.append((mod, codestring))
339 write = sys.stdout.write
340 save_stdout = sys.stdout
341 try:
342 sys.stdout = f
343 for mod, codestring in codelist:
344 if verbose:
345 write('Writing initializer for %s\n'%mod)
346 print 'static char M_' + mod + '[' + \
347 str(len(codestring)) + '+1] = {'
348 for i in range(0, len(codestring), 16):
349 for c in codestring[i:i+16]:
350 print str(ord(c)) + ',',
351 print
352 print '};'
353 print 'struct frozen {'
354 print ' char *name;'
355 print ' char *code;'
356 print ' int size;'
357 print '} frozen_modules[] = {'
358 for mod, codestring in codelist:
359 print ' {"' + mod + '",',
360 print 'M_' + mod + ',',
361 print str(len(codestring)) + '},'
362 print ' {0, 0, 0} /* sentinel */'
363 print '};'
364 finally:
365 sys.stdout = save_stdout
366 f.close()
367
368
369# Determine the names and filenames of the modules imported by the
370# script, recursively. This is done by scanning for lines containing
371# import statements. (The scanning has only superficial knowledge of
372# Python syntax and no knowledge of semantics, so in theory the result
373# may be incorrect -- however this is quite unlikely if you don't
374# intentionally obscure your Python code.)
375
376# Compute the closure of scanfile() -- special first file because of script
377def closure(todo):
378 done = {}
379 while todo:
380 newtodo = {}
381 for modname in todo.keys():
382 if not done.has_key(modname):
383 filename = todo[modname]
384 if filename is None:
385 filename = findmodule(modname)
386 done[modname] = filename
387 if filename in ('<builtin>', '<unknown>'):
388 continue
389 modules = scanfile(filename)
390 for m in modules:
391 if not done.has_key(m):
392 newtodo[m] = None
393 todo = newtodo
394 return done
395
396# Scan a file looking for import statements
397importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)'
398fromstr = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+'
399isimport = regex.compile(importstr)
400isfrom = regex.compile(fromstr)
401def scanfile(filename):
402 allmodules = {}
403 try:
404 f = open(filename, 'r')
405 except IOError, msg:
406 raise NoSuchFile, filename
407 while 1:
408 line = f.readline()
409 if not line: break # EOF
410 while line[-2:] == '\\\n': # Continuation line
411 line = line[:-2] + ' '
412 line = line + f.readline()
413 if isimport.search(line) >= 0:
414 rawmodules = isimport.group(2)
415 modules = string.splitfields(rawmodules, ',')
416 for i in range(len(modules)):
417 modules[i] = string.strip(modules[i])
418 elif isfrom.search(line) >= 0:
419 modules = [isfrom.group(2)]
420 else:
421 continue
422 for mod in modules:
423 allmodules[mod] = None
424 f.close()
425 return allmodules.keys()
426
427# Find the file containing a module, given its name; None if not found
428builtins = sys.builtin_module_names + ['sys']
429def findmodule(modname):
430 if modname in builtins: return '<builtin>'
431 for dirname in sys.path:
432 dlfullname = os.path.join(dirname, modname + 'module.o')
433 try:
434 f = open(dlfullname, 'r')
435 except IOError:
436 f = None
437 if f:
438 f.close()
439 return dlfullname
440 fullname = os.path.join(dirname, modname + '.py')
441 try:
442 f = open(fullname, 'r')
443 except IOError:
444 continue
445 f.close()
446 return fullname
447 if not quiet:
448 sys.stderr.write('Warning: module %s not found\n' % modname)
449 return '<unknown>'
450
451
452# Call the main program
453main()