diff --git a/direct/src/p3d/PackageMerger.py b/direct/src/p3d/PackageMerger.py new file mode 100644 index 0000000000..31ebc3d21c --- /dev/null +++ b/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 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() + diff --git a/direct/src/p3d/pmerge.py b/direct/src/p3d/pmerge.py new file mode 100755 index 0000000000..f766f7a9fe --- /dev/null +++ b/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)