src/tools/py2sis/ensymble/squeeze/squeeze.py
changeset 0 ca70ae20a155
equal deleted inserted replaced
-1:000000000000 0:ca70ae20a155
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # SQUEEZE
       
     4 # $Id$
       
     5 #
       
     6 # squeeze a python program
       
     7 #
       
     8 # installation:
       
     9 # - use this script as is, or squeeze it using the following command:
       
    10 #
       
    11 # python squeezeTool.py -1su -o squeeze -b squeezeTool squeezeTool.py
       
    12 #
       
    13 # notes:
       
    14 # - this is pretty messy.  make sure to test everything carefully
       
    15 #   if you change anything
       
    16 #
       
    17 # - the name "squeeze" is taken from an ABC800 utility which did
       
    18 #   about the same thing with Basic II bytecodes.
       
    19 #
       
    20 # history:
       
    21 # 1.0 1997-04-22 fl   Created
       
    22 # 1.1 1997-05-25 fl   Added base64 embedding option (-1)
       
    23 #     1997-05-25 fl   Check for broken package file
       
    24 # 1.2 1997-05-26 fl   Support uncompressed packages (-u)
       
    25 # 1.3 1997-05-27 fl   Check byte code magic, eliminated StringIO, etc.
       
    26 # 1.4 1997-06-04 fl   Removed last bits of white space, removed try/except
       
    27 # 1.5 1997-06-17 fl   Added squeeze archive capabilities (-x)
       
    28 # 1.6 1998-05-04 fl   Minor fixes in preparation for public source release
       
    29 #
       
    30 # reviews:
       
    31 #       "Fredrik Lundh is a friggin genius"
       
    32 #       -- Aaron Watters, author of 'Internet Programming with Python'
       
    33 #
       
    34 #       "I agree ... this is a friggin Good Thing"
       
    35 #       -- Paul Everitt, Digital Creations
       
    36 #
       
    37 # Copyright (c) 1997 by Fredrik Lundh.
       
    38 # Copyright (c) 1997-1998 by Secret Labs AB
       
    39 #
       
    40 # info@pythonware.com
       
    41 # http://www.pythonware.com
       
    42 #
       
    43 # --------------------------------------------------------------------
       
    44 # Permission to use, copy, modify, and distribute this software and
       
    45 # its associated documentation for any purpose and without fee is
       
    46 # hereby granted.  This software is provided as is.
       
    47 # --------------------------------------------------------------------
       
    48 
       
    49 VERSION = "1.6/1998-05-04"
       
    50 MAGIC   = "[SQUEEZE]"
       
    51 
       
    52 import base64, imp, marshal, os, string, sys, md5
       
    53 
       
    54 # --------------------------------------------------------------------
       
    55 # usage
       
    56 
       
    57 def usage():
       
    58     print
       
    59     print "SQUEEZE", VERSION, "(c) 1997-1998 by Secret Labs AB"
       
    60     print """\
       
    61 Convert a Python application to a compressed module package.
       
    62 
       
    63 Usage: squeeze [-1ux] -o app [-b start] modules... [-d files...]
       
    64 
       
    65 This utility creates a compressed package file named "app.pyz", which
       
    66 contains the given module files.  It also creates a bootstrap script
       
    67 named "app.py", which loads the package and imports the given "start"
       
    68 module to get things going.  Example:
       
    69 
       
    70         squeeze -o app -b appMain app*.py
       
    71 
       
    72 The -1 option tells squeeze to put the package file inside the boot-
       
    73 strap script using base64 encoding.  The result is a single text file
       
    74 containing the full application.
       
    75 
       
    76 The -u option disables compression.  Otherwise, the package will be
       
    77 compressed using zlib, and the user needs zlib to run the resulting
       
    78 application.
       
    79 
       
    80 The -d option can be used to put additional files in the package file.
       
    81 You can access these files via "__main__.open(filename)" (returns a
       
    82 StringIO file object).
       
    83 
       
    84 The -x option can be used with -d to create a self-extracting archive,
       
    85 instead of a package.  When the resulting script is executed, the
       
    86 data files are extracted.  Omit the -b option in this case.
       
    87 """
       
    88     sys.exit(1)
       
    89 
       
    90 
       
    91 # --------------------------------------------------------------------
       
    92 # squeezer -- collect squeezed modules
       
    93 
       
    94 class Squeezer:
       
    95 
       
    96     def __init__(self):
       
    97 
       
    98         self.rawbytes = self.bytes = 0
       
    99         self.modules = {}
       
   100 
       
   101     def addmodule(self, file):
       
   102 
       
   103         if file[-1] == "c":
       
   104             file = file[:-1]
       
   105 
       
   106         m = os.path.splitext(os.path.split(file)[1])[0]
       
   107 
       
   108         # read sourcefile
       
   109         f = open(file)
       
   110         codestring = f.read()
       
   111         f.close()
       
   112 
       
   113         # dump to file
       
   114         self.modules[m] = compile(codestring, file, "exec")
       
   115 
       
   116     def adddata(self, file):
       
   117 
       
   118         self.modules["+"+file] = open(file, "rb").read()
       
   119 
       
   120     def getarchive(self):
       
   121 
       
   122         # marshal our module dictionary
       
   123         data = marshal.dumps(self.modules)
       
   124         self.rawbytes = len(data)
       
   125 
       
   126         # return (compressed) dictionary
       
   127         if zlib:
       
   128             data = zlib.compress(data, 9)
       
   129         self.bytes = len(data)
       
   130 
       
   131         return data
       
   132 
       
   133     def getstatus(self):
       
   134         return self.bytes, self.rawbytes
       
   135 
       
   136 
       
   137 # --------------------------------------------------------------------
       
   138 # loader (used in bootstrap code)
       
   139 
       
   140 loader = """
       
   141 import ihooks
       
   142 
       
   143 PYZ_MODULE = 64
       
   144 
       
   145 class Loader(ihooks.ModuleLoader):
       
   146 
       
   147     def __init__(self, modules):
       
   148         self.__modules = modules
       
   149         return ihooks.ModuleLoader.__init__(self)
       
   150 
       
   151     def find_module(self, name, path = None):
       
   152         try:
       
   153             self.__modules[name]
       
   154             return None, None, (None, None, PYZ_MODULE)
       
   155         except KeyError:
       
   156             return ihooks.ModuleLoader.find_module(self, name, path)
       
   157 
       
   158     def load_module(self, name, stuff):
       
   159         file, filename, (suff, mode, type) = stuff
       
   160         if type != PYZ_MODULE:
       
   161             return ihooks.ModuleLoader.load_module(self, name, stuff)
       
   162         #print "PYZ:", "import", name
       
   163         code = self.__modules[name]
       
   164         del self.__modules[name] # no need to keep this one around
       
   165         m = self.hooks.add_module(name)
       
   166         m.__file__ = filename
       
   167         exec code in m.__dict__
       
   168         return m
       
   169 
       
   170 def boot(name, fp, size, offset = 0):
       
   171 
       
   172     global data
       
   173 
       
   174     try:
       
   175         import %(modules)s
       
   176     except ImportError:
       
   177         #print "PYZ:", "failed to load marshal and zlib libraries"
       
   178         return # cannot boot from PYZ file
       
   179     #print "PYZ:", "boot from", name+".PYZ"
       
   180 
       
   181     # load archive and install import hook
       
   182     if offset:
       
   183         data = fp[offset:]
       
   184     else:
       
   185         data = fp.read(size)
       
   186         fp.close()
       
   187 
       
   188     if len(data) != size:
       
   189         raise IOError, "package is truncated"
       
   190 
       
   191     data = marshal.loads(%(data)s)
       
   192 
       
   193     ihooks.install(ihooks.ModuleImporter(Loader(data)))
       
   194 """
       
   195 
       
   196 loaderopen = """
       
   197 def open(name):
       
   198     import StringIO
       
   199     try:
       
   200         return StringIO.StringIO(data["+"+name])
       
   201     except KeyError:
       
   202         raise IOError, (0, "no such file")
       
   203 """
       
   204 
       
   205 loaderexplode = """
       
   206 
       
   207 def explode():
       
   208     for k, v in data.items():
       
   209         if k[0] == "+":
       
   210             try:
       
   211                 open(k[1:], "wb").write(v)
       
   212                 print k[1:], "extracted ok"
       
   213             except IOError, v:
       
   214                 print k[1:], "failed:", "IOError", v
       
   215 
       
   216 """
       
   217 
       
   218 def getloader(data, zlib, package):
       
   219 
       
   220     s = loader
       
   221 
       
   222     if data:
       
   223         if explode:
       
   224             s = s + loaderexplode
       
   225         else:
       
   226             s = s + loaderopen
       
   227 
       
   228     if zlib:
       
   229         dict = {
       
   230             "modules": "marshal, zlib",
       
   231             "data":    "zlib.decompress(data)",
       
   232             }
       
   233     else:
       
   234         dict = {
       
   235             "modules": "marshal",
       
   236             "data":    "data",
       
   237             }
       
   238 
       
   239     s = s % dict
       
   240 
       
   241     return marshal.dumps(compile(s, "<package>", "exec"))
       
   242 
       
   243 
       
   244 # --------------------------------------------------------------------
       
   245 # Main
       
   246 # --------------------------------------------------------------------
       
   247 
       
   248 #
       
   249 # parse options
       
   250 
       
   251 import getopt, glob, sys
       
   252 
       
   253 try:
       
   254     opt, arg = getopt.getopt(sys.argv[1:], "1b:o:suzxd")
       
   255 except: usage()
       
   256 
       
   257 app = ""
       
   258 start = ""
       
   259 embed = 0
       
   260 zlib = 1
       
   261 explode = 0
       
   262 
       
   263 data = None
       
   264 
       
   265 for i, v in opt:
       
   266     if i == "-o":
       
   267         app = v
       
   268     elif i == "-b":
       
   269         start = "import " + v
       
   270     elif i == "-d":
       
   271         data = 0
       
   272     elif i == "-1":
       
   273         embed = 1
       
   274     elif i == "-z":
       
   275         zlib = 1
       
   276     elif i == "-u":
       
   277         zlib = 0
       
   278     elif i == "-x":
       
   279         explode = 1
       
   280         start = "explode()"
       
   281 
       
   282 print app, start
       
   283 
       
   284 if not app or not start:
       
   285     usage()
       
   286 
       
   287 bootstrap = app + ".py"
       
   288 archive   = app + ".pyz"
       
   289 
       
   290 archiveid = app
       
   291 if explode:
       
   292     archiveid = "this is a self-extracting archive. run the script to unpack"
       
   293 elif embed:
       
   294     archiveid = "this is an embedded package"
       
   295 elif zlib:
       
   296     archiveid = "this is a bootstrap script for a compressed package"
       
   297 else:
       
   298     archiveid = "this is a bootstrap script for an uncompressed package"
       
   299 
       
   300 #
       
   301 # import compression library (as necessary)
       
   302 
       
   303 if zlib:
       
   304     try:
       
   305         import zlib
       
   306     except ImportError:
       
   307         print "You must have the zlib module to generate compressed archives."
       
   308         print "Squeeze will create an uncompressed archive."
       
   309         zlib = None
       
   310 
       
   311 #
       
   312 # avoid overwriting files not generated by squeeze
       
   313 
       
   314 try:
       
   315     fp = open(bootstrap)
       
   316     s = fp.readline()
       
   317     s = fp.readline()
       
   318     string.index(s, MAGIC)
       
   319 except IOError:
       
   320     pass
       
   321 except ValueError:
       
   322     print bootstrap, "was not created by squeeze.  You have to manually"
       
   323     print "remove the file to proceed."
       
   324     sys.exit(1)
       
   325 
       
   326 #
       
   327 # collect modules
       
   328 
       
   329 sq = Squeezer()
       
   330 for patt in arg:
       
   331     if patt == "-d":
       
   332         data = 0
       
   333     else:
       
   334         for file in glob.glob(patt):
       
   335             if file != bootstrap:
       
   336                 if data is not None:
       
   337                     print file, "(data)"
       
   338                     sq.adddata(file)
       
   339                     data = data + 1
       
   340                 else:
       
   341                     print file
       
   342                     sq.addmodule(file)
       
   343 
       
   344 package = sq.getarchive()
       
   345 size = len(package)
       
   346 
       
   347 #
       
   348 # get loader
       
   349 
       
   350 loader = getloader(data, zlib, package)
       
   351 
       
   352 if zlib:
       
   353     zbegin, zend = "zlib.decompress(", ")"
       
   354     zimport = 'try:import zlib\n'\
       
   355         'except:raise RuntimeError,"requires zlib"\n'
       
   356     loader = zlib.compress(loader, 9)
       
   357 else:
       
   358     zbegin = zend = zimport = ""
       
   359 
       
   360 loaderlen = len(loader)
       
   361 
       
   362 magic = repr(imp.get_magic())
       
   363 version = string.split(sys.version)[0]
       
   364 
       
   365 magictest = 'import imp\n'\
       
   366     's="requires python %s or bytecode compatible"\n'\
       
   367     'if imp.get_magic()!=%s:raise RuntimeError,s' % (version, magic)
       
   368 
       
   369 #
       
   370 # generate script and package files
       
   371 
       
   372 if embed:
       
   373 
       
   374     # embedded archive
       
   375     data = base64.encodestring(loader + package)
       
   376 
       
   377     fp = open(bootstrap, "w")
       
   378     fp.write('''\
       
   379 #!/usr/bin/env python
       
   380 #%(MAGIC)s %(archiveid)s
       
   381 %(magictest)s
       
   382 %(zimport)simport base64,marshal
       
   383 s=base64.decodestring("""
       
   384 %(data)s""")
       
   385 exec marshal.loads(%(zbegin)ss[:%(loaderlen)d]%(zend)s)
       
   386 boot("%(app)s",s,%(size)d,%(loaderlen)d)
       
   387 %(start)s
       
   388 ''' % locals())
       
   389     bytes = fp.tell()
       
   390 
       
   391 else:
       
   392 
       
   393     # separate archive file
       
   394 
       
   395     fp = open(archive, "wb")
       
   396 
       
   397     fp.write(loader)
       
   398     fp.write(package)
       
   399 
       
   400     bytes = fp.tell()
       
   401 
       
   402     #
       
   403     # create bootstrap code
       
   404 
       
   405     fp = open(bootstrap, "w")
       
   406     fp.write("""\
       
   407 #!/usr/bin/env python
       
   408 #%(MAGIC)s %(archiveid)s
       
   409 %(magictest)s
       
   410 %(zimport)simport marshal,sys,os
       
   411 for p in filter(os.path.exists,map(lambda p:os.path.join(p,"%(archive)s"),sys.path)):
       
   412  f=open(p,"rb")
       
   413  exec marshal.loads(%(zbegin)sf.read(%(loaderlen)d)%(zend)s)
       
   414  boot("%(app)s",f,%(size)d)
       
   415  break
       
   416 %(start)s # failed to load package
       
   417 """ % locals())
       
   418     bytes = bytes + fp.tell()
       
   419 
       
   420 #
       
   421 # show statistics
       
   422 
       
   423 dummy, rawbytes = sq.getstatus()
       
   424 
       
   425 print "squeezed", rawbytes, "to", bytes, "bytes",
       
   426 print "(%d%%)" % (bytes * 100 / rawbytes)