Initial revision
[blender.git] / intern / python / freeze / freeze.py
1 #! /usr/bin/env python
2
3 # changes made by strubi@blender.nl
4
5 """Freeze a Python script into a binary.
6
7 usage: freeze [options...] script [module]...
8
9 Options:
10 -p prefix:    This is the prefix used when you ran ``make install''
11               in the Python build directory.
12               (If you never ran this, freeze won't work.)
13               The default is whatever sys.prefix evaluates to.
14               It can also be the top directory of the Python source
15               tree; then -P must point to the build tree.
16
17 -P exec_prefix: Like -p but this is the 'exec_prefix', used to
18                 install objects etc.  The default is whatever sys.exec_prefix
19                 evaluates to, or the -p argument if given.
20                 If -p points to the Python source tree, -P must point
21                 to the build tree, if different.
22
23 -e extension: A directory containing additional .o files that
24               may be used to resolve modules.  This directory
25               should also have a Setup file describing the .o files.
26               On Windows, the name of a .INI file describing one
27               or more extensions is passed.
28               More than one -e option may be given.
29
30 -o dir:       Directory where the output files are created; default '.'.
31
32 -m:           Additional arguments are module names instead of filenames.
33
34 -a package=dir: Additional directories to be added to the package's
35                 __path__.  Used to simulate directories added by the
36                 package at runtime (eg, by OpenGL and win32com).
37                 More than one -a option may be given for each package.
38
39 -l file:      Pass the file to the linker (windows only)
40
41 -d:           Debugging mode for the module finder.
42
43 -q:           Make the module finder totally quiet.
44
45 -h:           Print this help message.
46
47 -x module     Exclude the specified module.
48
49 -i filename:  Include a file with additional command line options.  Used
50               to prevent command lines growing beyond the capabilities of
51               the shell/OS.  All arguments specified in filename
52               are read and the -i option replaced with the parsed
53               params (note - quoting args in this file is NOT supported)
54
55 -s subsystem: Specify the subsystem (For Windows only.); 
56               'console' (default), 'windows', 'service' or 'com_dll'
57               
58 -w:           Toggle Windows (NT or 95) behavior.
59               (For debugging only -- on a win32 platform, win32 behavior
60               is automatic.)
61
62 Arguments:
63
64 script:       The Python script to be executed by the resulting binary.
65
66 module ...:   Additional Python modules (referenced by pathname)
67               that will be included in the resulting binary.  These
68               may be .py or .pyc files.  If -m is specified, these are
69               module names that are search in the path instead.
70
71 NOTES:
72
73 In order to use freeze successfully, you must have built Python and
74 installed it ("make install").
75
76 The script should not use modules provided only as shared libraries;
77 if it does, the resulting binary is not self-contained.
78 """
79
80
81 # Import standard modules
82
83 import getopt
84 import os
85 import string
86 import sys
87
88
89 # Import the freeze-private modules
90
91 import checkextensions
92 import modulefinder
93 import makeconfig
94 import makefreeze
95 import makemakefile
96 import parsesetup
97 import bkfile
98
99
100 # Main program
101
102 def main():
103     # overridable context
104     prefix = None                       # settable with -p option
105     exec_prefix = None                  # settable with -P option
106     extensions = []
107     exclude = []                        # settable with -x option
108     addn_link = []      # settable with -l, but only honored under Windows.
109     path = sys.path[:]
110     modargs = 0
111     debug = 1
112     odir = ''
113     win = sys.platform[:3] == 'win'
114
115     # default the exclude list for each platform
116     if win: exclude = exclude + [
117         'dos', 'dospath', 'mac', 'macpath', 'macfs', 'MACFS', 'posix', 'os2', 'ce']
118
119     # modules that are imported by the Python runtime
120     #implicits = ["site", "exceptions"]
121     implicits = ["exceptions"]
122
123     # output files
124     frozen_c = 'frozen.c'
125     config_c = 'config.c'
126     target = 'a.out'                    # normally derived from script name
127     makefile = 'Makefile.freeze'
128     subsystem = 'console'
129
130     # parse command line by first replacing any "-i" options with the file contents.
131     pos = 1
132     while pos < len(sys.argv)-1: # last option can not be "-i", so this ensures "pos+1" is in range!
133         if sys.argv[pos] == '-i':
134             try:
135                 options = string.split(open(sys.argv[pos+1]).read())
136             except IOError, why:
137                 usage("File name '%s' specified with the -i option can not be read - %s" % (sys.argv[pos+1], why) )
138             # Replace the '-i' and the filename with the read params.
139             sys.argv[pos:pos+2] = options
140             pos = pos + len(options) - 1 # Skip the name and the included args.
141         pos = pos + 1
142
143     # Now parse the command line with the extras inserted.
144     try:
145         opts, args = getopt.getopt(sys.argv[1:], 'a:de:hmo:p:P:I:qs:wx:l:')
146     except getopt.error, msg:
147         usage('getopt error: ' + str(msg))
148
149     # proces option arguments
150     for o, a in opts:
151         if o == '-h':
152             print __doc__
153             return
154         if o == '-d':
155             debug = debug + 1
156         if o == '-e':
157             extensions.append(a)
158         if o == '-I': # include path
159                         path.append(a)
160         if o == '-m':
161             modargs = 1
162         if o == '-o':
163             odir = a
164         if o == '-p':
165             prefix = a
166         if o == '-P':
167             exec_prefix = a
168         if o == '-q':
169             debug = 0
170         if o == '-w':
171             win = not win
172         if o == '-s':
173             if not win:
174                 usage("-s subsystem option only on Windows")
175             subsystem = a
176         if o == '-x':
177             exclude.append(a)
178         if o == '-l':
179             addn_link.append(a)
180         if o == '-a':
181             apply(modulefinder.AddPackagePath, tuple(string.split(a,"=", 2)))
182
183     # default prefix and exec_prefix
184     if not exec_prefix:
185         if prefix:
186             exec_prefix = prefix
187         else:
188             exec_prefix = sys.exec_prefix
189     if not prefix:
190         prefix = sys.prefix
191
192     # determine whether -p points to the Python source tree
193     ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
194
195     # locations derived from options
196     version = sys.version[:3]
197     if win:
198         extensions_c = 'frozen_extensions.c'
199     if ishome:
200         print "(Using Python source directory)"
201         binlib = exec_prefix
202         incldir = os.path.join(prefix, 'Include')
203         config_h_dir = exec_prefix
204         config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
205         frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
206         makefile_in = os.path.join(exec_prefix, 'Modules', 'Makefile')
207         if win:
208             frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
209     else:
210         binlib = os.path.join(exec_prefix,
211                               'lib', 'python%s' % version, 'config')
212         incldir = os.path.join(prefix, 'include', 'python%s' % version)
213         config_h_dir = os.path.join(exec_prefix, 'include',
214                                     'python%s' % version)
215         config_c_in = os.path.join(binlib, 'config.c.in')
216         frozenmain_c = os.path.join(binlib, 'frozenmain.c')
217         makefile_in = os.path.join(binlib, 'Makefile')
218         frozendllmain_c = os.path.join(binlib, 'frozen_dllmain.c')
219     supp_sources = []
220     defines = []
221     includes = ['-I' + incldir, '-I' + config_h_dir]
222
223     # sanity check of directories and files
224     check_dirs = [prefix, exec_prefix, binlib, incldir]
225     if not win: check_dirs = check_dirs + extensions # These are not directories on Windows.
226     for dir in check_dirs:
227         if not os.path.exists(dir):
228             usage('needed directory %s not found' % dir)
229         if not os.path.isdir(dir):
230             usage('%s: not a directory' % dir)
231     if win:
232         files = supp_sources + extensions # extensions are files on Windows.
233     else:
234         files = [config_c_in, makefile_in] + supp_sources
235     for file in supp_sources:
236         if not os.path.exists(file):
237             usage('needed file %s not found' % file)
238         if not os.path.isfile(file):
239             usage('%s: not a plain file' % file)
240     if not win:
241         for dir in extensions:
242             setup = os.path.join(dir, 'Setup')
243             if not os.path.exists(setup):
244                 usage('needed file %s not found' % setup)
245             if not os.path.isfile(setup):
246                 usage('%s: not a plain file' % setup)
247
248     # check that enough arguments are passed
249     if not args:
250         usage('at least one filename argument required')
251
252     # check that file arguments exist
253     for arg in args:
254         if arg == '-m':
255             break
256         # if user specified -m on the command line before _any_
257         # file names, then nothing should be checked (as the
258         # very first file should be a module name)
259         if modargs:
260             break
261         if not os.path.exists(arg):
262             usage('argument %s not found' % arg)
263         if not os.path.isfile(arg):
264             usage('%s: not a plain file' % arg)
265
266     # process non-option arguments
267     scriptfile = args[0]
268     modules = args[1:]
269
270     # derive target name from script name
271     base = os.path.basename(scriptfile)
272     base, ext = os.path.splitext(base)
273     if base:
274         if base != scriptfile:
275             target = base
276         else:
277             target = base + '.bin'
278
279     # handle -o option
280     base_frozen_c = frozen_c
281     base_config_c = config_c
282     base_target = target
283     if odir and not os.path.isdir(odir):
284         try:
285             os.mkdir(odir)
286             print "Created output directory", odir
287         except os.error, msg:
288             usage('%s: mkdir failed (%s)' % (odir, str(msg)))
289     base = ''
290     if odir:
291         base = os.path.join(odir, '')
292         frozen_c = os.path.join(odir, frozen_c)
293         config_c = os.path.join(odir, config_c)
294         target = os.path.join(odir, target)
295         makefile = os.path.join(odir, makefile)
296         if win: extensions_c = os.path.join(odir, extensions_c)
297
298     # Handle special entry point requirements
299     # (on Windows, some frozen programs do not use __main__, but
300     # import the module directly.  Eg, DLLs, Services, etc
301     custom_entry_point = None  # Currently only used on Windows
302     python_entry_is_main = 1   # Is the entry point called __main__?
303     # handle -s option on Windows
304     if win:
305         import winmakemakefile
306         try:
307             custom_entry_point, python_entry_is_main = \
308                 winmakemakefile.get_custom_entry_point(subsystem)
309         except ValueError, why:
310             usage(why)
311             
312
313     # Actual work starts here...
314
315     # collect all modules of the program
316     dir = os.path.dirname(scriptfile)
317     path[0] = dir
318     mf = modulefinder.ModuleFinder(path, debug, exclude)
319     
320     if win and subsystem=='service':
321         # If a Windows service, then add the "built-in" module.
322         mod = mf.add_module("servicemanager")
323         mod.__file__="dummy.pyd" # really built-in to the resulting EXE
324
325     for mod in implicits:
326         mf.import_hook(mod)
327     for mod in modules:
328         if mod == '-m':
329             modargs = 1
330             continue
331         if modargs:
332             if mod[-2:] == '.*':
333                 mf.import_hook(mod[:-2], None, ["*"])
334             else:
335                 mf.import_hook(mod)
336         else:
337             mf.load_file(mod)
338
339     # Add the main script as either __main__, or the actual module name.
340     if python_entry_is_main:
341         mf.run_script(scriptfile)
342     else:
343         mf.load_file(scriptfile)
344
345     if debug > 0:
346         mf.report()
347         print
348     dict = mf.modules
349
350     # generate output for frozen modules
351     files = makefreeze.makefreeze(base, dict, debug, custom_entry_point, 1)
352     # look for unfrozen modules (builtin and of unknown origin)
353     builtins = []
354     unknown = []
355     mods = dict.keys()
356     mods.sort()
357     for mod in mods:
358         if dict[mod].__code__:
359             continue
360         if not dict[mod].__file__:
361             builtins.append(mod)
362         else:
363             unknown.append(mod)
364
365     # search for unknown modules in extensions directories (not on Windows)
366     addfiles = []
367     frozen_extensions = [] # Windows list of modules.
368     if unknown or (not win and builtins):
369         if not win:
370             addfiles, addmods = \
371                       checkextensions.checkextensions(unknown+builtins,
372                                                       extensions)
373             for mod in addmods:
374                 if mod in unknown:
375                     unknown.remove(mod)
376                     builtins.append(mod)
377         else:
378             # Do the windows thang...
379             import checkextensions_win32
380             # Get a list of CExtension instances, each describing a module 
381             # (including its source files)
382             frozen_extensions = checkextensions_win32.checkextensions(
383                 unknown, extensions, prefix)
384             for mod in frozen_extensions:
385                 unknown.remove(mod.name)
386
387     # report unknown modules
388     if unknown:
389         sys.stderr.write('Warning: unknown modules remain: %s\n' %
390                          string.join(unknown))
391
392     # windows gets different treatment
393     if win:
394         # Taking a shortcut here...
395         import winmakemakefile, checkextensions_win32
396         checkextensions_win32.write_extension_table(extensions_c,
397                                                     frozen_extensions)
398         # Create a module definition for the bootstrap C code.
399         xtras = [frozenmain_c, os.path.basename(frozen_c),
400                  frozendllmain_c, os.path.basename(extensions_c)] + files
401         maindefn = checkextensions_win32.CExtension( '__main__', xtras )
402         frozen_extensions.append( maindefn )
403         outfp = open(makefile, 'w')
404         try:
405             winmakemakefile.makemakefile(outfp,
406                                          locals(),
407                                          frozen_extensions,
408                                          os.path.basename(target))
409         finally:
410             outfp.close()
411         return
412
413     # generate config.c and Makefile
414     builtins.sort()
415     infp = open(config_c_in)
416     outfp = bkfile.open(config_c, 'w')
417     try:
418         makeconfig.makeconfig(infp, outfp, builtins)
419     finally:
420         outfp.close()
421     infp.close()
422
423     cflags = defines + includes + ['$(OPT)']
424     libs = [os.path.join(binlib, 'libpython$(VERSION).a')]
425
426     somevars = {}
427     if os.path.exists(makefile_in):
428         makevars = parsesetup.getmakevars(makefile_in)
429     for key in makevars.keys():
430         somevars[key] = makevars[key]
431
432     somevars['CFLAGS'] = string.join(cflags) # override
433     files = ['$(OPT)', '$(LDFLAGS)', base_config_c, base_frozen_c] + \
434             files + supp_sources +  addfiles + libs + \
435             ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
436
437     outfp = bkfile.open(makefile, 'w')
438     try:
439         makemakefile.makemakefile(outfp, somevars, files, base_target)
440     finally:
441         outfp.close()
442
443     # Done!
444
445     if odir:
446         print 'Now run "make" in', odir,
447         print 'to build the target:', base_target
448     else:
449         print 'Now run "make" to build the target:', base_target
450
451
452 # Print usage message and exit
453
454 def usage(msg):
455     sys.stdout = sys.stderr
456     print "Error:", msg
457     print "Use ``%s -h'' for help" % sys.argv[0]
458     sys.exit(2)
459
460
461 main()