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