srcanamdw/codescanner/pyinstaller/Build.py
changeset 1 22878952f6e2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/srcanamdw/codescanner/pyinstaller/Build.py	Thu Feb 18 12:29:02 2010 +0530
@@ -0,0 +1,843 @@
+#! /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])
+
+
+
+