diff --git a/direct/src/showutil/Packager.py b/direct/src/showutil/Packager.py index 320cd6bb42..e87a4496e8 100644 --- a/direct/src/showutil/Packager.py +++ b/direct/src/showutil/Packager.py @@ -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. diff --git a/direct/src/showutil/ppackage.py b/direct/src/showutil/ppackage.py index f09dee6c57..42f28c0bdc 100755 --- a/direct/src/showutil/ppackage.py +++ b/direct/src/showutil/ppackage.py @@ -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 -# -# Begins a new multifile. All files named after this line and -# until the next multifile line will be a part of this multifile. -# -# -# The filename of the multifile, no directory. -# -# -# The numeric phase in which this multifile should be downloaded. -# -# -# file -# -# Adds a single file to the current multifile. -# -# -# 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. -# -# -# 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. -# -# -# 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. -# -# -# 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 -# -# 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. -# -# -# (Same as for the file command, above.) -# -# -# The name of the local directory to scan on the publishing -# machine. -# -# -# The name of the corresponding local directory on the client -# machine; similar to 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 -# -# -# (Same as for the file command, above.) -# -# -# 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. -# -# -# The name of the python module that will be invoked first (like -# main()) when the resulting executable is started. -# -# -# (Same as for the file command, above.) -# -# freeze_dll -# -# -# (Same as for the file command, above.) -# -# -# 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. -# -# -# (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)