فهرست منبع

better xml, better requires

David Rose 16 سال پیش
والد
کامیت
57e6e80491
2فایلهای تغییر یافته به همراه207 افزوده شده و 202 حذف شده
  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.packageDesc = packageDir + self.packageDesc
             self.packageImportDesc = packageDir + self.packageImportDesc
             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:
             if self.dryRun:
                 self.multifile = None
                 self.multifile = None
             else:
             else:
                 self.multifile = Multifile()
                 self.multifile = Multifile()
-                self.multifile.openReadWrite(self.packageFilename)
+                self.multifile.openReadWrite(self.packageFullpath)
 
 
             self.extracts = []
             self.extracts = []
             self.components = []
             self.components = []
@@ -183,12 +180,14 @@ class Packager:
             """ Compresses the .mf file into an .mf.pz file. """
             """ Compresses the .mf file into an .mf.pz file. """
 
 
             compressedName = self.packageFilename + '.pz'
             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
                 raise PackagerError, message
 
 
         def writeDescFile(self):
         def writeDescFile(self):
-            doc = TiXmlDocument(self.packageDesc)
+            packageDescFullpath = Filename(self.packager.installDir, self.packageDesc)
+            doc = TiXmlDocument(packageDescFullpath.toOsSpecific())
             decl = TiXmlDeclaration("1.0", "utf-8", "")
             decl = TiXmlDeclaration("1.0", "utf-8", "")
             doc.InsertEndChild(decl)
             doc.InsertEndChild(decl)
 
 
@@ -200,9 +199,11 @@ class Packager:
                 xpackage.SetAttribute('version', self.version)
                 xpackage.SetAttribute('version', self.version)
 
 
             xuncompressedArchive = self.getFileSpec(
             xuncompressedArchive = self.getFileSpec(
-                'uncompressed_archive', self.packageFilename, self.packageBasename)
+                'uncompressed_archive', self.packageFullpath,
+                self.packageBasename)
             xcompressedArchive = self.getFileSpec(
             xcompressedArchive = self.getFileSpec(
-                'compressed_archive', self.packageFilename + '.pz', self.packageBasename + '.pz')
+                'compressed_archive', self.packageFullpath + '.pz',
+                self.packageBasename + '.pz')
             xpackage.InsertEndChild(xuncompressedArchive)
             xpackage.InsertEndChild(xuncompressedArchive)
             xpackage.InsertEndChild(xcompressedArchive)
             xpackage.InsertEndChild(xcompressedArchive)
 
 
@@ -213,7 +214,8 @@ class Packager:
             doc.SaveFile()
             doc.SaveFile()
 
 
         def writeImportDescFile(self):
         def writeImportDescFile(self):
-            doc = TiXmlDocument(self.packageImportDesc)
+            packageImportDescFullpath = Filename(self.packager.installDir, self.packageImportDesc)
+            doc = TiXmlDocument(packageImportDescFullpath.toOsSpecific())
             decl = TiXmlDeclaration("1.0", "utf-8", "")
             decl = TiXmlDeclaration("1.0", "utf-8", "")
             doc.InsertEndChild(decl)
             doc.InsertEndChild(decl)
 
 
@@ -230,19 +232,52 @@ class Packager:
             doc.InsertEndChild(xpackage)
             doc.InsertEndChild(xpackage)
             doc.SaveFile()
             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
             """ Returns an xcomponent or similar element with the file
             information for the indicated file. """
             information for the indicated file. """
             
             
             xspec = TiXmlElement(element)
             xspec = TiXmlElement(element)
 
 
-            filename = Filename(filename)
-            size = filename.getFileSize()
-            timestamp = filename.getTimestamp()
+            size = pathname.getFileSize()
+            timestamp = pathname.getTimestamp()
 
 
             hv = HashVal()
             hv = HashVal()
-            hv.hashFile(filename)
+            hv.hashFile(pathname)
             hash = hv.asHex()
             hash = hv.asHex()
 
 
             xspec.SetAttribute('filename', newName)
             xspec.SetAttribute('filename', newName)
@@ -426,6 +461,10 @@ class Packager:
         self.installDir = None
         self.installDir = None
         self.persistDir = None
         self.persistDir = None
 
 
+        # A search list of directories and/or URL's to search for
+        # installed packages.
+        self.installSearch = []
+
         # The platform string.
         # The platform string.
         self.platform = PandaSystem.getPlatform()
         self.platform = PandaSystem.getPlatform()
 
 
@@ -500,28 +539,26 @@ class Packager:
 
 
         self.currentPackage = None
         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):
     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
@@ -755,21 +792,140 @@ class Packager:
         self.packages[package.packageName] = package
         self.packages[package.packageName] = package
         self.currentPackage = None
         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
         """ 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.
         search path.
 
 
         Returns the Package object, or None if the package cannot be
         Returns the Package object, or None if the package cannot be
         located. """
         located. """
 
 
         # Is it a package we already have resident?
         # Is it a package we already have resident?
-        package = self.packages.get(packageName, None)
+        package = self.packages.get((packageName, version), None)
         if package:
         if package:
             return 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
         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):
     def require(self, package):
         """ Indicates a dependency on the indicated 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:
 Usage:
 
 
-  ppackage.py [opts] package.pdef command
+  ppackage.py [opts] package.pdef
 
 
 Required:
 Required:
 
 
@@ -24,29 +24,15 @@ Required:
     to be built, in excruciating detail.  Use "ppackage.py -H" to
     to be built, in excruciating detail.  Use "ppackage.py -H" to
     describe the syntax of this file.
     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:
 Options:
 
 
   -i install_dir
   -i install_dir
      The full path to a local directory to copy the
      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
   -d persist_dir
      The full path to a local directory that retains persistant state
      The full path to a local directory that retains persistant state
@@ -69,133 +55,6 @@ Options:
      Display this help
      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 sys
 import getopt
 import getopt
 import os
 import os
@@ -235,24 +94,14 @@ for opt, arg in opts:
 if not args:
 if not args:
     usage(0)
     usage(0)
     
     
-if len(args) != 2:
+if len(args) != 1:
     usage(1)
     usage(1)
 
 
 packageDef = Filename.fromOsSpecific(args[0])
 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)