Browse Source

new merge tool

David Rose 16 years ago
parent
commit
d7f56e852d
2 changed files with 254 additions and 0 deletions
  1. 170 0
      direct/src/p3d/PackageMerger.py
  2. 84 0
      direct/src/p3d/pmerge.py

+ 170 - 0
direct/src/p3d/PackageMerger.py

@@ -0,0 +1,170 @@
+from direct.p3d.FileSpec import FileSpec
+from pandac.PandaModules import *
+import copy
+import shutil
+
+class PackageMergerError(StandardError):
+    pass
+
+class PackageMerger:
+    """ This class will combine two or more separately-build stage
+    directories, the output of Packager.py or the ppackage tool, into
+    a single output directory.  It assumes that the clocks on all
+    hosts are in sync, so that the file across all builds with the
+    most recent timestamp (indicated in the contents.xml file) is
+    always the most current version of the file. """
+ 
+    class PackageEntry:
+        """ This corresponds to a <package> entry in the contents.xml
+        file. """
+        
+        def __init__(self, xpackage, sourceDir):
+            self.sourceDir = sourceDir
+            self.loadXml(xpackage)
+
+        def getKey(self):
+            """ Returns a tuple used for sorting the PackageEntry
+            objects uniquely per package. """
+            return (self.packageName, self.platform, self.version)
+
+        def isNewer(self, other):
+            return self.descFile.timestamp > other.descFile.timestamp
+
+        def loadXml(self, xpackage):
+            self.packageName = xpackage.Attribute('name')
+            self.platform = xpackage.Attribute('platform')
+            self.version = xpackage.Attribute('version')
+            solo = xpackage.Attribute('solo')
+            self.solo = int(solo or '0')
+
+            self.descFile = FileSpec()
+            self.descFile.loadXml(xpackage)
+
+            self.importDescFile = None
+            ximport = xpackage.FirstChildElement('import')
+            if ximport:
+                self.importDescFile = FileSpec()
+                self.importDescFile.loadXml(ximport)
+
+        def makeXml(self):
+            """ Returns a new TiXmlElement. """
+            xpackage = TiXmlElement('package')
+            xpackage.SetAttribute('name', self.packageName)
+            if self.platform:
+                xpackage.SetAttribute('platform', self.platform)
+            if self.version:
+                xpackage.SetAttribute('version', self.version)
+            if self.solo:
+                xpackage.SetAttribute('solo', '1')
+
+            self.descFile.storeXml(xpackage)
+
+            if self.importDescFile:
+                ximport = TiXmlElement('import')
+                self.importDescFile.storeXml(ximport)
+                xpackage.InsertEndChild(ximport)
+            
+            return xpackage
+
+    # PackageMerger constructor
+    def __init__(self, installDir):
+        self.installDir = installDir
+        self.xhost = None
+        self.contents = {}
+
+        # We allow the first one to fail quietly.
+        self.__readContentsFile(self.installDir)
+
+    def __readContentsFile(self, sourceDir):
+        """ Reads the contents.xml file from the indicated sourceDir,
+        and updates the internal set of packages appropriately. """
+
+        contentsFilename = Filename(sourceDir, 'contents.xml')
+        doc = TiXmlDocument(contentsFilename.toOsSpecific())
+        if not doc.LoadFile():
+            # Couldn't read file.
+            return False
+
+        xcontents = doc.FirstChildElement('contents')
+        if xcontents:
+            xhost = xcontents.FirstChildElement('host')
+            if xhost:
+                self.xhost = xhost.Clone()
+                
+            xpackage = xcontents.FirstChildElement('package')
+            while xpackage:
+                pe = self.PackageEntry(xpackage, sourceDir)
+                other = self.contents.get(pe.getKey(), None)
+                if not other or pe.isNewer(other):
+                    # Store this package in the resulting output.
+                    self.contents[pe.getKey()] = pe
+                    
+                xpackage = xpackage.NextSiblingElement('package')
+
+        self.contentsDoc = doc
+
+        return True
+
+    def __writeContentsFile(self):
+        """ Writes the contents.xml file at the end of processing. """
+
+        filename = Filename(self.installDir, 'contents.xml')
+        doc = TiXmlDocument(filename.toOsSpecific())
+        decl = TiXmlDeclaration("1.0", "utf-8", "")
+        doc.InsertEndChild(decl)
+
+        xcontents = TiXmlElement('contents')
+        if self.xhost:
+            xcontents.InsertEndChild(self.xhost)
+
+        contents = self.contents.items()
+        contents.sort()
+        for key, pe in contents:
+            xpackage = pe.makeXml()
+            xcontents.InsertEndChild(xpackage)
+
+        doc.InsertEndChild(xcontents)
+        doc.SaveFile()
+
+    def __copySubdirectory(self, pe):
+        """ Copies the subdirectory referenced in the indicated
+        PackageEntry object into the installDir, replacing the
+        contents of any similarly-named subdirectory already
+        there. """
+
+        dirname = Filename(pe.descFile.filename).getDirname()
+        print "copying %s" % (dirname)
+        sourceDirname = Filename(pe.sourceDir, dirname)
+        targetDirname = Filename(self.installDir, dirname)
+
+        if targetDirname.exists():
+            # The target directory already exists; we have to clean it
+            # out first.
+            shutil.rmtree(targetDirname.toOsSpecific())
+
+        shutil.copytree(sourceDirname.toOsSpecific(), targetDirname.toOsSpecific())
+        
+
+
+    def merge(self, sourceDir):
+        """ Adds the contents of the indicated source directory into
+        the current pool. """
+        if not self.__readContentsFile(sourceDir):
+            message = "Couldn't read %s" % (sourceDir)
+            raise PackageMergerError, message            
+
+    def close(self):
+        """ Finalizes the results of all of the previous calls to
+        merge(), writes the new contents.xml file, and copies in all
+        of the new contents. """
+
+        dirname = Filename(self.installDir, '')
+        dirname.makeDir()
+
+        for pe in self.contents.values():
+            if pe.sourceDir != self.installDir:
+                # Here's a new subdirectory we have to copy in.
+                self.__copySubdirectory(pe)
+
+        self.__writeContentsFile()
+        

+ 84 - 0
direct/src/p3d/pmerge.py

@@ -0,0 +1,84 @@
+#! /usr/bin/env python
+
+usageText = """
+
+This script can be used to merge together the contents of two or more
+separately-built stage directories, built independently via ppackage,
+or via Packager.py.
+
+This script is actually a wrapper around Panda's PackageMerger.py.
+
+Usage:
+
+  %(prog)s [opts] [inputdir1 .. inputdirN]
+
+Parameters:
+
+  inputdir1 .. inputdirN
+    Specify the full path to the input directories you wish to merge.
+    These are the directories specified by -i on the previous
+    invocations of ppackage.  The order is mostly unimportant.
+
+Options:
+
+  -i install_dir
+     The full path to the final install directory.  This may also
+     contain some pre-existing contents; if so, it is merged with all
+     of the input directories as well.
+
+  -h
+     Display this help
+"""
+
+import sys
+import getopt
+import os
+
+from direct.p3d import PackageMerger
+from pandac.PandaModules import *
+
+def usage(code, msg = ''):
+    print >> sys.stderr, usageText % {'prog' : os.path.split(sys.argv[0])[1]}
+    print >> sys.stderr, msg
+    sys.exit(code)
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], 'i:h')
+except getopt.error, msg:
+    usage(1, msg)
+
+installDir = None
+for opt, arg in opts:
+    if opt == '-i':
+        installDir = Filename.fromOsSpecific(arg)
+        
+    elif opt == '-h':
+        usage(0)
+    else:
+        print 'illegal option: ' + flag
+        sys.exit(1)
+
+inputDirs = []
+for arg in args:
+    inputDirs.append(Filename.fromOsSpecific(arg))
+
+if not inputDirs:
+    print "no input directories specified."
+    sys.exit(1)
+
+try:
+    pm = PackageMerger.PackageMerger(installDir)
+    for dir in inputDirs:
+        pm.merge(dir)
+    pm.close()
+        
+except PackageMerger.PackageMergerError:
+    # Just print the error message and exit gracefully.
+    inst = sys.exc_info()[1]
+    print inst.args[0]
+    sys.exit(1)
+
+
+# An explicit call to exit() is required to exit the program, when
+# this module is packaged in a p3d file.
+sys.exit(0)