blob: 3706e734e5a9850ead9177c9883c6c43a45effcc [file] [log] [blame]
Guido van Rossume9563861995-04-05 10:58:52 +00001#! /usr/local/bin/python
2
3# "Freeze" a Python script into a binary.
4# Usage: see variable usage_msg below (before the imports!)
5
6# This version builds the frozen binary in a temporary directory;
7# courtesy Jaap Vermeulen. Use the -l option to get the functionality
8# of the standard version.
9
10# HINTS:
11# - Edit the lines marked XXX below to localize.
12# - You must have done "make inclinstall libainstall" in the Python
13# build directory.
14# - The script should not use dynamically loaded modules
15# (*.so on most systems).
16
17
18# Usage message
19
20usage_msg = """
21usage: freeze [-p prefix] [-e extension] [-l] ... script [module] ...
22
23-p prefix: This is the prefix used when you ran
24 'Make inclinstall libainstall' in the Python build directory.
25 (If you never ran this, freeze won't work.)
26 The default is /usr/local.
27
28-e extension: A directory containing additional .o files that
29 may be used to resolve modules. This directory
30 should also have a Setup file describing the .o files.
31 More than one -e option may be given.
32
33-l: Local compilation. Instead of using a temporary directory
34 that is removed after succesful compilation, the current
35 directory is used and temporary files are not removed.
36
37script: The Python script to be executed by the resulting binary.
38
39module ...: Additional Python modules (referenced by pathname)
40 that will be included in the resulting binary. These
41 may be .py or .pyc files.
42"""
43
44
45# XXX Change the following line to point to your Tools/freeze directory
46PACK = '/ufs/guido/src/python/Tools/freeze'
47
48# XXX Change the following line to point to your install prefix
49PREFIX = '/usr/local'
50
51# XXX Change the following line to point to your favority temporary directory
52TMPDIR = '/usr/tmp'
53
54
55# Import standard modules
56
57import cmp
58import getopt
59import os
60import string
61import sys
62import addpack
63
64
65# Set the directory to look for the freeze-private modules
66
67dir = os.path.dirname(sys.argv[0])
68if dir:
69 pack = dir
70else:
71 pack = PACK
72addpack.addpack(pack)
73
74
75# Establish temporary directory name
76
77tmpdir = os.path.join(TMPDIR, 'freeze.' + `os.getpid()`)
78
79
80# Import the freeze-private modules
81
82import checkextensions
83import findmodules
84import makeconfig
85import makefreeze
86import makemakefile
87import parsesetup
88
89
90# Main program
91
92def main():
93 # module variable
94 global tmpdir
95
96 # overridable context
97 prefix = PREFIX # settable with -p option
98 extensions = []
99 path = sys.path
100
101 # output files
102 frozen_c = 'frozen.c'
103 config_c = 'config.c'
104 target = 'a.out' # normally derived from script name
105 makefile = 'Makefile'
106
107 # parse command line
108 try:
109 opts, args = getopt.getopt(sys.argv[1:], 'e:p:l')
110 except getopt.error, msg:
111 usage('getopt error: ' + str(msg))
112
113 # proces option arguments
114 for o, a in opts:
115 if o == '-e':
116 extensions.append(a)
117 if o == '-l':
118 tmpdir = None
119 if o == '-p':
120 prefix = a
121
122 # locations derived from options
123 binlib = os.path.join(prefix, 'lib/python/lib')
124 incldir = os.path.join(prefix, 'include/Py')
125 config_c_in = os.path.join(binlib, 'config.c.in')
126 frozenmain_c = os.path.join(binlib, 'frozenmain.c')
127 makefile_in = os.path.join(binlib, 'Makefile')
128 defines = ['-DHAVE_CONFIG_H', '-DUSE_FROZEN', '-DNO_MAIN',
129 '-DPYTHONPATH=\\"$(PYTHONPATH)\\"']
130 includes = ['-I' + incldir, '-I' + binlib]
131
132 # sanity check of directories and files
133 for dir in [prefix, binlib, incldir] + extensions:
134 if not os.path.exists(dir):
135 usage('needed directory %s not found' % dir)
136 if not os.path.isdir(dir):
137 usage('%s: not a directory' % dir)
138 for file in config_c_in, makefile_in, frozenmain_c:
139 if not os.path.exists(file):
140 usage('needed file %s not found' % file)
141 if not os.path.isfile(file):
142 usage('%s: not a plain file' % file)
143 for dir in extensions:
144 setup = os.path.join(dir, 'Setup')
145 if not os.path.exists(setup):
146 usage('needed file %s not found' % setup)
147 if not os.path.isfile(setup):
148 usage('%s: not a plain file' % setup)
149
150 # check that enough arguments are passed
151 if not args:
152 usage('at least one filename argument required')
153
154 # check that file arguments exist
155 for arg in args:
156 if not os.path.exists(arg):
157 usage('argument %s not found' % arg)
158 if not os.path.isfile(arg):
159 usage('%s: not a plain file' % arg)
160
161 # process non-option arguments
162 scriptfile = args[0]
163 modules = args[1:]
164
165 # derive target name from script name
166 base = os.path.basename(scriptfile)
167 base, ext = os.path.splitext(base)
168 if base:
169 if base != scriptfile:
170 target = base
171 else:
172 target = base + '.bin'
173
174 # use temporary directory
175 if tmpdir:
176 try: os.mkdir(tmpdir, 0700)
177 except os.error, errmsg:
178 sys.stderr.write('mkdir: (%s) %s\n' % errmsg)
179 sys.stderr.write('Error: cannot create temporary directory: %s\n' % (tmpdir,))
180 sys.exit(2)
181 frozen_c = os.path.join(tmpdir, frozen_c)
182 config_c = os.path.join(tmpdir, config_c)
183 makefile = os.path.join(tmpdir, makefile)
184
185 dict = findmodules.findmodules(scriptfile, modules, path)
186
187 # Actual work starts here...
188
189 backup = frozen_c + '~'
190 try:
191 os.rename(frozen_c, backup)
192 except os.error:
193 backup = None
194 outfp = open(frozen_c, 'w')
195 try:
196 makefreeze.makefreeze(outfp, dict)
197 finally:
198 outfp.close()
199 if backup:
200 if cmp.cmp(backup, frozen_c):
201 sys.stderr.write('%s not changed, not written\n' %
202 frozen_c)
203 os.rename(backup, frozen_c)
204
205 builtins = []
206 unknown = []
207 mods = dict.keys()
208 mods.sort()
209 for mod in mods:
210 if dict[mod] == '<builtin>':
211 builtins.append(mod)
212 elif dict[mod] == '<unknown>':
213 unknown.append(mod)
214
215 addfiles = []
216 if unknown:
217 addfiles, addmods = \
218 checkextensions.checkextensions(unknown, extensions)
219 for mod in addmods:
220 unknown.remove(mod)
221 builtins = builtins + addmods
222 if unknown:
223 sys.stderr.write('Warning: unknown modules remain: %s\n' %
224 string.join(unknown))
225
226 builtins.sort()
227 infp = open(config_c_in)
228 backup = config_c + '~'
229 try:
230 os.rename(config_c, backup)
231 except os.error:
232 backup = None
233 outfp = open(config_c, 'w')
234 try:
235 makeconfig.makeconfig(infp, outfp, builtins)
236 finally:
237 outfp.close()
238 infp.close()
239 if backup:
240 if cmp.cmp(backup, config_c):
241 sys.stderr.write('%s not changed, not written\n' %
242 config_c)
243 os.rename(backup, config_c)
244
245 cflags = defines + includes + ['$(OPT)']
246 libs = []
247 for n in 'Modules', 'Python', 'Objects', 'Parser':
248 n = 'lib%s.a' % n
249 n = os.path.join(binlib, n)
250 libs.append(n)
251
252 makevars = parsesetup.getmakevars(makefile_in)
253 somevars = {}
254 for key in makevars.keys():
255 somevars[key] = makevars[key]
256
257 somevars['CFLAGS'] = string.join(cflags) # override
258 files = ['$(OPT)', config_c, frozen_c, frozenmain_c] + \
259 addfiles + libs + \
260 ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
261
262 backup = makefile + '~'
263 try:
264 os.rename(makefile, backup)
265 except os.error:
266 backup = None
267 outfp = open(makefile, 'w')
268 try:
269 makemakefile.makemakefile(outfp, somevars, files, target)
270 finally:
271 outfp.close()
272 if backup:
273 if not cmp.cmp(backup, makefile):
274 print 'previous Makefile saved as', backup
275
276 # Done!
277
278 if tmpdir:
279 # Run make
280 curdir = os.getcwd()
281 os.chdir(tmpdir)
282 status = os.system('make > /dev/null')
283 os.chdir(curdir)
284
285 if status:
286 sys.stderr.write('Compilation failed. Files left in %s\n' %
287 (tmpdir,))
288 else:
289 tmptarget = os.path.join(tmpdir, target)
290 try: os.rename(tmptarget, target)
291 except os.error:
292 os.system('cp %s %s' % (tmptarget, target))
293 os.system('rm -rf %s' % (tmpdir,))
294 print 'Frozen target:', target
295 tmpdir = None
296 else:
297 print 'Now run make to build the target:', target
298
299
300# Print usage message and exit
301
302def usage(msg = None):
303 if msg:
304 sys.stderr.write(str(msg) + '\n')
305 sys.stderr.write(usage_msg)
306 sys.exit(2)
307
308
309try: main()
310finally:
311 if tmpdir:
312 os.system('rm -rf %s' % (tmpdir,))