srcanamdw/codescanner/pyinstaller/Build.py
author noe\swadi
Thu, 18 Feb 2010 12:29:02 +0530
changeset 1 22878952f6e2
permissions -rw-r--r--
Committing the CodeScanner Core tool This component has been moved from the StaticAnaApps package. BUG : 5889 (http://developer.symbian.org/webbugs/show_bug.cgi?id=5889).

#! /usr/bin/env python
# Build packages using spec files
# Copyright (C) 2005, Giovanni Bajo
# Based on previous work under copyright (c) 1999, 2002 McMillan Enterprises, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
import sys, os, shutil, mf, archive, iu, carchive, pprint, time, py_compile, bindepend, tempfile

STRINGTYPE = type('')
TUPLETYPE = type((None,))

HOMEPATH = os.path.dirname(sys.argv[0])
SPECPATH = None
BUILDPATH = None
WARNFILE = None
rthooks = {}
iswin = sys.platform[:3] == 'win'
cygwin = sys.platform == 'cygwin'
try:
    config = eval(open(os.path.join(HOMEPATH, 'config.dat'), 'r').read())
except IOError:
    print "You must run Configure.py before building!"
    sys.exit(1)

if config['pythonVersion'] != sys.version:
    print "The current version of Python is not the same with which PyInstaller was configured."
    print "Please re-run Configure.py with this version."
    sys.exit(1)

if config['hasRsrcUpdate']:
    import icon, versionInfo

def setupUPXFlags():
    f = os.environ.get("UPX", "")
    is24 = hasattr(sys, "version_info") and sys.version_info[:2] >= (2,4)
    if iswin and is24:
        # Binaries built with Visual Studio 7.1 require --strip-loadconf
        # or they won't compress. Configure.py makes sure that UPX is new
        # enough to support --strip-loadconf.
        f = "--strip-loadconf " + f
    f = "--best " + f
    os.environ["UPX"] = f

if config['hasUPX']:
    setupUPXFlags()

def build(spec):
    global SPECPATH, BUILDPATH, WARNFILE, rthooks
    rthooks = eval(open(os.path.join(HOMEPATH, 'rthooks.dat'), 'r').read())
    SPECPATH, specnm = os.path.split(spec)
    specnm = os.path.splitext(specnm)[0]
    if SPECPATH == '':
        SPECPATH = os.getcwd()
    WARNFILE = os.path.join(SPECPATH, 'warn%s.txt' % specnm)
    BUILDPATH = os.path.join(SPECPATH, 'build%s' % specnm)
    if '-o' in sys.argv:
        bpath = sys.argv[sys.argv.index('-o')+1]
        if os.path.isabs(bpath):
            BUILDPATH = bpath
        else:
            BUILDPATH = os.path.join(SPECPATH, bpath)
    if not os.path.exists(BUILDPATH):
        os.mkdir(BUILDPATH)
    exec open(spec, 'r').read()+'\n'

def mtime(fnm):
    try:
        return os.stat(fnm)[8]
    except:
        return 0

class Target:
    invcnum = 0
    def __init__(self):
        self.invcnum = Target.invcnum
        Target.invcnum = Target.invcnum + 1
        self.out = os.path.join(BUILDPATH, 'out%d.toc' % self.invcnum)
        self.dependencies = TOC()
    def __postinit__(self):
        print "checking %s" % (self.__class__.__name__,)
        if self.check_guts(mtime(self.out)):
            self.assemble()

class Analysis(Target):
    def __init__(self, scripts=None, pathex=None, hookspath=None, excludes=None):
        Target.__init__(self)
        self.inputs = scripts
        for script in scripts:
            if not os.path.exists(script):
                raise ValueError, "script '%s' not found" % script
        self.pathex = []
        if pathex:
            for path in pathex:
                self.pathex.append(os.path.abspath(os.path.normpath(path)))
        self.hookspath = hookspath
        self.excludes = excludes
        self.scripts = TOC()
        self.pure = TOC()
        self.binaries = TOC()
        self.__postinit__()
    def check_guts(self, last_build):
        outnm = os.path.basename(self.out)
        if last_build == 0:
            print "building %s because %s non existent" % (self.__class__.__name__, outnm)
            return 1
        for fnm in self.inputs:
            if mtime(fnm) > last_build:
                print "building because %s changed" % fnm
                return 1
        try:
            inputs, pathex, hookspath, excludes, scripts, pure, binaries = eval(open(self.out, 'r').read())
        except:
            print "building because %s disappeared" % outnm
            return 1
        if inputs != self.inputs:
            print "building %s because inputs changed" % outnm
            return 1
        if pathex != self.pathex:
            print "building %s because pathex changed" % outnm
            return 1
        if hookspath != self.hookspath:
            print "building %s because hookspath changed" % outnm
            return 1
        if excludes != self.excludes:
            print "building %s because excludes changed" % outnm
            return 1
        for (nm, fnm, typ) in scripts:
            if mtime(fnm) > last_build:
                print "building because %s changed" % fnm
                return 1
        for (nm, fnm, typ) in pure:
            if mtime(fnm) > last_build:
                print "building because %s changed" % fnm
                return 1
            elif mtime(fnm[:-1]) > last_build:
                print "building because %s changed" % fnm[:-1]
                return 1
        for (nm, fnm, typ) in binaries:
            if mtime(fnm) > last_build:
                print "building because %s changed" % fnm
                return 1
        self.scripts = TOC(scripts)
        self.pure = TOC(pure)
        self.binaries = TOC(binaries)
        return 0
    def assemble(self):
        print "running Analysis", os.path.basename(self.out)
        paths = self.pathex
        for i in range(len(paths)):
            paths[i] = os.path.abspath(os.path.normpath(paths[i]))
        dirs = {}
        pynms = []
        for script in self.inputs:
            if not os.path.exists(script):
                print "Analysis: script %s not found!" % script
                sys.exit(1)
            d, base = os.path.split(script)
            if not d:
                d = os.getcwd()
            d = os.path.abspath(os.path.normpath(d))
            pynm, ext = os.path.splitext(base)
            dirs[d] = 1
            pynms.append(pynm)
        analyzer = mf.ImportTracker(dirs.keys()+paths, self.hookspath, self.excludes)
        #print analyzer.path
        scripts = []
        for i in range(len(self.inputs)):
            script = self.inputs[i]
            print "Analyzing:", script
            analyzer.analyze_script(script)
            scripts.append((pynms[i], script, 'PYSOURCE'))
        pure = []
        binaries = []
        rthooks = []
        for modnm, mod in analyzer.modules.items():
            if mod is not None:
                hooks = findRTHook(modnm)  #XXX
                if hooks:
                    rthooks.extend(hooks)
                if isinstance(mod, mf.BuiltinModule):
                    pass
                else:
                    fnm = mod.__file__
                    if isinstance(mod, mf.ExtensionModule):
                        binaries.append((mod.__name__, fnm, 'EXTENSION'))
                    elif modnm == '__main__':
                        pass
                    else:
                        pure.append((modnm, fnm, 'PYMODULE'))
        binaries.extend(bindepend.Dependencies(binaries))
        scripts[1:1] = rthooks
        self.scripts = TOC(scripts)
        self.pure = TOC(pure)
        self.binaries = TOC(binaries)
        try:
            oldstuff = eval(open(self.out, 'r').read())
        except:
            oldstuff = None
        if oldstuff != (self.inputs, self.pathex, self.hookspath, self.excludes, scripts, pure, binaries):
            outf = open(self.out, 'w')
            pprint.pprint(
                (self.inputs, self.pathex, self.hookspath, self.excludes, self.scripts, self.pure, self.binaries),
                outf)
            outf.close()
            wf = open(WARNFILE, 'w')
            for ln in analyzer.getwarnings():
                wf.write(ln+'\n')
            wf.close()
            print "Warnings written to %s" % WARNFILE
            return 1
        print self.out, "no change!"
        return 0

def findRTHook(modnm):
    hooklist = rthooks.get(modnm)
    if hooklist:
        rslt = []
        for script in hooklist:
            nm = os.path.basename(script)
            nm = os.path.splitext(nm)[0]
            if os.path.isabs(script):
                path = script
            else:
                path = os.path.join(HOMEPATH, script)
            rslt.append((nm, path, 'PYSOURCE'))
        return rslt
    return None

class PYZ(Target):
    typ = 'PYZ'
    def __init__(self, toc, name=None, level=9):
        Target.__init__(self)
        self.toc = toc
        self.name = name
        if name is None:
            self.name = self.out[:-3] + 'pyz'
        if config['useZLIB']:
            self.level = level
        else:
            self.level = 0
        self.dependencies = config['PYZ_dependencies']
        self.__postinit__()
    def check_guts(self, last_build):
        outnm = os.path.basename(self.out)
        if not os.path.exists(self.name):
            print "rebuilding %s because %s is missing" % (outnm, os.path.basename(self.name))
            return 1
        try:
            name, level, toc = eval(open(self.out, 'r').read())
        except:
            print "rebuilding %s because missing" % outnm
            return 1
        if name != self.name:
            print "rebuilding %s because name changed" % outnm
            return 1
        if level != self.level:
            print "rebuilding %s because level changed" % outnm
            return 1
        if toc != self.toc:
            print "rebuilding %s because toc changed" % outnm
            return 1
        for (nm, fnm, typ) in toc:
            if mtime(fnm) > last_build:
                print "rebuilding %s because %s changed" % (outnm, fnm)
                return 1
            if fnm[-1] in ('c', 'o'):
                if mtime(fnm[:-1]) > last_build:
                    print "rebuilding %s because %s changed" % (outnm, fnm[:-1])
                    return 1
        return 0
    def assemble(self):
        print "building PYZ", os.path.basename(self.out)
        pyz = archive.ZlibArchive(level=self.level)
        toc = self.toc - config['PYZ_dependencies']
        for (nm, fnm, typ) in toc:
            if mtime(fnm[:-1]) > mtime(fnm):
                py_compile.compile(fnm[:-1])
        pyz.build(self.name, toc)
        outf = open(self.out, 'w')
        pprint.pprint((self.name, self.level, self.toc), outf)
        outf.close()
        return 1

def checkCache(fnm, strip, upx):
    if not strip and not upx:
        return fnm
    if strip:
        strip = 1
    else:
        strip = 0
    if upx:
        upx = 1
    else:
        upx = 0
    cachedir = os.path.join(HOMEPATH, 'bincache%d%d' %  (strip, upx))
    if not os.path.exists(cachedir):
        os.makedirs(cachedir)
    basenm = os.path.basename(fnm)
    cachedfile = os.path.join(cachedir, basenm )
    if os.path.exists(cachedfile):
        if mtime(fnm) > mtime(cachedfile):
            os.remove(cachedfile)
        else:
            return cachedfile
    if upx:
        if strip:
            fnm = checkCache(fnm, 1, 0)
        cmd = "upx --best -q \"%s\"" % cachedfile
    else:
        cmd = "strip \"%s\"" % cachedfile
    shutil.copy2(fnm, cachedfile)
    os.chmod(cachedfile, 0755)
    os.system(cmd)
    return cachedfile

UNCOMPRESSED, COMPRESSED = range(2)
class PKG(Target):
    typ = 'PKG'
    xformdict = {'PYMODULE' : 'm',
                 'PYSOURCE' : 's',
                 'EXTENSION' : 'b',
                 'PYZ' : 'z',
                 'PKG' : 'a',
                 'DATA': 'x',
                 'BINARY': 'b',
                 'EXECUTABLE': 'b'}
    def __init__(self, toc, name=None, cdict=None, exclude_binaries=0,
                 strip_binaries=0, upx_binaries=0):
        Target.__init__(self)
        self.toc = toc
        self.cdict = cdict
        self.name = name
        self.exclude_binaries = exclude_binaries
        self.strip_binaries = strip_binaries
        self.upx_binaries = upx_binaries
        if name is None:
            self.name = self.out[:-3] + 'pkg'
        if self.cdict is None:
            if config['useZLIB']:
                self.cdict = {'EXTENSION':COMPRESSED,
                              'DATA':COMPRESSED,
                              'BINARY':COMPRESSED,
                              'EXECUTABLE':COMPRESSED,
                              'PYSOURCE':COMPRESSED,
                              'PYMODULE':COMPRESSED }
            else:
                self.cdict = { 'PYSOURCE':UNCOMPRESSED }
        self.__postinit__()
    def check_guts(self, last_build):
        outnm = os.path.basename(self.out)
        if not os.path.exists(self.name):
            print "rebuilding %s because %s is missing" % (outnm, os.path.basename(self.name))
            return 1
        try:
            name, cdict, toc, exclude_binaries, strip_binaries, upx_binaries = eval(open(self.out, 'r').read())
        except:
            print "rebuilding %s because %s is missing" % (outnm, outnm)
            return 1
        if name != self.name:
            print "rebuilding %s because name changed" % outnm
            return 1
        if cdict != self.cdict:
            print "rebuilding %s because cdict changed" % outnm
            return 1
        if toc != self.toc:
            print "rebuilding %s because toc changed" % outnm
            return 1
        if exclude_binaries != self.exclude_binaries:
            print "rebuilding %s because exclude_binaries changed" % outnm
            return 1
        if strip_binaries != self.strip_binaries:
            print "rebuilding %s because strip_binaries changed" % outnm
            return 1
        if upx_binaries != self.upx_binaries:
            print "rebuilding %s because upx_binaries changed" % outnm
            return 1
        for (nm, fnm, typ) in toc:
            if mtime(fnm) > last_build:
                print "rebuilding %s because %s changed" % (outnm, fnm)
                return 1
        return 0
    def assemble(self):
        print "building PKG", os.path.basename(self.name)
        trash = []
        mytoc = []
        toc = TOC()
        for item in self.toc:
            inm, fnm, typ = item
            if typ == 'EXTENSION':
                binext = os.path.splitext(fnm)[1]
                if not os.path.splitext(inm)[1] == binext:
                    inm = inm + binext
            toc.append((inm, fnm, typ))
        seen = {}
        for inm, fnm, typ in toc:
            if typ in ('BINARY', 'EXTENSION'):
                if self.exclude_binaries:
                    self.dependencies.append((inm, fnm, typ))
                else:
                    fnm = checkCache(fnm, self.strip_binaries,
                                     self.upx_binaries and ( iswin or cygwin )
                                      and config['hasUPX'])
                    # Avoid importing the same binary extension twice. This might
                    # happen if they come from different sources (eg. once from
                    # binary dependence, and once from direct import).
                    if typ == 'BINARY' and seen.has_key(fnm):
                        continue
                    seen[fnm] = 1
                    mytoc.append((inm, fnm, self.cdict.get(typ,0),
                                  self.xformdict.get(typ,'b')))
            elif typ == 'OPTION':
                mytoc.append((inm, '', 0, 'o'))
            else:
                mytoc.append((inm, fnm, self.cdict.get(typ,0), self.xformdict.get(typ,'b')))
        archive = carchive.CArchive()
        archive.build(self.name, mytoc)
        outf = open(self.out, 'w')
        pprint.pprint((self.name, self.cdict, self.toc, self.exclude_binaries, self.strip_binaries, self.upx_binaries), outf)
        outf.close()
        for item in trash:
            os.remove(item)
        return 1

class ELFEXE(Target):
    typ = 'EXECUTABLE'
    exclude_binaries = 0
    def __init__(self, *args, **kws):
        Target.__init__(self)
        self.console = kws.get('console',1)
        self.debug = kws.get('debug',0)
        self.name = kws.get('name',None)
        self.icon = kws.get('icon',None)
        self.versrsrc = kws.get('version',None)
        self.strip = kws.get('strip',None)
        self.upx = kws.get('upx',None)
        self.exclude_binaries = kws.get('exclude_binaries',0)
        if self.name is None:
            self.name = self.out[:-3] + 'exe'
        if not os.path.isabs(self.name):
            self.name = os.path.join(SPECPATH, self.name)
        self.toc = TOC()
        for arg in args:
            if isinstance(arg, TOC):
                self.toc.extend(arg)
            elif isinstance(arg, Target):
                self.toc.append((os.path.basename(arg.name), arg.name, arg.typ))
                self.toc.extend(arg.dependencies)
            else:
                self.toc.extend(arg)
        self.toc.extend(config['EXE_dependencies'])
        self.pkg = PKG(self.toc, cdict=kws.get('cdict',None), exclude_binaries=self.exclude_binaries,
                       strip_binaries=self.strip, upx_binaries=self.upx)
        self.dependencies = self.pkg.dependencies
        self.__postinit__()
    def check_guts(self, last_build):
        outnm = os.path.basename(self.out)
        if not os.path.exists(self.name):
            print "rebuilding %s because %s missing" % (outnm, os.path.basename(self.name))
            return 1
        try:
            name, console, debug, icon, versrsrc, strip, upx, mtm = eval(open(self.out, 'r').read())
        except:
            print "rebuilding %s because %s missing or bad" % (outnm, outnm)
            return 1
        if name != self.name:
            print "rebuilding %s because name changed" % outnm
            return 1
        if console != self.console:
            print "rebuilding %s because console option changed" % outnm
            return 1
        if debug != self.debug:
            print "rebuilding %s because debug option changed" % outnm
            return 1
        if config['hasRsrcUpdate']:
            if icon != self.icon:
                print "rebuilding %s because icon option changed" % outnm
                return 1
            if versrsrc != self.versrsrc:
                print "rebuilding %s because versrsrc option changed" % outnm
                return 1
        else:
            if icon or versrsrc:
                print "ignoring icon and version resources = platform not capable"
        if strip != self.strip:
            print "rebuilding %s because strip option changed" % outnm
            return 1
        if upx != self.upx:
            print "rebuilding %s because upx option changed" % outnm
            return 1
        if mtm != mtime(self.name):
            print "rebuilding %s because mtimes don't match" % outnm
            return 1
        if mtm < mtime(self.pkg.out):
            print "rebuilding %s because pkg is more recent" % outnm
            return 1
        return 0
    def _bootloader_postfix(self, exe):
        if iswin:
            exe = exe + "_"
            is24 = hasattr(sys, "version_info") and sys.version_info[:2] >= (2,4)
            exe = exe + "67"[is24]
            exe = exe + "rd"[self.debug]
            exe = exe + "wc"[self.console]
        else:
            if not self.console:
                exe = exe + 'w'
            if self.debug:
                exe = exe + '_d'
        return exe
    def assemble(self):
        print "building ELFEXE", os.path.basename(self.out)
        trash = []
        outf = open(self.name, 'wb')
        exe = self._bootloader_postfix('support/loader/run')
        exe = os.path.join(HOMEPATH, exe)
        if iswin or cygwin:
            exe = exe + '.exe'
        if config['hasRsrcUpdate']:
            if self.icon:
                tmpnm = tempfile.mktemp()
                shutil.copy2(exe, tmpnm)
                os.chmod(tmpnm, 0755)
                icon.CopyIcons(tmpnm, self.icon)
                trash.append(tmpnm)
                exe = tmpnm
            if self.versrsrc:
                tmpnm = tempfile.mktemp()
                shutil.copy2(exe, tmpnm)
                os.chmod(tmpnm, 0755)
                versionInfo.SetVersion(tmpnm, self.versrsrc)
                trash.append(tmpnm)
                exe = tmpnm
        exe = checkCache(exe, self.strip, self.upx and config['hasUPX'])
        self.copy(exe, outf)
        self.copy(self.pkg.name, outf)
        outf.close()
        os.chmod(self.name, 0755)
        f = open(self.out, 'w')
        pprint.pprint((self.name, self.console, self.debug, self.icon, self.versrsrc,
                       self.strip, self.upx, mtime(self.name)), f)
        f.close()
        for item in trash:
            os.remove(item)
        return 1
    def copy(self, fnm, outf):
        inf = open(fnm, 'rb')
        while 1:
            data = inf.read(64*1024)
            if not data:
                break
            outf.write(data)

class DLL(ELFEXE):
    def assemble(self):
        print "building DLL", os.path.basename(self.out)
        outf = open(self.name, 'wb')
        dll = self._bootloader_postfix('support/loader/inprocsrvr')
        dll = os.path.join(HOMEPATH, dll)  + '.dll'
        self.copy(dll, outf)
        self.copy(self.pkg.name, outf)
        outf.close()
        os.chmod(self.name, 0755)
        f = open(self.out, 'w')
        pprint.pprint((self.name, self.console, self.debug, self.icon, self.versrsrc,
                       self.strip, self.upx, mtime(self.name)), f)
        f.close()
        return 1

class NonELFEXE(ELFEXE):
    def assemble(self):
        print "building NonELFEXE", os.path.basename(self.out)
        trash = []
        exe = 'support/loader/run'
        if not self.console:
            exe = exe + 'w'
        if self.debug:
            exe = exe + '_d'
        exe = os.path.join(HOMEPATH, exe)
        exe = checkCache(exe, self.strip, self.upx and config['hasUPX'])
        shutil.copy2(exe, self.name)
        os.chmod(self.name, 0755)
        shutil.copy2(self.pkg.name, self.name+'.pkg')
        f = open(self.out, 'w')
        pprint.pprint((self.name, self.console, self.debug, self.icon, self.versrsrc,
                       self.strip, self.upx, mtime(self.name)), f)
        f.close()
        for fnm in trash:
            os.remove(fnm)
        return 1

if config['useELFEXE']:
    EXE = ELFEXE
else:
    EXE = NonELFEXE

class COLLECT(Target):
    def __init__(self, *args, **kws):
        Target.__init__(self)
        self.name = kws.get('name',None)
        if self.name is None:
            self.name = 'dist_' + self.out[:-4]
        self.strip_binaries = kws.get('strip',0)
        self.upx_binaries = kws.get('upx',0)
        if not os.path.isabs(self.name):
            self.name = os.path.join(SPECPATH, self.name)
        self.toc = TOC()
        for arg in args:
            if isinstance(arg, TOC):
                self.toc.extend(arg)
            elif isinstance(arg, Target):
                self.toc.append((os.path.basename(arg.name), arg.name, arg.typ))
                if isinstance(arg, NonELFEXE):
                    self.toc.append((os.path.basename(arg.name)+'.pkg', arg.name+'.pkg', 'PKG'))
                self.toc.extend(arg.dependencies)
            else:
                self.toc.extend(arg)
        self.__postinit__()
    def check_guts(self, last_build):
        outnm = os.path.basename(self.out)
        try:
            name, strip_binaries, upx_binaries, toc = eval(open(self.out, 'r').read())
        except:
            print "building %s because %s missing" % (outnm, outnm)
            return 1
        if name != self.name:
            print "building %s because name changed" % outnm
            return 1
        if strip_binaries != self.strip_binaries:
            print "building %s because strip_binaries option changed" % outnm
            return 1
        if upx_binaries != self.upx_binaries:
            print "building %s because upx_binaries option changed" % outnm
            return 1
        if toc != self.toc:
            print "building %s because toc changed" % outnm
            return 1
        for inm, fnm, typ in self.toc:
            if typ == 'EXTENSION':
                ext = os.path.splitext(fnm)[1]
                test = os.path.join(self.name, inm+ext)
            else:
                test = os.path.join(self.name, os.path.basename(fnm))
            if not os.path.exists(test):
                print "building %s because %s is missing" % (outnm, test)
                return 1
            if mtime(fnm) > mtime(test):
                print "building %s because %s is more recent" % (outnm, fnm)
                return 1
        return 0
    def assemble(self):
        print "building COLLECT", os.path.basename(self.out)
        if not os.path.exists(self.name):
            os.mkdir(self.name)
        toc = TOC()
        for inm, fnm, typ in self.toc:
            if typ == 'EXTENSION':
                binext = os.path.splitext(fnm)[1]
                if not os.path.splitext(inm)[1] == binext:
                    inm = inm + binext
            toc.append((inm, fnm, typ))
        for inm, fnm, typ in toc:
            tofnm = os.path.join(self.name, inm)
            todir = os.path.dirname(tofnm)
            if not os.path.exists(todir):
                os.makedirs(todir)
            if typ in ('EXTENSION', 'BINARY'):
                fnm = checkCache(fnm, self.strip_binaries,
                                 self.upx_binaries and ( iswin or cygwin )
                                  and config['hasUPX'])
            shutil.copy2(fnm, tofnm)
            if typ in ('EXTENSION', 'BINARY'):
                os.chmod(tofnm, 0755)
        f = open(self.out, 'w')
        pprint.pprint((self.name, self.strip_binaries, self.upx_binaries, self.toc), f)
        f.close()
        return 1

import UserList
class TOC(UserList.UserList):
    def __init__(self, initlist=None):
        UserList.UserList.__init__(self)
        self.fltr = {}
        if initlist:
            for tpl in initlist:
                self.append(tpl)
    def append(self, tpl):
        try:
            if not self.fltr.get(tpl[0]):
                self.data.append(tpl)
                self.fltr[tpl[0]] = 1
        except TypeError:
            print "TOC found a %s, not a tuple" % tpl
            raise
    def insert(self, pos, tpl):
        if not self.fltr.get(tpl[0]):
            self.data.insert(pos, tpl)
            self.fltr[tpl[0]] = 1
    def __add__(self, other):
        rslt = TOC(self.data)
        rslt.extend(other)
        return rslt
    def __radd__(self, other):
        rslt = TOC(other)
        rslt.extend(self.data)
        return rslt
    def extend(self, other):
        for tpl in other:
            self.append(tpl)
    def __sub__(self, other):
        fd = self.fltr.copy()
        # remove from fd if it's in other
        for tpl in other:
            if fd.get(tpl[0],0):
                del fd[tpl[0]]
        rslt = TOC()
        # return only those things still in fd (preserve order)
        for tpl in self.data:
            if fd.get(tpl[0],0):
                rslt.append(tpl)
        return rslt
    def __rsub__(self, other):
        rslt = TOC(other)
        return rslt.__sub__(self)
    def intersect(self, other):
        rslt = TOC()
        for tpl in other:
            if self.fltr.get(tpl[0],0):
                rslt.append(tpl)
        return rslt

class Tree(Target, TOC):
    def __init__(self, root=None, prefix=None, excludes=None):
        Target.__init__(self)
        TOC.__init__(self)
        self.root = root
        self.prefix = prefix
        self.excludes = excludes
        if excludes is None:
            self.excludes = []
        self.__postinit__()
    def check_guts(self, last_build):
        outnm = os.path.basename(self.out)
        try:
            root, prefix, excludes, toc = eval(open(self.out, 'r').read())
        except:
            print "building %s because %s is missing / bad" % (outnm, outnm)
            return 1
        if root != self.root:
            print "building %s because root changed" % outnm
            return 1
        if prefix != self.prefix:
            print "building %s because prefix changed" % outnm
            return 1
        if excludes != self.excludes:
            print "building %s because excludes changed" % outnm
            return 1
        stack = [root]
        while stack:
            d = stack.pop()
            if mtime(d) > last_build:
                print "building %s because directory %s changed" % (outnm, d)
                return 1
            for nm in os.listdir(d):
                path = os.path.join(d, nm)
                if os.path.isdir(path):
                    stack.append(path)
        self.data = toc
        return 0
    def assemble(self):
        print "building Tree", os.path.basename(self.out)
        stack = [(self.root, self.prefix)]
        excludes = {}
        xexcludes = {}
        for nm in self.excludes:
            if nm[0] == '*':
                xexcludes[nm[1:]] = 1
            else:
                excludes[nm] = 1
        rslt = []
        while stack:
            dir, prefix = stack.pop()
            for fnm in os.listdir(dir):
                if excludes.get(fnm, 0) == 0:
                    ext = os.path.splitext(fnm)[1]
                    if xexcludes.get(ext,0) == 0:
                        fullfnm = os.path.join(dir, fnm)
                        rfnm = prefix and os.path.join(prefix, fnm) or fnm
                        if os.path.isdir(fullfnm):
                            stack.append((fullfnm, rfnm))
                        else:
                            rslt.append((rfnm, fullfnm, 'DATA'))
        try:
            oldstuff = eval(open(self.out, 'r').read())
        except:
            oldstuff = None
        if oldstuff != (self.root, self.prefix, self.excludes, rslt):
            outf = open(self.out, 'w')
            pprint.pprint((self.root, self.prefix, self.excludes, rslt), outf)
            outf.close()
            self.data = rslt
            return 1
        print self.out, "no change!"
        return 0

def TkTree():
    tclroot = config['TCL_root']
    tclnm = os.path.join('_MEI', os.path.basename(tclroot))
    tkroot = config['TK_root']
    tknm = os.path.join('_MEI', os.path.basename(tkroot))
    tcltree = Tree(tclroot, tclnm, excludes=['demos','encoding','*.lib'])
    tktree = Tree(tkroot, tknm, excludes=['demos','encoding','*.lib'])
    return tcltree + tktree

def TkPKG():
    return PKG(TkTree(), name='tk.pkg')

usage = """\
Usage: python %s <specfile>

See doc/Tutorial.html for details.
"""

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print usage % sys.argv[0]
    else:
        build(sys.argv[1])