more package nonsense

This commit is contained in:
David Rose 2009-08-13 05:29:44 +00:00
parent da9f3f8a33
commit d3d91a3de2
5 changed files with 247 additions and 67 deletions

View File

@ -881,8 +881,10 @@ class Freezer:
false). The basename is the name of the file to write, false). The basename is the name of the file to write,
without the extension. without the extension.
The return value is the newly-generated filename, including The return value is the tuple (filename, extras) where
the extension. """ filename is the newly-generated filename, including the
filename extension, and extras is a list of (moduleName,
filename), for extension modules. """
if compileToExe: if compileToExe:
# We must have a __main__ module to make an exe file. # We must have a __main__ module to make an exe file.
@ -895,6 +897,7 @@ class Freezer:
# Now generate the actual export table. # Now generate the actual export table.
moduleDefs = [] moduleDefs = []
moduleList = [] moduleList = []
extras = []
for moduleName, mdef in self.getModuleDefs(): for moduleName, mdef in self.getModuleDefs():
token = mdef.token token = mdef.token
@ -926,6 +929,19 @@ class Freezer:
mangledName = self.mangleName(moduleName) mangledName = self.mangleName(moduleName)
moduleDefs.append(self.makeModuleDef(mangledName, code)) moduleDefs.append(self.makeModuleDef(mangledName, code))
moduleList.append(self.makeModuleListEntry(mangledName, code, moduleName, module)) moduleList.append(self.makeModuleListEntry(mangledName, code, moduleName, module))
else:
# This is a module with no associated Python
# code. It must be a compiled file. Get the
# filename.
filename = getattr(module, '__file__', None)
if filename:
extras.append((moduleName, filename))
else:
# It doesn't even have a filename; it must
# be a built-in module. No worries about
# this one, then.
pass
filename = basename + self.sourceExtension filename = basename + self.sourceExtension
@ -978,7 +994,7 @@ class Freezer:
if (os.path.exists(basename + self.objectExtension)): if (os.path.exists(basename + self.objectExtension)):
os.unlink(basename + self.objectExtension) os.unlink(basename + self.objectExtension)
return target return (target, extras)
def compileExe(self, filename, basename): def compileExe(self, filename, basename):
compile = self.compileObj % { compile = self.compileObj % {

View File

@ -67,6 +67,10 @@ class Packager:
def close(self): def close(self):
""" Writes out the contents of the current package. """ """ Writes out the contents of the current package. """
if not self.p3dApplication and not self.version:
# We must have a version string for packages.
self.version = '0.0'
self.packageBasename = self.packageName self.packageBasename = self.packageName
packageDir = self.packageName packageDir = self.packageName
if self.platform: if self.platform:
@ -262,6 +266,15 @@ class Packager:
if self.displayName: if self.displayName:
xpackage.SetAttribute('display_name', self.displayName) xpackage.SetAttribute('display_name', self.displayName)
for package in self.requires:
xrequires = TiXmlElement('requires')
xrequires.SetAttribute('name', package.packageName)
if package.platform:
xrequires.SetAttribute('platform', package.platform)
if package.version:
xrequires.SetAttribute('version', package.version)
xpackage.InsertEndChild(xrequires)
xuncompressedArchive = self.getFileSpec( xuncompressedArchive = self.getFileSpec(
'uncompressed_archive', self.packageFullpath, 'uncompressed_archive', self.packageFullpath,
self.packageBasename) self.packageBasename)
@ -290,6 +303,15 @@ class Packager:
if self.version: if self.version:
xpackage.SetAttribute('version', self.version) xpackage.SetAttribute('version', self.version)
for package in self.requires:
xrequires = TiXmlElement('requires')
xrequires.SetAttribute('name', package.packageName)
if package.platform:
xrequires.SetAttribute('platform', package.platform)
if package.version:
xrequires.SetAttribute('version', package.version)
xpackage.InsertEndChild(xrequires)
for xcomponent in self.components: for xcomponent in self.components:
xpackage.InsertEndChild(xcomponent) xpackage.InsertEndChild(xcomponent)
@ -311,23 +333,33 @@ class Packager:
self.platform = xpackage.Attribute('platform') self.platform = xpackage.Attribute('platform')
self.version = xpackage.Attribute('version') self.version = xpackage.Attribute('version')
self.requires = []
xrequires = xpackage.FirstChildElement('requires')
while xrequires:
packageName = xrequires.Attribute('name')
platform = xrequires.Attribute('platform')
version = xrequires.Attribute('version')
if packageName:
package = self.packager.findPackage(packageName, platform = platform, version = version, requires = self.requires)
if package:
self.requires.append(package)
xrequires = xrequires.NextSiblingElement()
self.targetFilenames = {} self.targetFilenames = {}
xcomponent = xpackage.FirstChildElement('component') xcomponent = xpackage.FirstChildElement('component')
while xcomponent: while xcomponent:
xcomponent = xcomponent.ToElement()
name = xcomponent.Attribute('filename') name = xcomponent.Attribute('filename')
if name: if name:
self.targetFilenames[name] = True self.targetFilenames[name] = True
xcomponent = xcomponent.NextSibling() xcomponent = xcomponent.NextSiblingElement()
self.moduleNames = {} self.moduleNames = {}
xmodule = xpackage.FirstChildElement('module') xmodule = xpackage.FirstChildElement('module')
while xmodule: while xmodule:
xmodule = xmodule.ToElement()
moduleName = xmodule.Attribute('name') moduleName = xmodule.Attribute('name')
if moduleName: if moduleName:
self.moduleNames[moduleName] = True self.moduleNames[moduleName] = True
xmodule = xmodule.NextSibling() xmodule = xmodule.NextSiblingElement()
return True return True
@ -522,17 +554,17 @@ class Packager:
self.components.append(xcomponent) self.components.append(xcomponent)
def requirePackage(self, package): def requirePackage(self, package):
""" Indicates a dependency on the given package. """ """ Indicates a dependency on the given package. This
also implicitly requires all of the package's requirements
as well. """
if package in self.requires: for p2 in package.requires + [package]:
# Already on the list. if p2 not in self.requires:
return self.requires.append(p2)
for filename in p2.targetFilenames.keys():
self.requires.append(package) self.skipFilenames[filename] = True
for filename in package.targetFilenames.keys(): for moduleName in p2.moduleNames.keys():
self.skipFilenames[filename] = True self.skipModules[moduleName] = True
for moduleName in package.moduleNames.keys():
self.skipModules[moduleName] = True
def __init__(self): def __init__(self):
@ -748,12 +780,13 @@ class Packager:
return words return words
def __getNextLine(self, file): def __getNextLine(self, file, lineNum):
""" Extracts the next line from the input file, and splits it """ Extracts the next line from the input file, and splits it
into words. Returns the list of words, or None at end of into words. Returns a tuple (lineNum, list), or (lineNum,
file. """ None) at end of file. """
line = file.readline() line = file.readline()
lineNum += 1
while line: while line:
line = line.strip() line = line.strip()
if not line: if not line:
@ -765,12 +798,13 @@ class Packager:
pass pass
else: else:
return self.__splitLine(line) return (lineNum, self.__splitLine(line))
line = file.readline() line = file.readline()
lineNum += 1
# End of file. # End of file.
return None return (lineNum, None)
def readPackageDef(self, packageDef): def readPackageDef(self, packageDef):
""" Reads the lines in the .pdef file named by packageDef and """ Reads the lines in the .pdef file named by packageDef and
@ -781,10 +815,11 @@ class Packager:
self.notify.info('Reading %s' % (packageDef)) self.notify.info('Reading %s' % (packageDef))
file = open(packageDef.toOsSpecific()) file = open(packageDef.toOsSpecific())
lineNum = 0
# Now start parsing the packageDef lines # Now start parsing the packageDef lines
try: try:
words = self.__getNextLine(file) lineNum, words = self.__getNextLine(file, lineNum)
while words: while words:
command = words[0] command = words[0]
try: try:
@ -803,13 +838,13 @@ class Packager:
message = '%s command encounted outside of package specification' %(command) message = '%s command encounted outside of package specification' %(command)
raise OutsideOfPackageError, message raise OutsideOfPackageError, message
words = self.__getNextLine(file) lineNum, words = self.__getNextLine(file, lineNum)
except PackagerError: except PackagerError:
# Append the line number and file name to the exception # Append the line number and file name to the exception
# error message. # error message.
inst = sys.exc_info()[1] inst = sys.exc_info()[1]
inst.args = (inst.args[0] + ' on line %s of %s' % (lineNum[0], packageDef),) inst.args = (inst.args[0] + ' on line %s of %s' % (lineNum, packageDef),)
raise raise
packageList = self.packageList packageList = self.packageList
@ -1053,15 +1088,20 @@ class Packager:
raise PackageError, message raise PackageError, message
package = self.Package(packageName, self) package = self.Package(packageName, self)
self.currentPackage = package
package.version = version package.version = version
package.p3dApplication = p3dApplication package.p3dApplication = p3dApplication
if package.p3dApplication: if package.p3dApplication:
# Default compression level for an app. # Default compression level for an app.
package.compressionLevel = 6 package.compressionLevel = 6
# Every p3dapp requires panda3d.
self.require('panda3d')
package.dryRun = self.dryRun package.dryRun = self.dryRun
self.currentPackage = package
def endPackage(self, packageName, p3dApplication = False): def endPackage(self, packageName, p3dApplication = False):
""" Closes a package specification. This actually generates """ Closes a package specification. This actually generates
the package file. The packageName must match the previous the package file. The packageName must match the previous
@ -1082,40 +1122,55 @@ class Packager:
package.close() package.close()
self.packageList.append(package) self.packageList.append(package)
self.packages[package.packageName] = package self.packages[(package.packageName, package.platform, package.version)] = package
self.currentPackage = None self.currentPackage = None
def findPackage(self, packageName, version = None, searchUrl = None): def findPackage(self, packageName, platform = None, version = None,
requires = None):
""" Searches for the named package from a previous publish """ Searches for the named package from a previous publish
operation, either at the indicated URL or along the install operation along the install search path.
search path.
If requires is not None, it is a list of Package objects that
are already required. The new Package object must be
compatible with the existing Packages, or an error is
returned. This is also useful for determining the appropriate
package version to choose when a version is not specified.
Returns the Package object, or None if the package cannot be Returns the Package object, or None if the package cannot be
located. """ located. """
if not platform:
platform = self.platform
# Is it a package we already have resident? # Is it a package we already have resident?
package = self.packages.get((packageName, version), None) package = self.packages.get((packageName, platform, version), None)
if package: if package:
return package return package
# Look on the searchlist. # Look on the searchlist.
for path in self.installSearch: for path in self.installSearch:
package = self.scanPackageDir(path, packageName, version, self.platform) package = self.__scanPackageDir(path, packageName, platform, version, requires = requires)
if not package:
package = self.__scanPackageDir(path, packageName, None, version, requires = requires)
if package: if package:
self.packages[(packageName, version)] = package package = self.packages.setdefault((package.packageName, package.platform, package.version), package)
return package self.packages[(packageName, platform, version)] = package
package = self.scanPackageDir(path, packageName, version, None)
if package:
self.packages[(packageName, version)] = package
return package return package
return None return None
def scanPackageDir(self, rootDir, packageName, version, platform): def __scanPackageDir(self, rootDir, packageName, platform, version,
""" Scans a directory on disk, looking for _import.xml files requires = None):
that match the indicated packageName and option version. If a """ Scans a directory on disk, looking for *_import.xml files
that match the indicated packageName and optional version. If a
suitable xml file is found, reads it and returns the assocated suitable xml file is found, reads it and returns the assocated
Package definition. """ Package definition.
If a version is not specified, and multiple versions are
available, the highest-numbered version that matches will be
selected.
"""
packageDir = Filename(rootDir, packageName) packageDir = Filename(rootDir, packageName)
basename = packageName basename = packageName
@ -1125,26 +1180,101 @@ class Packager:
basename += '_%s' % (platform) basename += '_%s' % (platform)
if version: if version:
# A specific version package.
packageDir = Filename(packageDir, version) packageDir = Filename(packageDir, version)
basename += '_%s' % (version) basename += '_%s' % (version)
else:
# Scan all versions.
packageDir = Filename(packageDir, '*')
basename += '_%s' % ('*')
basename += '_import.xml' basename += '_import.xml'
filename = Filename(packageDir, basename) filename = Filename(packageDir, basename)
if filename.exists(): filelist = glob.glob(filename.toOsSpecific())
# It exists in the nested directory. if not filelist:
package = self.readPackageImportDescFile(filename) # It doesn't exist in the nested directory; try the root
if package: # directory.
return package filename = Filename(rootDir, basename)
filename = Filename(rootDir, basename) filelist = glob.glob(filename.toOsSpecific())
if filename.exists():
# It exists in the root directory. self.__sortPackageImportFilelist(filelist)
package = self.readPackageImportDescFile(filename) for file in filelist:
if package: package = self.__readPackageImportDescFile(Filename.fromOsSpecific(file))
if package and self.__packageIsValid(package, requires):
return package return package
return None return None
def readPackageImportDescFile(self, filename): def __sortPackageImportFilelist(self, filelist):
""" Given a list of *_import.xml filenames, sorts them in
reverse order by version, so that the highest-numbered
versions appear first in the list. """
tuples = []
for file in filelist:
version = file.split('_')[-2]
version = self.__makeVersionTuple(version)
tuples.append((version, file))
tuples.sort(reverse = True)
return map(lambda t: t[1], tuples)
def __makeVersionTuple(self, version):
""" Converts a version string into a tuple for sorting, by
separating out numbers into separate numeric fields, so that
version numbers sort numerically where appropriate. """
words = []
p = 0
while p < len(version):
# Scan to the first digit.
w = ''
while p < len(version) and version[p] not in string.digits:
w += version[p]
p += 1
words.append(w)
# Scan to the end of the string of digits.
w = ''
while p < len(version) and version[p] in string.digits:
w += version[p]
p += 1
words.append(int(w))
return tuple(words)
def __packageIsValid(self, package, requires):
""" Returns true if the package is valid, meaning it can be
imported without conflicts with existing packages already
required (such as different versions of panda3d). """
if not requires:
return True
# Really, we only check the panda3d package for now. The
# other packages will list this as a dependency, and this is
# all that matters.
panda1 = self.__findPackageInList('panda3d', [package] + package.requires)
panda2 = self.__findPackageInList('panda3d', requires)
if not panda1 or not panda2:
return True
if panda1.version == panda2.version:
return True
return False
def __findPackageInList(self, packageName, list):
""" Returns the first package with the indicated name in the list. """
for package in list:
if package.packageName == packageName:
return package
return None
def __readPackageImportDescFile(self, filename):
""" Reads the named xml file as a Package, and returns it if """ Reads the named xml file as a Package, and returns it if
valid, or None otherwise. """ valid, or None otherwise. """
@ -1162,15 +1292,18 @@ class Packager:
named package also. Files already included in the named named package also. Files already included in the named
package will be omitted from this one when building it. """ package will be omitted from this one when building it. """
if not self.currentPackage:
raise OutsideOfPackageError
# A special case for the Panda3D package. We enforce that the # A special case for the Panda3D package. We enforce that the
# version number matches what we've been compiled with. # version number matches what we've been compiled with.
if packageName == 'panda3d': if packageName == 'panda3d':
if version is None: if version is None:
version = PandaSystem.getPackageVersionString() version = PandaSystem.getPackageVersionString()
package = self.findPackage(packageName, version = version) package = self.findPackage(packageName, version = version, requires = self.currentPackage.requires)
if not package: if not package:
message = "Unknown package %s" % (packageName) message = 'Unknown package %s, version "%s"' % (packageName, version)
raise PackagerError, message raise PackagerError, message
self.requirePackage(package) self.requirePackage(package)
@ -1183,6 +1316,9 @@ class Packager:
named package also. Files already included in the named named package also. Files already included in the named
package will be omitted from this one. """ package will be omitted from this one. """
if not self.currentPackage:
raise OutsideOfPackageError
# A special case for the Panda3D package. We enforce that the # A special case for the Panda3D package. We enforce that the
# version number matches what we've been compiled with. # version number matches what we've been compiled with.
if package.packageName == 'panda3d': if package.packageName == 'panda3d':
@ -1255,9 +1391,17 @@ class Packager:
dirname, basename = filename.rsplit('/', 1) dirname, basename = filename.rsplit('/', 1)
dirname += '/' dirname += '/'
basename = freezer.generateCode(basename, compileToExe = compileToExe) basename, extras = freezer.generateCode(basename, compileToExe = compileToExe)
package.files.append(self.PackFile(Filename(basename), newName = dirname + basename, deleteTemp = True, extract = True)) package.files.append(self.PackFile(Filename(basename), newName = dirname + basename, deleteTemp = True, extract = True))
for moduleName, filename in extras:
filename = Filename.fromOsSpecific(filename)
newName = filename.getBasename()
if '.' in moduleName:
newName = '/'.join(moduleName.split('.')[:-1])
newName += '/' + filename.getBasename()
package.files.append(self.PackFile(filename, newName = newName, extract = True))
if not package.platform: if not package.platform:
package.platform = PandaSystem.getPlatform() package.platform = PandaSystem.getPlatform()

View File

@ -38,7 +38,7 @@ class FileSpec:
s = os.stat(pathname) s = os.stat(pathname)
self.size = s.st_size self.size = s.st_size
self.timestamp = s.st_mtime self.timestamp = int(s.st_mtime)
m = md5.new() m = md5.new()
m.update(open(pathname, 'rb').read()) m.update(open(pathname, 'rb').read())

View File

@ -16,7 +16,7 @@ Usage:
Options: Options:
-r application_root -d application_root
Specify the root directory of the application source; this is a Specify the root directory of the application source; this is a
directory tree that contains all of your .py files and models. directory tree that contains all of your .py files and models.
If this is omitted, the default is the current directory. If this is omitted, the default is the current directory.
@ -30,6 +30,11 @@ Options:
(this is preferable to having the module start itself immediately (this is preferable to having the module start itself immediately
upon importing). upon importing).
-r package
Names an additional package that this application requires at
startup time. The default package is 'panda3d'; you may repeat
this option to indicate dependencies on additional packages.
-s search_dir -s search_dir
Additional directories to search for previously-built packages. Additional directories to search for previously-built packages.
This option may be repeated as necessary. This option may be repeated as necessary.
@ -56,18 +61,22 @@ class ArgumentError(StandardError):
pass pass
def makePackedApp(args): def makePackedApp(args):
opts, args = getopt.getopt(args, 'r:m:s:xh') opts, args = getopt.getopt(args, 'd:m:r:s:xh')
packager = Packager.Packager() packager = Packager.Packager()
root = '.' root = '.'
main = None main = None
requires = []
versionIndependent = False versionIndependent = False
for option, value in opts: for option, value in opts:
if option == '-r': if option == '-d':
root = Filename.fromOsSpecific(value) root = Filename.fromOsSpecific(value)
elif option == '-m': elif option == '-m':
main = value main = value
elif option == '-r':
requires.append(value)
elif option == '-s': elif option == '-s':
packager.installSearch.append(Filename.fromOsSpecific(value)) packager.installSearch.append(Filename.fromOsSpecific(value))
elif option == '-x': elif option == '-x':
@ -110,8 +119,9 @@ def makePackedApp(args):
packager.setup() packager.setup()
packager.beginPackage(appBase, p3dApplication = True) packager.beginPackage(appBase, p3dApplication = True)
for requireName in requires:
packager.require('panda3d') packager.require(requireName)
packager.dir(root) packager.dir(root)
packager.mainModule(mainModule) packager.mainModule(mainModule)

View File

@ -111,10 +111,20 @@ if not packager.installDir:
packager.installSearch = [packager.installDir] + packager.installSearch packager.installSearch = [packager.installDir] + packager.installSearch
packager.setup() packager.setup()
packager.readPackageDef(packageDef) packages = packager.readPackageDef(packageDef)
# Update the contents.xml at the root of the install directory. # Look to see if we built any true packages, or if all of them were
cm = make_contents.ContentsMaker() # p3d files.
cm.installDir = packager.installDir.toOsSpecific() anyPackages = False
cm.build() for package in packages:
if not package.p3dApplication:
anyPackages = True
break
if anyPackages:
# If we built any true packages, then update the contents.xml at
# the root of the install directory.
cm = make_contents.ContentsMaker()
cm.installDir = packager.installDir.toOsSpecific()
cm.build()