better xml, better requires

This commit is contained in:
David Rose 2009-08-11 17:32:41 +00:00
parent 383a6e512c
commit 57e6e80491
2 changed files with 207 additions and 202 deletions

View File

@ -83,18 +83,15 @@ class Packager:
self.packageDesc = packageDir + self.packageDesc
self.packageImportDesc = packageDir + self.packageImportDesc
Filename(self.packageFilename).makeDir()
try:
os.unlink(self.packageFilename)
except OSError:
pass
self.packageFullpath = Filename(self.packager.installDir, self.packageFilename)
self.packageFullpath.makeDir()
self.packageFullpath.unlink()
if self.dryRun:
self.multifile = None
else:
self.multifile = Multifile()
self.multifile.openReadWrite(self.packageFilename)
self.multifile.openReadWrite(self.packageFullpath)
self.extracts = []
self.components = []
@ -183,12 +180,14 @@ class Packager:
""" Compresses the .mf file into an .mf.pz file. """
compressedName = self.packageFilename + '.pz'
if not compressFile(self.packageFilename, compressedName, 6):
message = 'Unable to write %s' % (compressedName)
compressedPath = Filename(self.packager.installDir, compressedName)
if not compressFile(self.packageFullpath, compressedPath, 6):
message = 'Unable to write %s' % (compressedPath)
raise PackagerError, message
def writeDescFile(self):
doc = TiXmlDocument(self.packageDesc)
packageDescFullpath = Filename(self.packager.installDir, self.packageDesc)
doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
decl = TiXmlDeclaration("1.0", "utf-8", "")
doc.InsertEndChild(decl)
@ -200,9 +199,11 @@ class Packager:
xpackage.SetAttribute('version', self.version)
xuncompressedArchive = self.getFileSpec(
'uncompressed_archive', self.packageFilename, self.packageBasename)
'uncompressed_archive', self.packageFullpath,
self.packageBasename)
xcompressedArchive = self.getFileSpec(
'compressed_archive', self.packageFilename + '.pz', self.packageBasename + '.pz')
'compressed_archive', self.packageFullpath + '.pz',
self.packageBasename + '.pz')
xpackage.InsertEndChild(xuncompressedArchive)
xpackage.InsertEndChild(xcompressedArchive)
@ -213,7 +214,8 @@ class Packager:
doc.SaveFile()
def writeImportDescFile(self):
doc = TiXmlDocument(self.packageImportDesc)
packageImportDescFullpath = Filename(self.packager.installDir, self.packageImportDesc)
doc = TiXmlDocument(packageImportDescFullpath.toOsSpecific())
decl = TiXmlDeclaration("1.0", "utf-8", "")
doc.InsertEndChild(decl)
@ -230,19 +232,52 @@ class Packager:
doc.InsertEndChild(xpackage)
doc.SaveFile()
def readImportDescFile(self, filename):
""" Reads the import desc file. Returns True on success,
False on failure. """
def getFileSpec(self, element, filename, newName):
doc = TiXmlDocument(filename.toOsSpecific())
if not doc.LoadFile():
return False
xpackage = doc.FirstChildElement('package')
if not xpackage:
return False
self.packageName = xpackage.Attribute('name')
self.platform = xpackage.Attribute('platform')
self.version = xpackage.Attribute('version')
self.targetFilenames = {}
xcomponent = xpackage.FirstChildElement('component')
while xcomponent:
xcomponent = xcomponent.ToElement()
name = xcomponent.Attribute('filename')
if name:
self.targetFilenames[name] = True
xcomponent = xcomponent.NextSibling()
self.moduleNames = {}
xmodule = xpackage.FirstChildElement('module')
while xmodule:
xmodule = xmodule.ToElement()
moduleName = xmodule.Attribute('name')
if moduleName:
self.moduleNames[moduleName] = True
xmodule = xmodule.NextSibling()
return True
def getFileSpec(self, element, pathname, newName):
""" Returns an xcomponent or similar element with the file
information for the indicated file. """
xspec = TiXmlElement(element)
filename = Filename(filename)
size = filename.getFileSize()
timestamp = filename.getTimestamp()
size = pathname.getFileSize()
timestamp = pathname.getTimestamp()
hv = HashVal()
hv.hashFile(filename)
hv.hashFile(pathname)
hash = hv.asHex()
xspec.SetAttribute('filename', newName)
@ -426,6 +461,10 @@ class Packager:
self.installDir = None
self.persistDir = None
# A search list of directories and/or URL's to search for
# installed packages.
self.installSearch = []
# The platform string.
self.platform = PandaSystem.getPlatform()
@ -500,28 +539,26 @@ class Packager:
self.currentPackage = None
# The persist dir is the directory in which the results from
# past publishes are stored so we can generate patches against
# them. There must be a nonempty directory name here.
assert(self.persistDir)
# We must have an actual install directory.
assert(self.installDir)
# If the persist dir names an empty or nonexistent directory,
# we will be generating a brand new publish with no previous
# patches.
self.persistDir.makeDir()
## # If the persist dir names an empty or nonexistent directory,
## # we will be generating a brand new publish with no previous
## # patches.
## self.persistDir.makeDir()
# Within the persist dir, we make a temporary holding dir for
# generating multifiles.
self.mfTempDir = Filename(self.persistDir, Filename('mftemp/'))
#self.mfTempDir.makeDir()
## # Within the persist dir, we make a temporary holding dir for
## # generating multifiles.
## self.mfTempDir = Filename(self.persistDir, Filename('mftemp/'))
## self.mfTempDir.makeDir()
# We also need a temporary holding dir for squeezing py files.
self.pyzTempDir = Filename(self.persistDir, Filename('pyz/'))
#self.pyzTempDir.makeDir()
## # We also need a temporary holding dir for squeezing py files.
## self.pyzTempDir = Filename(self.persistDir, Filename('pyz/'))
## self.pyzTempDir.makeDir()
# Change to the persist directory so the temp files will be
# created there
os.chdir(self.persistDir.toOsSpecific())
## # Change to the persist directory so the temp files will be
## # created there
## os.chdir(self.persistDir.toOsSpecific())
def readPackageDef(self, packageDef):
""" Reads the lines in the .pdef file named by packageDef and
@ -755,21 +792,140 @@ class Packager:
self.packages[package.packageName] = package
self.currentPackage = None
def findPackage(self, packageName, searchUrl = None):
def findPackage(self, packageName, version = None, searchUrl = None):
""" Searches for the named package from a previous publish
operation, either at the indicated URL or along the default
operation, either at the indicated URL or along the install
search path.
Returns the Package object, or None if the package cannot be
located. """
# Is it a package we already have resident?
package = self.packages.get(packageName, None)
package = self.packages.get((packageName, version), None)
if package:
return package
# Look on the searchlist.
for path in self.installSearch:
packageDir = Filename(path, packageName)
if packageDir.isDirectory():
# Hey, this package appears to exist!
package = self.scanPackageDir(packageDir, packageName, version)
if package:
self.packages[(packageName, version)] = package
return package
return None
def scanPackageDir(self, packageDir, packageName, packageVersion):
""" Scans a directory on disk, looking for _import.xml files
that match the indicated packageName and option version. If a
suitable xml file is found, reads it and returns the assocated
Package definition. """
bestVersion = None
bestPackage = None
# First, look for a platform-specific file.
platformDir = Filename(packageDir, self.platform)
prefix = '%s_%s_' % (packageName, self.platform)
if packageVersion:
# Do we have a specific version request?
basename = prefix + '%s_import.xml' % (packageVersion)
filename = Filename(platformDir, basename)
if filename.exists():
# Got it!
package = self.readPackageImportDescFile(filename)
if package:
return package
else:
# Look for a generic version request. Get the highest
# version available.
files = vfs.scanDirectory(platformDir) or []
for file in files:
filename = file.getFilename()
basename = filename.getBasename()
if not basename.startswith(prefix) or \
not basename.endswith('_import.xml'):
continue
package = self.readPackageImportDescFile(filename)
if not package:
continue
parts = basename.split('_')
if len(parts) == 3:
# No version number.
if bestVersion is None:
bestPackage = package
elif len(parts) == 4:
# Got a version number.
version = self.parseVersionForCompare(parts[2])
if bestVersion > version:
bestVersion = version
bestPackage = package
# Didn't find a suitable platform-specific file, so look for a
# platform-nonspecific one.
prefix = '%s_' % (packageName)
if packageVersion:
# Do we have a specific version request?
basename = prefix + '%s_import.xml' % (packageVersion)
filename = Filename(packageDir, basename)
if filename.exists():
# Got it!
package = self.readPackageImportDescFile(filename)
if package:
return package
else:
# Look for a generic version request. Get the highest
# version available.
files = vfs.scanDirectory(packageDir) or []
for file in files:
filename = file.getFilename()
basename = filename.getBasename()
if not basename.startswith(prefix) or \
not basename.endswith('_import.xml'):
continue
package = self.readPackageImportDescFile(filename)
if not package:
continue
parts = basename.split('_')
if len(parts) == 2:
# No version number.
if bestVersion is None:
bestPackage = package
elif len(parts) == 3:
# Got a version number.
version = self.parseVersionForCompare(parts[1])
if bestVersion > version:
bestVersion = version
bestPackage = package
return bestPackage
def readPackageImportDescFile(self, filename):
""" Reads the named xml file as a Package, and returns it if
valid, or None otherwise. """
package = self.Package('', self)
if package.readImportDescFile(filename):
return package
return None
def parseVersionForCompare(self, version):
""" Given a formatted version string, breaks it up into a
tuple, grouped so that putting the tuples into sorted order
will put the order numbers in increasing order. """
# TODO. For now, we just use a dumb string compare.
return (version,)
def require(self, package):
""" Indicates a dependency on the indicated package.

View File

@ -15,7 +15,7 @@ This script is actually a wrapper around Panda's Packager.py.
Usage:
ppackage.py [opts] package.pdef command
ppackage.py [opts] package.pdef
Required:
@ -24,29 +24,15 @@ Required:
to be built, in excruciating detail. Use "ppackage.py -H" to
describe the syntax of this file.
command
The action to perform. The following commands are supported:
"build": Builds the package file(s) named in the package.pdef, and
places the result in the install_dir. Does not attempt to
generate any patches.
"publish": Builds a package file, as above, and then generates
patches against previous builds, and updates install_dir and
persist_dir appropriately. Instead of the current directory,
the built package file(s) are placed in the install_dir where
they may be downloaded.
Options:
-i install_dir
The full path to a local directory to copy the
ready-to-be-published files into. This directory structure is
populated by the "publish" command, and it may contain multiple
different packages from multiple different invocations of the
"publish" command. It is the user's responsibility to copy this
directory structure to a web host where it may be downloaded by
the client.
ready-to-be-published files into. This directory structure may
contain multiple different packages from multiple different
invocations of this script. It is the user's responsibility to
copy this directory structure to a web host where it may be
downloaded by the client.
-d persist_dir
The full path to a local directory that retains persistant state
@ -69,133 +55,6 @@ Options:
Display this help
"""
#
# package.pdef syntax:
#
# multifile <mfname> <phase>
#
# Begins a new multifile. All files named after this line and
# until the next multifile line will be a part of this multifile.
#
# <mfname>
# The filename of the multifile, no directory.
#
# <phase>
# The numeric phase in which this multifile should be downloaded.
#
#
# file <extractFlag> <filename> <dirname> <platforms>
#
# Adds a single file to the current multifile.
#
# <extractFlag>
# One of:
# 0 - Leave this file within the multifile; it can be read by
# Panda from there.
# 1 - Extract this file from the multifile, but do not bother
# to hash check it on restart.
# 2 - Extract this file, and hash check it every time the
# client starts, to ensure it is not changed.
#
# <filename>
# The name of the file to add. This is the full path to the
# file on the publishing machine at the time this script is run.
#
# <dirname>
# The directory in which to install the file, on the client.
# This should be a relative pathname from the game directory.
# The file is written to the multifile with its directory part
# taken from this, and its basename taken from the source
# filename, above. Also, if the file is extracted, it will be
# written into this directory on the client machine.
#
# The directory name "toplevel" is treated as a special case;
# this maps to the game directory itself, and is used for files
# in the initial download.
#
# <platforms>
# A comma-delimited list of platforms for which this file should
# be included with the distribution. Presently, the only
# options are WIN32 and/or OSX.
#
#
# dir <extractFlag> <localDirname> <dirname>
#
# Adds an entire directory tree to the current multifile. The
# named directory is searched recursively and all files found are
# added to the current multifile, as if they were listed one a
# time with a file command.
#
# <extractFlag>
# (Same as for the file command, above.)
#
# <localDirname>
# The name of the local directory to scan on the publishing
# machine.
#
# <dirname>
# The name of the corresponding local directory on the client
# machine; similar to <dirname> in the file command, above.
#
#
# module modulename
#
# Adds the named Python module to the exe or dll archive. All files
# named by module, until the next freeze_exe or freeze_dll command (below),
# will be compiled and placed into the same archive.
#
# exclude_module modulename
#
# Excludes the named Python module from the archive. This module
# will not be included in the archive, but an import command may
# still find it if a .py file exists on disk.
#
# forbid_module modulename
#
# Excludes the named Python module from the archive. This module
# will specifically never be imported by the resulting executable,
# even if a .py file exists on disk. (However, a module command
# appearing in a later phase--e.g. Phase3.pyd--can override an
# earlier forbid_module command.)
#
# dc_module file.dc
#
# Adds the modules imported by the indicated dc file to the
# archive. Normally this is not necessary if the file.dc is
# explicitly included in the package.pdef; but this command may be
# useful if the file.dc is imported in a later phase.
#
# freeze_exe <extractFlag> <exeFilename> <mainModule> <dirname>
#
# <extractFlag>
# (Same as for the file command, above.)
#
# <exeFilename>
# The name of the executable file to generate. Do not include
# an extension name; on Windows, the default extension is .exe;
# on OSX there is no extension.
#
# <mainModule>
# The name of the python module that will be invoked first (like
# main()) when the resulting executable is started.
#
# <dirname>
# (Same as for the file command, above.)
#
# freeze_dll <extractFlag> <dllFilename> <dirname>
#
# <extractFlag>
# (Same as for the file command, above.)
#
# <dllFilename>
# The name of the shared library file to generate. Do not include
# an extension name; on Windows, the default extension is .pyd;
# on OSX the extension is .so.
#
# <dirname>
# (Same as for the file command, above.)
import sys
import getopt
import os
@ -235,24 +94,14 @@ for opt, arg in opts:
if not args:
usage(0)
if len(args) != 2:
if len(args) != 1:
usage(1)
packageDef = Filename.fromOsSpecific(args[0])
command = args[1]
if not packager.installDir:
packager.installDir = Filename('install')
packager.installSearch = [packager.installDir]
if command == 'build':
#packager.doBuild()
if not packager.persistDir:
packager.persistDir = Filename('.')
packager.setup()
packager.readPackageDef(packageDef)
elif command == 'publish':
packager.setup()
packager.doPublish()
else:
print 'Undefined command: ' + command
sys.exit(1)
packager.setup()
packager.readPackageDef(packageDef)