mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 02:15:43 -04:00
PatchMaker
This commit is contained in:
parent
1fc186bda4
commit
f1588e4e9f
@ -519,27 +519,6 @@ class Packager:
|
|||||||
|
|
||||||
self.cleanup()
|
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):
|
def installSolo(self):
|
||||||
""" Installs the package as a "solo", which means we
|
""" Installs the package as a "solo", which means we
|
||||||
simply copy the one file into the install directory. This
|
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