freeze.py 15.2 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
Arguments:

Guido van Rossum's avatar
Guido van Rossum committed
62
script:       The Python script to be executed by the resulting binary.
63 64 65

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

NOTES:

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

74 75
The script should not use modules provided only as shared libraries;
if it does, the resulting binary is not self-contained.
76 77 78
"""


79
# Import standard modules
80 81

import getopt
82
import os
83
import string
84 85 86 87 88
import sys


# Import the freeze-private modules

89
import checkextensions
90
import modulefinder
91 92 93 94
import makeconfig
import makefreeze
import makemakefile
import parsesetup
95
import bkfile
96 97 98 99


# Main program

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

113
    # default the exclude list for each platform
114
    if win: exclude = exclude + [
115
        'dos', 'dospath', 'mac', 'macpath', 'macfs', 'MACFS', 'posix', 'os2', 'ce']
116

117 118 119
    # modules that are imported by the Python runtime
    implicits = ["site", "exceptions"]

120 121 122 123 124
    # output files
    frozen_c = 'frozen.c'
    config_c = 'config.c'
    target = 'a.out'                    # normally derived from script name
    makefile = 'Makefile'
125
    subsystem = 'console'
126

127 128 129 130 131 132 133 134 135 136 137 138 139 140
    # 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.
141
    try:
142
        opts, args = getopt.getopt(sys.argv[1:], 'a:de:hmo:p:P:qs:wx:l:')
143 144 145 146 147 148 149 150
    except getopt.error, msg:
        usage('getopt error: ' + str(msg))

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

    # 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
188
    ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
189 190 191

    # locations derived from options
    version = sys.version[:3]
192 193
    if win:
        extensions_c = 'frozen_extensions.c'
194 195 196 197
    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
198
        config_h_dir = exec_prefix
199
        config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
200
        frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
201
        makefile_in = os.path.join(exec_prefix, 'Modules', 'Makefile')
202 203
        if win:
            frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
204 205 206 207
    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
208 209
        config_h_dir = os.path.join(exec_prefix, 'include',
                                    'python%s' % version)
210 211 212
        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')
213
        frozendllmain_c = os.path.join(binlib, 'frozen_dllmain.c')
214 215
    supp_sources = []
    defines = []
Guido van Rossum's avatar
Guido van Rossum committed
216
    includes = ['-I' + incldir, '-I' + config_h_dir]
217 218

    # sanity check of directories and files
219 220 221
    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:
222 223 224 225
        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)
226
    if win:
227
        files = supp_sources + extensions # extensions are files on Windows.
228 229 230
    else:
        files = [config_c_in, makefile_in] + supp_sources
    for file in supp_sources:
231 232 233 234
        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)
235 236 237 238 239 240 241
    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)
242 243 244 245 246 247 248

    # 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
249 250
        if arg == '-m':
            break
251 252 253 254 255
        # 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
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
        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)))
284
    base = ''
285
    if odir:
286
        base = os.path.join(odir, '')
287 288 289 290
        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)
291
        if win: extensions_c = os.path.join(odir, extensions_c)
292

293 294 295 296 297 298 299 300 301
    # 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:
302 303
            custom_entry_point, python_entry_is_main = \
                winmakemakefile.get_custom_entry_point(subsystem)
304 305 306 307
        except ValueError, why:
            usage(why)
            

308 309
    # Actual work starts here...

310
    # collect all modules of the program
Guido van Rossum's avatar
Guido van Rossum committed
311 312
    dir = os.path.dirname(scriptfile)
    path[0] = dir
313 314 315 316 317 318 319
    mf = modulefinder.ModuleFinder(path, debug, exclude)
    
    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

320 321 322 323 324 325 326 327 328 329 330 331 332
    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)
333 334 335 336 337

    # 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
338
        mf.load_file(scriptfile)
339

340 341 342 343 344 345
    if debug > 0:
        mf.report()
        print
    dict = mf.modules

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

348
    # look for unfrozen modules (builtin and of unknown origin)
349 350 351 352 353
    builtins = []
    unknown = []
    mods = dict.keys()
    mods.sort()
    for mod in mods:
354 355 356
        if dict[mod].__code__:
            continue
        if not dict[mod].__file__:
357
            builtins.append(mod)
358
        else:
359 360
            unknown.append(mod)

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

    # report unknown modules
384 385 386 387
    if unknown:
        sys.stderr.write('Warning: unknown modules remain: %s\n' %
                         string.join(unknown))

388 389 390
    # windows gets different treatment
    if win:
        # Taking a shortcut here...
391 392 393 394 395
        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),
396
                 frozendllmain_c, os.path.basename(extensions_c)] + files
397 398
        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
        frozen_extensions.append( maindefn )
399 400 401 402
        outfp = open(makefile, 'w')
        try:
            winmakemakefile.makemakefile(outfp,
                                         locals(),
403
                                         frozen_extensions,
404 405 406 407 408 409
                                         os.path.basename(target))
        finally:
            outfp.close()
        return

    # generate config.c and Makefile
410 411
    builtins.sort()
    infp = open(config_c_in)
412
    outfp = bkfile.open(config_c, 'w')
413 414 415 416 417 418 419 420 421 422
    try:
        makeconfig.makeconfig(infp, outfp, builtins)
    finally:
        outfp.close()
    infp.close()

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

    somevars = {}
423 424
    if os.path.exists(makefile_in):
        makevars = parsesetup.getmakevars(makefile_in)
425 426 427 428 429
    for key in makevars.keys():
        somevars[key] = makevars[key]

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

433
    outfp = bkfile.open(makefile, 'w')
434 435 436 437 438 439 440 441 442 443 444 445
    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
446

447 448 449

# Print usage message and exit

450
def usage(msg):
451 452 453 454
    sys.stdout = sys.stderr
    print "Error:", msg
    print "Use ``%s -h'' for help" % sys.argv[0]
    sys.exit(2)
455 456


457
main()