freeze.py 15.5 KB
Newer Older
1
#! /usr/bin/env python
2

3
"""Freeze a Python script into a binary.
4

Guido van Rossum's avatar
Guido van Rossum committed
5
usage: freeze [options...] script [module]...
6

7
Options:
8
-p prefix:    This is the prefix used when you ran ``make install''
9
              in the Python build directory.
10
              (If you never ran this, freeze won't work.)
11
              The default is whatever sys.prefix evaluates to.
12 13
              It can also be the top directory of the Python source
              tree; then -P must point to the build tree.
14

15
-P exec_prefix: Like -p but this is the 'exec_prefix', used to
16 17 18 19
                install objects etc.  The default is whatever sys.exec_prefix
                evaluates to, or the -p argument if given.
                If -p points to the Python source tree, -P must point
                to the build tree, if different.
20

21 22 23
-e extension: A directory containing additional .o files that
              may be used to resolve modules.  This directory
              should also have a Setup file describing the .o files.
24 25
              On Windows, the name of a .INI file describing one
              or more extensions is passed.
26 27
              More than one -e option may be given.

28 29
-o dir:       Directory where the output files are created; default '.'.

30 31
-m:           Additional arguments are module names instead of filenames.

32 33 34 35 36
-a package=dir: Additional directories to be added to the package's
                __path__.  Used to simulate directories added by the
                package at runtime (eg, by OpenGL and win32com).
                More than one -a option may be given for each package.

37 38
-l file:      Pass the file to the linker (windows only)

39 40 41 42
-d:           Debugging mode for the module finder.

-q:           Make the module finder totally quiet.

43 44
-h:           Print this help message.

45 46
-x module     Exclude the specified module.

47 48 49 50 51 52
-i filename:  Include a file with additional command line options.  Used
              to prevent command lines growing beyond the capabilities of
              the shell/OS.  All arguments specified in filename
              are read and the -i option replaced with the parsed
              params (note - quoting args in this file is NOT supported)

53 54 55
-s subsystem: Specify the subsystem (For Windows only.); 
              'console' (default), 'windows', 'service' or 'com_dll'
              
56
-w:           Toggle Windows (NT or 95) behavior.
57
              (For debugging only -- on a win32 platform, win32 behavior
58
              is automatic.)
59

60 61 62 63
-r prefix=f:  Replace path prefix.
              Replace prefix with f in the source path references 
              contained in the resulting binary.

64 65
Arguments:

Guido van Rossum's avatar
Guido van Rossum committed
66
script:       The Python script to be executed by the resulting binary.
67 68 69

module ...:   Additional Python modules (referenced by pathname)
              that will be included in the resulting binary.  These
70 71
              may be .py or .pyc files.  If -m is specified, these are
              module names that are search in the path instead.
72 73 74 75

NOTES:

In order to use freeze successfully, you must have built Python and
76
installed it ("make install").
77

78 79
The script should not use modules provided only as shared libraries;
if it does, the resulting binary is not self-contained.
80 81 82
"""


83
# Import standard modules
84 85

import getopt
86
import os
87
import string
88 89 90 91 92
import sys


# Import the freeze-private modules

93
import checkextensions
94
import modulefinder
95 96 97 98
import makeconfig
import makefreeze
import makemakefile
import parsesetup
99
import bkfile
100 101 102 103


# Main program

104
def main():
105 106 107 108
    # overridable context
    prefix = None                       # settable with -p option
    exec_prefix = None                  # settable with -P option
    extensions = []
109
    exclude = []                        # settable with -x option
110
    addn_link = []      # settable with -l, but only honored under Windows.
Guido van Rossum's avatar
Guido van Rossum committed
111
    path = sys.path[:]
112 113
    modargs = 0
    debug = 1
114
    odir = ''
115
    win = sys.platform[:3] == 'win'
116
    replace_paths = []                  # settable with -r option
117

118
    # default the exclude list for each platform
119
    if win: exclude = exclude + [
120
        'dos', 'dospath', 'mac', 'macpath', 'macfs', 'MACFS', 'posix', 'os2', 'ce']
121

122 123 124
    # modules that are imported by the Python runtime
    implicits = ["site", "exceptions"]

125 126 127 128 129
    # output files
    frozen_c = 'frozen.c'
    config_c = 'config.c'
    target = 'a.out'                    # normally derived from script name
    makefile = 'Makefile'
130
    subsystem = 'console'
131

132 133 134 135 136 137 138 139 140 141 142 143 144 145
    # parse command line by first replacing any "-i" options with the file contents.
    pos = 1
    while pos < len(sys.argv)-1: # last option can not be "-i", so this ensures "pos+1" is in range!
        if sys.argv[pos] == '-i':
            try:
                options = string.split(open(sys.argv[pos+1]).read())
            except IOError, why:
                usage("File name '%s' specified with the -i option can not be read - %s" % (sys.argv[pos+1], why) )
            # Replace the '-i' and the filename with the read params.
            sys.argv[pos:pos+2] = options
            pos = pos + len(options) - 1 # Skip the name and the included args.
        pos = pos + 1

    # Now parse the command line with the extras inserted.
146
    try:
147
        opts, args = getopt.getopt(sys.argv[1:], 'r:a:de:hmo:p:P:qs:wx:l:')
148 149 150 151 152 153 154 155
    except getopt.error, msg:
        usage('getopt error: ' + str(msg))

    # proces option arguments
    for o, a in opts:
        if o == '-h':
            print __doc__
            return
156 157
        if o == '-d':
            debug = debug + 1
158 159
        if o == '-e':
            extensions.append(a)
160 161
        if o == '-m':
            modargs = 1
162 163 164 165 166 167
        if o == '-o':
            odir = a
        if o == '-p':
            prefix = a
        if o == '-P':
            exec_prefix = a
168 169
        if o == '-q':
            debug = 0
170 171 172 173 174 175
        if o == '-w':
            win = not win
        if o == '-s':
            if not win:
                usage("-s subsystem option only on Windows")
            subsystem = a
176 177 178 179
        if o == '-x':
            exclude.append(a)
        if o == '-l':
            addn_link.append(a)
180 181
        if o == '-a':
            apply(modulefinder.AddPackagePath, tuple(string.split(a,"=", 2)))
182 183 184
        if o == '-r':
            f,r = string.split(a,"=", 2)
            replace_paths.append( (f,r) )
185 186 187 188 189 190 191 192 193 194 195

    # default prefix and exec_prefix
    if not exec_prefix:
        if prefix:
            exec_prefix = prefix
        else:
            exec_prefix = sys.exec_prefix
    if not prefix:
        prefix = sys.prefix

    # determine whether -p points to the Python source tree
196
    ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
197 198 199

    # locations derived from options
    version = sys.version[:3]
200 201
    if win:
        extensions_c = 'frozen_extensions.c'
202 203 204 205
    if ishome:
        print "(Using Python source directory)"
        binlib = exec_prefix
        incldir = os.path.join(prefix, 'Include')
Guido van Rossum's avatar
Guido van Rossum committed
206
        config_h_dir = exec_prefix
207
        config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
208
        frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
209
        makefile_in = os.path.join(exec_prefix, 'Modules', 'Makefile')
210 211
        if win:
            frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
212 213 214 215
    else:
        binlib = os.path.join(exec_prefix,
                              'lib', 'python%s' % version, 'config')
        incldir = os.path.join(prefix, 'include', 'python%s' % version)
Guido van Rossum's avatar
Guido van Rossum committed
216 217
        config_h_dir = os.path.join(exec_prefix, 'include',
                                    'python%s' % version)
218 219 220
        config_c_in = os.path.join(binlib, 'config.c.in')
        frozenmain_c = os.path.join(binlib, 'frozenmain.c')
        makefile_in = os.path.join(binlib, 'Makefile')
221
        frozendllmain_c = os.path.join(binlib, 'frozen_dllmain.c')
222 223
    supp_sources = []
    defines = []
Guido van Rossum's avatar
Guido van Rossum committed
224
    includes = ['-I' + incldir, '-I' + config_h_dir]
225 226

    # sanity check of directories and files
227 228 229
    check_dirs = [prefix, exec_prefix, binlib, incldir]
    if not win: check_dirs = check_dirs + extensions # These are not directories on Windows.
    for dir in check_dirs:
230 231 232 233
        if not os.path.exists(dir):
            usage('needed directory %s not found' % dir)
        if not os.path.isdir(dir):
            usage('%s: not a directory' % dir)
234
    if win:
235
        files = supp_sources + extensions # extensions are files on Windows.
236 237 238
    else:
        files = [config_c_in, makefile_in] + supp_sources
    for file in supp_sources:
239 240 241 242
        if not os.path.exists(file):
            usage('needed file %s not found' % file)
        if not os.path.isfile(file):
            usage('%s: not a plain file' % file)
243 244 245 246 247 248 249
    if not win:
        for dir in extensions:
            setup = os.path.join(dir, 'Setup')
            if not os.path.exists(setup):
                usage('needed file %s not found' % setup)
            if not os.path.isfile(setup):
                usage('%s: not a plain file' % setup)
250 251 252 253 254 255 256

    # check that enough arguments are passed
    if not args:
        usage('at least one filename argument required')

    # check that file arguments exist
    for arg in args:
Guido van Rossum's avatar
Guido van Rossum committed
257 258
        if arg == '-m':
            break
259 260 261 262 263
        # if user specified -m on the command line before _any_
        # file names, then nothing should be checked (as the
        # very first file should be a module name)
        if modargs:
            break
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        if not os.path.exists(arg):
            usage('argument %s not found' % arg)
        if not os.path.isfile(arg):
            usage('%s: not a plain file' % arg)

    # process non-option arguments
    scriptfile = args[0]
    modules = args[1:]

    # derive target name from script name
    base = os.path.basename(scriptfile)
    base, ext = os.path.splitext(base)
    if base:
        if base != scriptfile:
            target = base
        else:
            target = base + '.bin'

    # handle -o option
    base_frozen_c = frozen_c
    base_config_c = config_c
    base_target = target
    if odir and not os.path.isdir(odir):
        try:
            os.mkdir(odir)
            print "Created output directory", odir
        except os.error, msg:
            usage('%s: mkdir failed (%s)' % (odir, str(msg)))
292
    base = ''
293
    if odir:
294
        base = os.path.join(odir, '')
295 296 297 298
        frozen_c = os.path.join(odir, frozen_c)
        config_c = os.path.join(odir, config_c)
        target = os.path.join(odir, target)
        makefile = os.path.join(odir, makefile)
299
        if win: extensions_c = os.path.join(odir, extensions_c)
300

301 302 303 304 305 306 307 308 309
    # Handle special entry point requirements
    # (on Windows, some frozen programs do not use __main__, but
    # import the module directly.  Eg, DLLs, Services, etc
    custom_entry_point = None  # Currently only used on Windows
    python_entry_is_main = 1   # Is the entry point called __main__?
    # handle -s option on Windows
    if win:
        import winmakemakefile
        try:
310 311
            custom_entry_point, python_entry_is_main = \
                winmakemakefile.get_custom_entry_point(subsystem)
312 313 314 315
        except ValueError, why:
            usage(why)
            

316 317
    # Actual work starts here...

318
    # collect all modules of the program
Guido van Rossum's avatar
Guido van Rossum committed
319 320
    dir = os.path.dirname(scriptfile)
    path[0] = dir
321
    mf = modulefinder.ModuleFinder(path, debug, exclude, replace_paths)
322 323 324 325 326 327
    
    if win and subsystem=='service':
        # If a Windows service, then add the "built-in" module.
        mod = mf.add_module("servicemanager")
        mod.__file__="dummy.pyd" # really built-in to the resulting EXE

328 329 330 331 332 333 334 335 336 337 338 339 340
    for mod in implicits:
        mf.import_hook(mod)
    for mod in modules:
        if mod == '-m':
            modargs = 1
            continue
        if modargs:
            if mod[-2:] == '.*':
                mf.import_hook(mod[:-2], None, ["*"])
            else:
                mf.import_hook(mod)
        else:
            mf.load_file(mod)
341 342 343 344 345

    # Add the main script as either __main__, or the actual module name.
    if python_entry_is_main:
        mf.run_script(scriptfile)
    else:
Guido van Rossum's avatar
Guido van Rossum committed
346
        mf.load_file(scriptfile)
347

348 349 350 351 352 353
    if debug > 0:
        mf.report()
        print
    dict = mf.modules

    # generate output for frozen modules
354
    files = makefreeze.makefreeze(base, dict, debug, custom_entry_point)
355

356
    # look for unfrozen modules (builtin and of unknown origin)
357 358 359 360 361
    builtins = []
    unknown = []
    mods = dict.keys()
    mods.sort()
    for mod in mods:
362 363 364
        if dict[mod].__code__:
            continue
        if not dict[mod].__file__:
365
            builtins.append(mod)
366
        else:
367 368
            unknown.append(mod)

369
    # search for unknown modules in extensions directories (not on Windows)
370
    addfiles = []
371
    frozen_extensions = [] # Windows list of modules.
372
    if unknown or (not win and builtins):
373 374
        if not win:
            addfiles, addmods = \
375 376
                      checkextensions.checkextensions(unknown+builtins,
                                                      extensions)
377
            for mod in addmods:
378 379 380
                if mod in unknown:
                    unknown.remove(mod)
                    builtins.append(mod)
381 382 383 384 385
        else:
            # Do the windows thang...
            import checkextensions_win32
            # Get a list of CExtension instances, each describing a module 
            # (including its source files)
386
            frozen_extensions = checkextensions_win32.checkextensions(
387
                unknown, extensions, prefix)
388
            for mod in frozen_extensions:
389
                unknown.remove(mod.name)
390 391

    # report unknown modules
392 393 394 395
    if unknown:
        sys.stderr.write('Warning: unknown modules remain: %s\n' %
                         string.join(unknown))

396 397 398
    # windows gets different treatment
    if win:
        # Taking a shortcut here...
399 400 401 402 403
        import winmakemakefile, checkextensions_win32
        checkextensions_win32.write_extension_table(extensions_c,
                                                    frozen_extensions)
        # Create a module definition for the bootstrap C code.
        xtras = [frozenmain_c, os.path.basename(frozen_c),
404
                 frozendllmain_c, os.path.basename(extensions_c)] + files
405 406
        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
        frozen_extensions.append( maindefn )
407 408 409 410
        outfp = open(makefile, 'w')
        try:
            winmakemakefile.makemakefile(outfp,
                                         locals(),
411
                                         frozen_extensions,
412 413 414 415 416 417
                                         os.path.basename(target))
        finally:
            outfp.close()
        return

    # generate config.c and Makefile
418 419
    builtins.sort()
    infp = open(config_c_in)
420
    outfp = bkfile.open(config_c, 'w')
421 422 423 424 425 426 427 428 429 430
    try:
        makeconfig.makeconfig(infp, outfp, builtins)
    finally:
        outfp.close()
    infp.close()

    cflags = defines + includes + ['$(OPT)']
    libs = [os.path.join(binlib, 'libpython$(VERSION).a')]

    somevars = {}
431 432
    if os.path.exists(makefile_in):
        makevars = parsesetup.getmakevars(makefile_in)
433 434 435 436 437
    for key in makevars.keys():
        somevars[key] = makevars[key]

    somevars['CFLAGS'] = string.join(cflags) # override
    files = ['$(OPT)', '$(LDFLAGS)', base_config_c, base_frozen_c] + \
438
            files + supp_sources +  addfiles + libs + \
439 440
            ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']

441
    outfp = bkfile.open(makefile, 'w')
442 443 444 445 446 447 448 449 450 451 452 453
    try:
        makemakefile.makemakefile(outfp, somevars, files, base_target)
    finally:
        outfp.close()

    # Done!

    if odir:
        print 'Now run "make" in', odir,
        print 'to build the target:', base_target
    else:
        print 'Now run "make" to build the target:', base_target
454

455 456 457

# Print usage message and exit

458
def usage(msg):
459 460 461 462
    sys.stdout = sys.stderr
    print "Error:", msg
    print "Use ``%s -h'' for help" % sys.argv[0]
    sys.exit(2)
463 464


465
main()