1 #! /usr/local/bin/python
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.
10 # Some problems remain:
11 # - It's highly non-portable, since it knows about paths and libraries
12 # (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).
15 # - 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.
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
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')
62 # Function to join two pathnames with a slash in between
65 ##################################
66 # START OF CONFIGURATION SECTION #
67 ##################################
69 # Attempt to guess machine architecture
70 if os
.path
.exists('/usr/lib/libgl_s'): ARCH
= 'sgi'
71 elif os
.path
.exists('/etc/issue'): ARCH
= 'sequent'
74 # Site parametrizations (change to match your site)
75 CC
= 'cc' # C compiler
76 TOP
= '/ufs/guido/src' # Parent of all source trees
77 PYTHON
= j(TOP
, 'python') # Top of the Python source tree
78 SRC
= j(PYTHON
, 'src') # Python source directory
79 BLD
= j(PYTHON
, 'build.' + ARCH
) # Python build directory
80 #BLD = SRC # Use this if you build in SRC
82 LIBINST
= '/ufs/guido/src/python/irix4/tmp/lib/python/lib' # installed libraries
83 INCLINST
= '/ufs/guido/src/python/irix4/tmp/include/Py' # installed include files
85 # Other packages (change to match your site)
86 DL
= j(TOP
, 'dl') # Top of the dl source tree
87 DL_DLD
= j(TOP
, 'dl-dld') # The dl-dld source directory
88 DLD
= j(TOP
, 'dld-3.2.3') # The dld source directory
89 FORMS
= j(TOP
, 'forms') # Top of the FORMS source tree
90 STDWIN
= j(TOP
, 'stdwin') # Top of the STDWIN source tree
91 READLINE
= j(TOP
, 'readline.' + ARCH
) # Top of the GNU Readline source tree
92 SUN_X11
= '/usr/local/X11R5/lib/libX11.a'
94 # File names (usually no need to change)
95 LIBP
= [ # Main Python libraries
96 j(LIBINST
, 'libPython.a'),
97 j(LIBINST
, 'libParser.a'),
98 j(LIBINST
, 'libObjects.a'),
99 j(LIBINST
, 'libModules.a')
101 CONFIG_IN
= j(LIBINST
, 'config.c.in') # Configuration source file
102 FMAIN
= j(LIBINST
, 'frozenmain.c') # Special main source file
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.
108 ('stdwin', j(STDWIN
, 'Build/' + ARCH
+ '/x11/lib/lib.a')), \
109 ('fl', j(FORMS
, 'FORMS/libforms.a'), '-lfm_s'), \
110 ('*', j(READLINE
, 'libreadline.a'), '-ltermcap'), \
112 ('sv', '-lsvideo', '-lXext'), \
113 ('cd', '-lcdaudio', '-lds'), \
115 ('imgfile', '-limage', '-lgutil', '-lm'), \
116 ('mpz', '/ufs/guido/src/gmp/libgmp.a'), \
118 ('*', j(DL
, 'libdl.a'), '-lmld'), \
121 ('gl', '-lgl_s', '-lX11_s'), \
122 ('stdwin', '-lX11_s'), \
128 ('stdwin', j(STDWIN
, 'Build/' + ARCH
+ '/x11/lib/lib.a')), \
129 ('*', j(READLINE
, 'libreadline.a')), \
131 ('*', j(DL_DLD
,'libdl.a'), j(DLD
,'libdld.a')), \
133 ('*', '-ltermcap'), \
136 libdeps_sequent
= [ \
137 ('*', j(LIBINST
, 'libreadline.a'), '-ltermcap'), \
144 libdeps
= eval('libdeps_' + ARCH
)
146 ################################
147 # END OF CONFIGURATION SECTION #
148 ################################
150 # Exception used when scanfile fails
151 NoSuchFile
= 'NoSuchFile'
158 ofile
= 'a.out' # -o file
160 # Main program -- argument parsing etc.
162 global quiet
, verbose
, noexec
, nowrite
, ofile
164 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'nNo:qv')
165 except getopt
.error
, msg
:
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
175 usage('please pass at least one file argument')
177 process(args
[0], args
[1:])
179 # Print usage message to stderr
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)'
190 # Process the script file
191 def process(filename
, addmodules
):
194 if not quiet
: print 'Computing needed modules ...'
196 todo
['__main__'] = filename
197 for name
in addmodules
:
198 mod
= os
.path
.basename(name
)
199 if mod
[-3:] == '.py': mod
= mod
[:-3]
203 except NoSuchFile
, filename
:
204 sys
.stderr
.write('Can\'t open file %s\n' % filename
)
211 print '%-15s %s' % ('Module', 'Filename')
213 print '%-15s %s' % (`mod`
, dict[mod
])
215 if not quiet
: print 'Looking for dynamically linked modules ...'
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'
226 f
= open(libsname
, 'r')
232 for lib
in string
.split(libtext
):
233 if lib
in libs
: libs
.remove(lib
)
237 if not quiet
: print 'Writing frozen.c ...'
238 writefrozen('frozen.c', dict)
240 if not quiet
: print 'NOT writing frozen.c ...'
245 if not quiet
: print 'Using existing', config
, '...'
247 config
= 'tmpconfig.c'
249 if not quiet
: print 'NOT writing config.c ...'
252 print 'Writing config.c with dl modules ...'
253 f
= open(CONFIG_IN
, 'r')
254 g
= open(config
, 'w')
255 m1
= regex
.compile('-- ADDMODULE MARKER 1 --')
256 m2
= regex
.compile('-- ADDMODULE MARKER 2 --')
258 stdmodules
= ('sys', '__main__', '__builtin__',
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
)
269 if m1
.search(line
) >= 0:
270 if verbose
: print 'Marker 1 ...'
271 for mod
in todomodules
:
272 g
.write('extern void init' + \
274 if m2
.search(line
) >= 0:
275 if verbose
: print 'Marker 2 ...'
276 for mod
in todomodules
:
277 g
.write('{"' + mod
+ \
278 '", init' + mod
+ '},\n')
282 if noexec
: print 'Generating compilation commands ...'
283 else: print 'Starting compilation ...'
284 defs
= ['-DNO_MAIN', '-DUSE_FROZEN', '-DPYTHONPATH=\'"."\'']
286 incs
= ['-I.', '-I' + INCLINST
]
287 if dict.has_key('stdwin'):
288 incs
.append('-I' + j(STDWIN
, 'H'))
290 srcs
= [config
, FMAIN
]
292 if type(LIBP
) == type(''):
299 if m
== '*' or dict.has_key(m
):
301 if l
in libs
: libs
.remove(l
)
307 cmd
= cmd
+ ' ' + string
.join(defs
)
308 cmd
= cmd
+ ' ' + string
.join(incs
)
309 cmd
= cmd
+ ' ' + string
.join(srcs
)
315 print 'Exit status', sts
, '-- turning on -n'
319 s
= os
.path
.basename(s
)
320 if s
[-2:] == '.c': s
= s
[:-2]
325 cmd
= cmd
+ ' ' + string
.join(objs
)
326 cmd
= cmd
+ ' ' + string
.join(libs
)
327 cmd
= cmd
+ ' -o ' + ofile
333 print 'Exit status', sts
337 if not quiet
and not noexec
and sts
== 0:
338 print 'Note: consider this:'; print '\tstrip', ofile
343 # Generate code for a given module
344 def makecode(filename
):
345 if filename
[-2:] == '.o':
348 f
= open(filename
, 'r')
351 if verbose
: print 'Making code from', filename
, '...'
353 code
= compile(text
, filename
, 'exec')
355 return marshal
.dumps(code
)
358 # Write the C source file containing the frozen Python code
359 def writefrozen(filename
, dict):
360 f
= open(filename
, 'w')
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
370 for mod
, codestring
in codelist
:
372 write('Writing initializer for %s\n'%mod
)
373 print 'static unsigned char M_' + mod
+ '[' + \
374 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
)) + ',',
380 print 'struct frozen {'
382 print ' unsigned char *code;'
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 */'
392 sys
.stdout
= save_stdout
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.)
403 # Compute the closure of scanfile() -- special first file because of script
408 for modname
in todo
.keys():
409 if not done
.has_key(modname
):
410 filename
= todo
[modname
]
412 filename
= findmodule(modname
)
413 done
[modname
] = filename
414 if filename
in ('<builtin>', '<unknown>'):
416 modules
= scanfile(filename
)
418 if not done
.has_key(m
):
423 # Scan a file looking for import statements
424 importstr
= '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)'
425 fromstr
= '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+'
426 isimport
= regex
.compile(importstr
)
427 isfrom
= regex
.compile(fromstr
)
428 def scanfile(filename
):
431 f
= open(filename
, 'r')
433 raise NoSuchFile
, filename
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)]
450 allmodules
[mod
] = None
452 return allmodules
.keys()
454 # Find the file containing a module, given its name; None if not found
455 builtins
= sys
.builtin_module_names
+ ['sys']
456 def findmodule(modname
):
457 if modname
in builtins
: return '<builtin>'
458 for dirname
in sys
.path
:
459 dlfullname
= os
.path
.join(dirname
, modname
+ 'module.o')
461 f
= open(dlfullname
, 'r')
467 fullname
= os
.path
.join(dirname
, modname
+ '.py')
469 f
= open(fullname
, 'r')
475 sys
.stderr
.write('Warning: module %s not found\n' % modname
)
479 # Call the main program