Browse Source

better xml, better requires

David Rose 16 years ago
parent
commit
57e6e80491
2 changed files with 207 additions and 202 deletions
  1. 195 39
      direct/src/showutil/Packager.py
  2. 12 163
      direct/src/showutil/ppackage.py

+ 195 - 39
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. """
+
+            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, filename, newName):
+        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.
 

+ 12 - 163
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 <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 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)
-    
+if not packager.installDir:
+    packager.installDir = Filename('install')
+packager.installSearch = [packager.installDir]
 
+packager.setup()
+packager.readPackageDef(packageDef)