mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-02 09:52:27 -04:00
PatchMaker
This commit is contained in:
parent
1fc186bda4
commit
f1588e4e9f
@ -519,27 +519,6 @@ class Packager:
|
||||
|
||||
self.cleanup()
|
||||
|
||||
## def buildPatch(self, origFilename, newFilename):
|
||||
## """ Creates a patch file from origFilename to newFilename,
|
||||
## in a temporary filename. Returns the temporary filename
|
||||
## on success, or None on failure. """
|
||||
|
||||
## if not origFilename.exists():
|
||||
## # No original version to patch from.
|
||||
## return None
|
||||
|
||||
## print "Building patch from %s" % (origFilename)
|
||||
## patchFilename = Filename.temporary('', self.packageName + '.', '.patch')
|
||||
## p = Patchfile()
|
||||
## if p.build(origFilename, newFilename, patchFilename):
|
||||
## return patchFilename
|
||||
|
||||
## # Unable to build a patch for some reason.
|
||||
## patchFilename.unlink()
|
||||
## return None
|
||||
|
||||
|
||||
|
||||
def installSolo(self):
|
||||
""" Installs the package as a "solo", which means we
|
||||
simply copy the one file into the install directory. This
|
||||
|
444
direct/src/p3d/PatchMaker.py
Normal file
444
direct/src/p3d/PatchMaker.py
Normal file
@ -0,0 +1,444 @@
|
||||
from direct.p3d.FileSpec import FileSpec
|
||||
from pandac.PandaModules import *
|
||||
|
||||
class PatchMaker:
|
||||
""" This class will operate on an existing package install
|
||||
directory, as generated by the Packager, and create patchfiles
|
||||
between versions as needed. """
|
||||
|
||||
class PackageVersion:
|
||||
""" A specific patch version of a package. This is not just
|
||||
the package's "version" number; it also corresponds to the
|
||||
particular patch version, which increments independently of
|
||||
the "version". """
|
||||
|
||||
def __init__(self, packageName, platform, version, host, hash):
|
||||
self.packageName = packageName
|
||||
self.platform = platform
|
||||
self.version = version
|
||||
self.host = host
|
||||
self.hash = hash
|
||||
|
||||
# The Package object that produces this version, in the
|
||||
# current form or the base form, respectively.
|
||||
self.packageCurrent = None
|
||||
self.packageBase = None
|
||||
|
||||
# A list of patchfiles that can produce this version.
|
||||
self.fromPatches = []
|
||||
|
||||
# A list of patchfiles that can start from this version.
|
||||
self.toPatches = []
|
||||
|
||||
# A temporary file for re-creating the archive file for
|
||||
# this version.
|
||||
self.tempFile = None
|
||||
|
||||
def cleanup(self):
|
||||
if self.tempFile:
|
||||
self.tempFile.unlink()
|
||||
|
||||
def getFile(self):
|
||||
""" Returns the Filename of the archive file associated
|
||||
with this version. If the file doesn't actually exist on
|
||||
disk, a temporary file will be created. Returns None if
|
||||
the file can't be recreated. """
|
||||
|
||||
if self.tempFile:
|
||||
return self.tempFile
|
||||
|
||||
if self.packageCurrent:
|
||||
package = self.packageCurrent
|
||||
return Filename(package.packageDir, package.currentFile.filename)
|
||||
if self.packageBase:
|
||||
package = self.packageBase
|
||||
return Filename(package.packageDir, package.baseFile.filename)
|
||||
|
||||
# We'll need to re-create the file.
|
||||
for patchfile in self.fromPatches:
|
||||
fromPv = patchfile.fromPv
|
||||
prevFile = fromPv.getFile()
|
||||
if prevFile:
|
||||
patchFilename = Filename(patchfile.package.packageDir, patchfile.file.filename)
|
||||
result = self.applyPatch(prevFile, patchFilename, patchfile.toHash)
|
||||
if result:
|
||||
self.tempFile = result
|
||||
return result
|
||||
|
||||
# Couldn't re-create the file.
|
||||
return None
|
||||
|
||||
def applyPatch(self, origFile, patchFilename, targetHash):
|
||||
""" Applies the named patch to the indicated original
|
||||
file, storing the results in a temporary file, and returns
|
||||
that temporary Filename. Returns None on failure. """
|
||||
|
||||
result = Filename.temporary('', 'patch_')
|
||||
print "patching %s + %s -> %s" % (
|
||||
origFile, patchFilename, result)
|
||||
|
||||
p = Patchfile()
|
||||
if not p.apply(patchFilename, origFile, result):
|
||||
print "patching failed."
|
||||
return None
|
||||
|
||||
hv = HashVal()
|
||||
hv.hashFile(result)
|
||||
if hv.asHex() != targetHash:
|
||||
print "patching produced incorrect results."
|
||||
result.unlink()
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
def getNext(self, package):
|
||||
""" Gets the next patch in the chain towards this
|
||||
package. """
|
||||
for patch in self.toPatches:
|
||||
if patch.packageName == package.packageName and \
|
||||
patch.platform == package.platform and \
|
||||
patch.version == package.version and \
|
||||
patch.host == package.host:
|
||||
return patch.toPv
|
||||
|
||||
return None
|
||||
|
||||
class Patchfile:
|
||||
""" A single patchfile for a package. """
|
||||
|
||||
def __init__(self, package):
|
||||
self.package = package
|
||||
self.packageName = package.packageName
|
||||
self.platform = package.platform
|
||||
self.version = package.version
|
||||
self.host = None
|
||||
|
||||
def getFromKey(self):
|
||||
return (self.packageName, self.platform, self.version, self.host, self.fromHash)
|
||||
|
||||
def getToKey(self):
|
||||
return (self.packageName, self.platform, self.version, self.host, self.toHash)
|
||||
|
||||
def fromFile(self, packageDir, patchFilename,
|
||||
fromHash, toHash):
|
||||
self.file = FileSpec()
|
||||
self.file.fromFile(packageDir, patchFilename)
|
||||
self.fromHash = fromHash
|
||||
self.toHash = toHash
|
||||
|
||||
def loadXml(self, xpatch):
|
||||
self.packageName = xpatch.Attribute('name') or self.packageName
|
||||
self.platform = xpatch.Attribute('platform') or self.platform
|
||||
self.version = xpatch.Attribute('version') or self.version
|
||||
self.host = xpatch.Attribute('host') or self.host
|
||||
|
||||
self.file = FileSpec()
|
||||
self.file.loadXml(xpatch)
|
||||
|
||||
self.fromHash = xpatch.Attribute('from_hash')
|
||||
self.toHash = xpatch.Attribute('to_hash')
|
||||
|
||||
def makeXml(self, package):
|
||||
xpatch = TiXmlElement('patch')
|
||||
|
||||
if self.packageName != package.packageName:
|
||||
xpatch.SetAttribute('name', self.packageName)
|
||||
if self.platform != package.platform:
|
||||
xpatch.SetAttribute('platform', self.platform)
|
||||
if self.version != package.version:
|
||||
xpatch.SetAttribute('version', self.version)
|
||||
if self.host != package.host:
|
||||
xpatch.SetAttribute('host', self.host)
|
||||
|
||||
self.file.storeXml(xpatch)
|
||||
|
||||
xpatch.SetAttribute('from_hash', self.fromHash)
|
||||
xpatch.SetAttribute('to_hash', self.toHash)
|
||||
|
||||
return xpatch
|
||||
|
||||
class Package:
|
||||
""" This is a particular package. This contains all of the
|
||||
information needed to reconstruct the package's desc file. """
|
||||
|
||||
def __init__(self, packageDesc, patchMaker):
|
||||
self.packageDir = Filename(patchMaker.installDir, packageDesc.getDirname())
|
||||
self.packageDesc = packageDesc
|
||||
self.patchMaker = patchMaker
|
||||
self.patchVersion = 1
|
||||
|
||||
self.doc = None
|
||||
self.anyChanges = False
|
||||
self.patches = []
|
||||
|
||||
def getCurrentKey(self):
|
||||
return (self.packageName, self.platform, self.version, self.host, self.currentFile.hash)
|
||||
|
||||
def getBaseKey(self):
|
||||
return (self.packageName, self.platform, self.version, self.host, self.baseFile.hash)
|
||||
|
||||
def readDescFile(self):
|
||||
""" Reads the existing package.xml file and stores
|
||||
it in this class for later rewriting. """
|
||||
|
||||
packageDescFullpath = Filename(self.patchMaker.installDir, self.packageDesc)
|
||||
self.doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
|
||||
if not self.doc.LoadFile():
|
||||
return
|
||||
|
||||
xpackage = self.doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
return
|
||||
self.packageName = xpackage.Attribute('name')
|
||||
self.platform = xpackage.Attribute('platform')
|
||||
self.version = xpackage.Attribute('version')
|
||||
|
||||
# All packages we defined in-line are assigned to the
|
||||
# "none" host. TODO: support patching from packages on
|
||||
# other hosts, which means we'll need to fill in a value
|
||||
# here for those hosts.
|
||||
self.host = None
|
||||
|
||||
# Get the current patch version. If we have a
|
||||
# patch_version attribute, it refers to this particular
|
||||
# instance of the file, and that is the current patch
|
||||
# version number. If we only have a last_patch_version
|
||||
# attribute, it means a patch has not yet been built for
|
||||
# this particular instance, and that number is the
|
||||
# previous version's patch version number.
|
||||
patchVersion = xpackage.Attribute('patch_version')
|
||||
if patchVersion:
|
||||
self.patchVersion = int(patchVersion)
|
||||
else:
|
||||
patchVersion = xpackage.Attribute('last_patch_version')
|
||||
if patchVersion:
|
||||
self.patchVersion = int(patchVersion)
|
||||
self.patchVersion += 1
|
||||
|
||||
self.currentFile = None
|
||||
self.baseFile = None
|
||||
|
||||
xarchive = xpackage.FirstChildElement('uncompressed_archive')
|
||||
if xarchive:
|
||||
self.currentFile = FileSpec()
|
||||
self.currentFile.loadXml(xarchive)
|
||||
|
||||
xarchive = xpackage.FirstChildElement('base_version')
|
||||
if xarchive:
|
||||
self.baseFile = FileSpec()
|
||||
self.baseFile.loadXml(xarchive)
|
||||
|
||||
self.patches = []
|
||||
xpatch = xpackage.FirstChildElement('patch')
|
||||
while xpatch:
|
||||
patchfile = PatchMaker.Patchfile(self)
|
||||
patchfile.loadXml(xpatch)
|
||||
self.patches.append(patchfile)
|
||||
xpatch = xpatch.NextSiblingElement('patch')
|
||||
|
||||
self.anyChanges = False
|
||||
|
||||
def writeDescFile(self):
|
||||
""" Rewrites the desc file with the new patch
|
||||
information. """
|
||||
|
||||
if not self.anyChanges:
|
||||
# No need to rewrite.
|
||||
return
|
||||
|
||||
xpackage = self.doc.FirstChildElement('package')
|
||||
if not xpackage:
|
||||
return
|
||||
|
||||
# Remove all of the old patch entries from the desc file
|
||||
# we read earlier.
|
||||
xremove = []
|
||||
for value in ['base_version', 'patch']:
|
||||
xpatch = xpackage.FirstChildElement(value)
|
||||
while xpatch:
|
||||
xremove.append(xpatch)
|
||||
xpatch = xpatch.NextSiblingElement(value)
|
||||
|
||||
for xelement in xremove:
|
||||
xpackage.RemoveChild(xelement)
|
||||
|
||||
xpackage.RemoveAttribute('last_patch_version')
|
||||
|
||||
# Now replace them with the current patch information.
|
||||
xpackage.SetAttribute('patch_version', str(self.patchVersion))
|
||||
|
||||
xarchive = TiXmlElement('base_version')
|
||||
self.baseFile.storeXml(xarchive)
|
||||
xpackage.InsertEndChild(xarchive)
|
||||
|
||||
for patchfile in self.patches:
|
||||
xpatch = patchfile.makeXml(self)
|
||||
xpackage.InsertEndChild(xpatch)
|
||||
|
||||
self.doc.SaveFile()
|
||||
|
||||
|
||||
def __init__(self, installDir):
|
||||
self.installDir = installDir
|
||||
self.packageVersions = {}
|
||||
|
||||
def run(self):
|
||||
if not self.readContentsFile():
|
||||
return False
|
||||
self.buildPatchChains()
|
||||
self.processPackages()
|
||||
|
||||
self.cleanup()
|
||||
return True
|
||||
|
||||
def cleanup(self):
|
||||
for pv in self.packageVersions.values():
|
||||
pv.cleanup()
|
||||
|
||||
def readContentsFile(self):
|
||||
""" Reads the contents.xml file at the beginning of
|
||||
processing. """
|
||||
|
||||
contentsFilename = Filename(self.installDir, 'contents.xml')
|
||||
doc = TiXmlDocument(contentsFilename.toOsSpecific())
|
||||
if not doc.LoadFile():
|
||||
# Couldn't read file.
|
||||
print "couldn't read %s" % (contentsFilename)
|
||||
return False
|
||||
|
||||
self.packages = []
|
||||
xcontents = doc.FirstChildElement('contents')
|
||||
if xcontents:
|
||||
xpackage = xcontents.FirstChildElement('package')
|
||||
while xpackage:
|
||||
solo = xpackage.Attribute('solo')
|
||||
solo = int(solo or '0')
|
||||
filename = xpackage.Attribute('filename')
|
||||
if filename and not solo:
|
||||
filename = Filename(filename)
|
||||
package = self.Package(filename, self)
|
||||
package.readDescFile()
|
||||
self.packages.append(package)
|
||||
|
||||
xpackage = xpackage.NextSiblingElement('package')
|
||||
|
||||
return True
|
||||
|
||||
def getPackageVersion(self, key):
|
||||
""" Returns a shared PackageVersion object for the indicated
|
||||
key. """
|
||||
|
||||
pv = self.packageVersions.get(key, None)
|
||||
if not pv:
|
||||
pv = self.PackageVersion(*key)
|
||||
self.packageVersions[key] = pv
|
||||
return pv
|
||||
|
||||
def buildPatchChains(self):
|
||||
""" Builds up the chains of PackageVersions and the patchfiles
|
||||
that connect them. """
|
||||
|
||||
self.patchFilenames = {}
|
||||
|
||||
for package in self.packages:
|
||||
currentPv = self.getPackageVersion(package.getCurrentKey())
|
||||
package.currentPv = currentPv
|
||||
currentPv.packageCurrent = package
|
||||
|
||||
basePv = self.getPackageVersion(package.getBaseKey())
|
||||
package.basePv = basePv
|
||||
basePv.packageBase = package
|
||||
|
||||
for patchfile in package.patches:
|
||||
self.recordPatchfile(patchfile)
|
||||
|
||||
def recordPatchfile(self, patchfile):
|
||||
""" Adds the indicated patchfile to the patch chains. """
|
||||
self.patchFilenames[patchfile.file.filename] = patchfile
|
||||
|
||||
fromPv = self.getPackageVersion(patchfile.getFromKey())
|
||||
patchfile.fromPv = fromPv
|
||||
fromPv.toPatches.append(patchfile)
|
||||
|
||||
toPv = self.getPackageVersion(patchfile.getToKey())
|
||||
patchfile.toPv = toPv
|
||||
toPv.fromPatches.append(patchfile)
|
||||
|
||||
def processPackages(self):
|
||||
""" Walks through the list of packages, and builds missing
|
||||
patches for each one. """
|
||||
|
||||
for package in self.packages:
|
||||
self.processPackage(package)
|
||||
|
||||
def processPackage(self, package):
|
||||
""" Builds missing patches for the indicated package. """
|
||||
|
||||
# Starting with the package base, how far forward can we go?
|
||||
currentPv = package.currentPv
|
||||
basePv = package.basePv
|
||||
|
||||
pv = basePv
|
||||
nextPv = pv.getNext(package)
|
||||
while nextPv:
|
||||
pv = nextPv
|
||||
nextPv = pv.getNext(package)
|
||||
|
||||
if pv.packageCurrent != package:
|
||||
# It doesn't reach all the way to the latest version, so
|
||||
# build a new patch.
|
||||
filename = Filename(package.currentFile.filename + '.%s.patch' % (package.patchVersion))
|
||||
assert filename not in self.patchFilenames
|
||||
if not self.buildPatch(pv, currentPv, package, filename):
|
||||
raise StandardError, "Couldn't build patch."
|
||||
|
||||
package.writeDescFile()
|
||||
|
||||
def buildPatch(self, v1, v2, package, patchFilename):
|
||||
""" Builds a patch from PackageVersion v1 to PackageVersion
|
||||
v2, and stores it in patchFilename. Returns true on success,
|
||||
false on failure."""
|
||||
|
||||
f1 = v1.getFile()
|
||||
f2 = v2.getFile()
|
||||
|
||||
pathname = Filename(package.packageDir, patchFilename)
|
||||
if not self.buildPatchFile(f1, f2, pathname):
|
||||
return False
|
||||
|
||||
patchfile = self.Patchfile(package)
|
||||
patchfile.fromFile(package.packageDir, patchFilename,
|
||||
v1.hash, v2.hash)
|
||||
package.patches.append(patchfile)
|
||||
package.anyChanges = True
|
||||
|
||||
self.recordPatchfile(patchfile)
|
||||
|
||||
return True
|
||||
|
||||
def buildPatchFile(self, origFilename, newFilename, patchFilename):
|
||||
""" Creates a patch file from origFilename to newFilename,
|
||||
storing the result in patchFilename. Returns true on success,
|
||||
false on failure. """
|
||||
|
||||
if not origFilename.exists():
|
||||
# No original version to patch from.
|
||||
return False
|
||||
|
||||
print "Building patch from %s to %s" % (origFilename, newFilename)
|
||||
patchFilename.unlink()
|
||||
p = Patchfile()
|
||||
if p.build(origFilename, newFilename, patchFilename):
|
||||
return True
|
||||
|
||||
# Unable to build a patch for some reason.
|
||||
patchFilename.unlink()
|
||||
return False
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pm = PatchMaker(Filename('install'))
|
||||
result = pm.run()
|
||||
print "run returned %s" % (result)
|
||||
|
Loading…
x
Reference in New Issue
Block a user