Browse Source

p3d_info.xml

David Rose 16 years ago
parent
commit
0e596fe4e2
3 changed files with 330 additions and 160 deletions
  1. 13 11
      direct/src/showutil/FreezeTool.py
  2. 284 122
      direct/src/showutil/Packager.py
  3. 33 27
      direct/src/showutil/runp3d.py

+ 13 - 11
direct/src/showutil/FreezeTool.py

@@ -754,16 +754,16 @@ class Freezer:
                 co = self.mf.replace_paths_in_code(module.__code__)
                 module.__code__ = co;
 
-    def __addPyc(self, multifile, filename, code):
+    def __addPyc(self, multifile, filename, code, compressionLevel):
         if code:
             data = imp.get_magic() + '\0\0\0\0' + \
                    marshal.dumps(code)
 
             stream = StringStream(data)
-            multifile.addSubfile(filename, stream, 0)
+            multifile.addSubfile(filename, stream, compressionLevel)
             multifile.flush()
 
-    def __addPythonDirs(self, multifile, moduleDirs, dirnames):
+    def __addPythonDirs(self, multifile, moduleDirs, dirnames, compressionLevel):
         """ Adds all of the names on dirnames as a module directory. """
         if not dirnames:
             return
@@ -785,17 +785,18 @@ class Freezer:
                 else:
                     filename += '.pyo'
                 code = compile('', moduleName, 'exec')
-                self.__addPyc(multifile, filename, code)
+                self.__addPyc(multifile, filename, code, compressionLevel)
 
             moduleDirs[str] = True
-            self.__addPythonDirs(multifile, moduleDirs, dirnames[:-1])
+            self.__addPythonDirs(multifile, moduleDirs, dirnames[:-1], compressionLevel)
 
-    def __addPythonFile(self, multifile, moduleDirs, moduleName, mdef):
+    def __addPythonFile(self, multifile, moduleDirs, moduleName, mdef,
+                        compressionLevel):
         """ Adds the named module to the multifile as a .pyc file. """
 
         # First, split the module into its subdirectory names.
         dirnames = moduleName.split('.')
-        self.__addPythonDirs(multifile, moduleDirs, dirnames[:-1])
+        self.__addPythonDirs(multifile, moduleDirs, dirnames[:-1], compressionLevel)
 
         filename = '/'.join(dirnames)
 
@@ -823,7 +824,7 @@ class Freezer:
         if self.storePythonSource:
             if sourceFilename and sourceFilename.exists():
                 filename += '.py'
-                multifile.addSubfile(filename, sourceFilename, 0)
+                multifile.addSubfile(filename, sourceFilename, compressionLevel)
                 return
 
         # If we can't find the source file, add the compiled pyc instead.
@@ -844,16 +845,17 @@ class Freezer:
                     source = source + '\n'
                 code = compile(source, sourceFilename.cStr(), 'exec')
 
-        self.__addPyc(multifile, filename, code)
+        self.__addPyc(multifile, filename, code, compressionLevel)
 
-    def addToMultifile(self, multifile):
+    def addToMultifile(self, multifile, compressionLevel = 0):
         """ After a call to done(), this stores all of the accumulated
         python code into the indicated Multifile. """
 
         moduleDirs = {}
         for moduleName, mdef in self.getModuleDefs():
             if mdef.token != self.MTForbid:
-                self.__addPythonFile(multifile, moduleDirs, moduleName, mdef)
+                self.__addPythonFile(multifile, moduleDirs, moduleName, mdef,
+                                     compressionLevel)
     
     def writeMultifile(self, mfname):
         """ After a call to done(), this stores all of the accumulated

+ 284 - 122
direct/src/showutil/Packager.py

@@ -28,11 +28,13 @@ class Packager:
     notify = directNotify.newCategory("Packager")
 
     class PackFile:
-        def __init__(self, filename, newName = None, deleteTemp = False):
+        def __init__(self, filename, newName = None, deleteTemp = False,
+                     extract = False):
             assert isinstance(filename, Filename)
             self.filename = filename
             self.newName = newName
             self.deleteTemp = deleteTemp
+            self.extract = extract
 
     class Package:
         def __init__(self, packageName, packager):
@@ -44,6 +46,8 @@ class Packager:
             self.files = []
             self.compressionLevel = 0
             self.importedMapsDir = 'imported_maps'
+            self.mainModule = None
+            self.requires = []
 
             # This is the set of files and modules, already included
             # by required packages, that we can skip.
@@ -148,12 +152,23 @@ class Packager:
                         # Any other file.
                         self.addComponent(file)
 
+            if not self.mainModule and self.p3dApplication:
+                message = 'No main_module specified for application %s' % (self.packageName)
+                raise PackagerError, message
+            if self.mainModule:
+                if self.mainModule not in self.freezer.modules:
+                    self.freezer.addModule(self.mainModule)
+
             # Pick up any unfrozen Python files.
             self.freezer.done()
 
             # Add known module names.
             self.moduleNames = {}
             for moduleName in self.freezer.getAllModuleNames():
+                if moduleName == '__main__':
+                    # Ignore this special case.
+                    continue
+                
                 self.moduleNames[moduleName] = True
 
                 xmodule = TiXmlElement('module')
@@ -161,7 +176,9 @@ class Packager:
                 self.components.append(xmodule)
 
             if not self.dryRun:
-                self.freezer.addToMultifile(self.multifile)
+                self.freezer.addToMultifile(self.multifile, self.compressionLevel)
+                if self.p3dApplication:
+                    self.makeP3dInfo()
                 self.multifile.repack()
                 self.multifile.close()
 
@@ -176,6 +193,43 @@ class Packager:
                 if file.deleteTemp:
                     file.filename.unlink()
 
+        def makeP3dInfo(self):
+            """ Makes the p3d_info.xml file that defines the
+            application startup parameters and such. """
+
+            doc = TiXmlDocument()
+            decl = TiXmlDeclaration("1.0", "utf-8", "")
+            doc.InsertEndChild(decl)
+
+            xpackage = TiXmlElement('package')
+            xpackage.SetAttribute('name', self.packageName)
+            if self.platform:
+                xpackage.SetAttribute('platform', self.platform)
+            if self.version:
+                xpackage.SetAttribute('version', self.version)
+
+            xpackage.SetAttribute('main_module', self.mainModule)
+
+            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)
+
+            doc.InsertEndChild(xpackage)
+
+            # Write the xml file to a temporary file on disk, so we
+            # can add it to the multifile.
+            filename = Filename.temporary('', 'p3d_', '.xml')
+            doc.SaveFile(filename.toOsSpecific())
+            self.multifile.addSubfile('p3d_info.xml', filename, self.compressionLevel)
+            self.multifile.flush()
+            filename.unlink()
+            
+
         def compressMultifile(self):
             """ Compresses the .mf file into an .mf.pz file. """
 
@@ -435,8 +489,14 @@ class Packager:
             ext = Filename(file.newName).getExtension()
             if ext in self.packager.uncompressibleExtensions:
                 compressible = False
+
+            extract = file.extract
             if ext in self.packager.extractExtensions:
                 extract = True
+
+            if ext in self.packager.platformSpecificExtensions:
+                if not self.platform:
+                    self.platform = PandaSystem.getPlatform()
                 
             compressionLevel = 0
             if compressible:
@@ -451,6 +511,19 @@ class Packager:
             xcomponent.SetAttribute('filename', file.newName)
             self.components.append(xcomponent)
 
+        def requirePackage(self, package):
+            """ Indicates a dependency on the given package. """
+
+            if package in self.requires:
+                # Already on the list.
+                return
+
+            self.requires.append(package)
+            for filename in package.targetFilenames.keys():
+                self.skipFilenames[filename] = True
+            for moduleName in package.moduleNames.keys():
+                self.skipModules[moduleName] = True
+
     def __init__(self):
 
         # The following are config settings that the caller may adjust
@@ -513,6 +586,9 @@ class Packager:
         # Files that should be extracted to disk.
         self.extractExtensions = [ 'dll', 'so', 'dylib', 'exe' ]
 
+        # Files that indicate a platform dependency.
+        self.platformSpecificExtensions = [ 'dll', 'so', 'dylib', 'exe' ]
+
         # Binary files that are considered uncompressible, and are
         # copied without compression.
         self.uncompressibleExtensions = [ 'mp3', 'ogg' ]
@@ -633,30 +709,40 @@ class Packager:
 
         return packageList
 
-    def parse_setenv(self, lineList):
+    def parse_set(self, lineList):
         """
-        setenv variable value
+        set variable=value
         """
         
         try:
-            command, variable, value = lineList
+            command, assign = lineList
         except ValueError:
             raise ArgumentNumber
+        
+        try:
+            variable, value = assign.split('=', 1)
+        except ValueError:
+            raise PackagerError, 'Equals sign required in assignment'
 
-        value = ExecutionEnvironment.expandString(value)
+        variable = variable.strip()
+        value = ExecutionEnvironment.expandString(value.strip())
         ExecutionEnvironment.setEnvironmentVariable(variable, value)
 
     def parse_begin_package(self, lineList):
         """
-        begin_package packageName
+        begin_package packageName [version=v]
         """
-        
+
+        args = self.parseArgs(lineList, ['version'])
+
         try:
             command, packageName = lineList
         except ValueError:
             raise ArgumentNumber
 
-        self.beginPackage(packageName)
+        version = args.get('version', None)
+
+        self.beginPackage(packageName, version = version, p3dApplication = False)
 
     def parse_end_package(self, lineList):
         """
@@ -668,11 +754,23 @@ class Packager:
         except ValueError:
             raise ArgumentError
 
-        self.endPackage(packageName)
+        self.endPackage(packageName, p3dApplication = False)
 
-    def parse_require(self, lineList):
+    def parse_begin_p3d(self, lineList):
         """
-        require packageName
+        begin_p3d appName
+        """
+
+        try:
+            command, packageName = lineList
+        except ValueError:
+            raise ArgumentNumber
+
+        self.beginPackage(packageName, p3dApplication = True)
+
+    def parse_end_p3d(self, lineList):
+        """
+        end_p3d appName
         """
 
         try:
@@ -680,12 +778,22 @@ class Packager:
         except ValueError:
             raise ArgumentError
 
-        package = self.findPackage(packageName)
-        if not package:
-            message = "Unknown package %s" % (packageName)
-            raise PackagerError, message
+        self.endPackage(packageName, p3dApplication = True)
 
-        self.require(package)
+    def parse_require(self, lineList):
+        """
+        require packageName [version=v]
+        """
+
+        args = self.parseArgs(lineList, ['version'])
+
+        try:
+            command, packageName = lineList
+        except ValueError:
+            raise ArgumentError
+
+        version = args.get('version', None)
+        self.require(packageName, version = version)
 
     def parse_module(self, lineList):
         """
@@ -703,6 +811,18 @@ class Packager:
 
         self.module(moduleName, newName = newName)
 
+    def parse_main_module(self, lineList):
+        """
+        main_module moduleName
+        """
+
+        try:
+            command, moduleName = lineList
+        except ValueError:
+            raise ArgumentError
+
+        self.mainModule(moduleName)
+
     def parse_freeze_exe(self, lineList):
         """
         freeze_exe path/to/basename
@@ -760,8 +880,31 @@ class Packager:
             raise ArgumentError
 
         self.dir(dirname, newDir = newDir)
+
+    def parseArgs(self, lineList, argList):
+        args = {}
+        
+        while len(lineList) > 1:
+            arg = lineList[-1]
+            if '=' not in arg:
+                return args
+
+            parameter, value = arg.split('=', 1)
+            parameter = parameter.strip()
+            value = value.strip()
+            if parameter not in argList:
+                message = 'Unknown parameter %s' % (parameter)
+                raise PackagerError, message
+            if parameter in args:
+                message = 'Duplicate parameter %s' % (parameter)
+                raise PackagerError, message
+
+            args[parameter] = value
+
+            del lineList[-1]
+                
     
-    def beginPackage(self, packageName):
+    def beginPackage(self, packageName, version = None, p3dApplication = False):
         """ Begins a new package specification.  packageName is the
         basename of the package.  Follow this with a number of calls
         to file() etc., and close the package with endPackage(). """
@@ -769,12 +912,27 @@ class Packager:
         if self.currentPackage:
             raise PackagerError, 'unmatched end_package %s' % (self.currentPackage.packageName)
 
+        # A special case for the Panda3D package.  We enforce that the
+        # version number matches what we've been compiled with.
+        if packageName == 'panda3d':
+            if version is None:
+                version = PandaSystem.getPackageVersionString()
+            else:
+                if version != PandaSystem.getPackageVersionString():
+                    message = 'mismatched Panda3D version: requested %s, but Panda3D is built as %s' % (version, PandaSystem.getPackageVersionString())
+                    raise PackageError, message
+
         package = self.Package(packageName, self)
+        package.version = version
+        package.p3dApplication = p3dApplication
+        if package.p3dApplication:
+            # Default compression level for an app.
+            package.compressionLevel = 6
         package.dryRun = self.dryRun
         
         self.currentPackage = package
 
-    def endPackage(self, packageName):
+    def endPackage(self, packageName, p3dApplication = False):
         """ Closes a package specification.  This actually generates
         the package file.  The packageName must match the previous
         call to beginPackage(). """
@@ -784,6 +942,11 @@ class Packager:
         if self.currentPackage.packageName != packageName:
             raise PackagerError, 'end_package %s where %s expected' % (
                 packageName, self.currentPackage.packageName)
+        if self.currentPackage.p3dApplication != p3dApplication:
+            if p3dApplication:
+                raise PackagerError, 'end_p3d where end_package expected'
+            else:
+                raise PackagerError, 'end_package where end_p3d expected'
 
         package = self.currentPackage
         package.close()
@@ -807,105 +970,49 @@ class Packager:
 
         # 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
+            package = self.scanPackageDir(path, packageName, version, self.platform)
+            if package:
+                self.packages[(packageName, version)] = package
+                return package
+            package = self.scanPackageDir(path, packageName, version, None)
+            if package:
+                self.packages[(packageName, version)] = package
+                return package
                 
         return None
 
-    def scanPackageDir(self, packageDir, packageName, packageVersion):
+    def scanPackageDir(self, rootDir, packageName, version, platform):
         """ 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
+        packageDir = Filename(rootDir, packageName)
+        basename = packageName
+
+        if platform:
+            packageDir = Filename(packageDir, platform)
+            basename += '_%s' % (platform)
+
+        if version:
+            packageDir = Filename(packageDir, version)
+            basename += '_%s' % (version)
+
+        basename += '_import.xml'
+        filename = Filename(packageDir, basename)
+        if filename.exists():
+            # It exists in the nested directory.
+            package = self.readPackageImportDescFile(filename)
+            if package:
+                return package
+        filename = Filename(rootDir, basename)
+        if filename.exists():
+            # It exists in the root directory.
+            package = self.readPackageImportDescFile(filename)
+            if package:
+                return package
 
-                elif len(parts) == 3:
-                    # Got a version number.
-                    version = self.parseVersionForCompare(parts[1])
-                    if bestVersion > version:
-                        bestVersion = version
-                        bestPackage = package
-
-        return bestPackage
+        return None
 
     def readPackageImportDescFile(self, filename):
         """ Reads the named xml file as a Package, and returns it if
@@ -917,26 +1024,51 @@ class Packager:
 
         return None
 
+    def require(self, packageName, version = None):
+        """ Indicates a dependency on the named package, supplied as
+        name.
+
+        Attempts to install this package will implicitly install the
+        named package also.  Files already included in the named
+        package will be omitted from this one. """
 
-    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. """
+        # A special case for the Panda3D package.  We enforce that the
+        # version number matches what we've been compiled with.
+        if packageName == 'panda3d':
+            if version is None:
+                version = PandaSystem.getPackageVersionString()
+        
+        package = self.findPackage(packageName, version = version)
+        if not package:
+            message = "Unknown package %s" % (packageName)
+            raise PackagerError, message
 
-        # TODO.  For now, we just use a dumb string compare.
-        return (version,)
+        self.requirePackage(package)
 
-    def require(self, package):
-        """ Indicates a dependency on the indicated package.
+    def requirePackage(self, package):
+        """ Indicates a dependency on the indicated package, supplied
+        as a Package object.
 
         Attempts to install this package will implicitly install the
         named package also.  Files already included in the named
         package will be omitted from this one. """
 
-        for filename in package.targetFilenames.keys():
-            self.currentPackage.skipFilenames[filename] = True
-        for moduleName in package.moduleNames.keys():
-            self.currentPackage.skipModules[moduleName] = True
+        # A special case for the Panda3D package.  We enforce that the
+        # version number matches what we've been compiled with.
+        if package.packageName == 'panda3d':
+            if package.version != PandaSystem.getPackageVersionString():
+                if not PandaSystem.getPackageVersionString():
+                    # We haven't been compiled with any particular
+                    # version of Panda.  This is a warning, not an
+                    # error.
+                    print "Warning: requiring panda3d version %s, which may or may not match the current build of Panda.  Recommend that you use only the official Panda3D build for making distributable applications to ensure compatibility." % (package.version)
+                else:
+                    # This particular version of Panda doesn't match
+                    # the requested version.  Again, a warning, not an
+                    # error.
+                    print "Warning: requiring panda3d version %s, which does not match the current build of Panda, which is version %s." % (package, PandaSystem.getPackageVersionString())
+
+        self.currentPackage.requirePackage(package)
 
     def module(self, moduleName, newName = None):
         """ Adds the indicated Python module to the current package. """
@@ -946,6 +1078,19 @@ class Packager:
 
         self.currentPackage.freezer.addModule(moduleName, newName = newName)
 
+    def mainModule(self, moduleName, newName = None):
+        """ Names the indicated module as the "main" module of the
+        application or exe. """
+
+        if not self.currentPackage:
+            raise OutsideOfPackageError
+
+        if self.currentPackage.mainModule and self.currentPackage.mainModule != moduleName:
+            self.notify.warning("Replacing main_module %s with %s" % (
+                self.currentPackage.mainModule, moduleName))
+
+        self.currentPackage.mainModule = moduleName
+
     def freeze(self, filename, compileToExe = False):
         """ Freezes all of the current Python code into either an
         executable (if compileToExe is true) or a dynamic library (if
@@ -958,6 +1103,19 @@ class Packager:
 
         package = self.currentPackage
         freezer = package.freezer
+
+        if package.mainModule and not compileToExe:
+            self.notify.warning("Ignoring main_module for dll %s" % (filename))
+            package.mainModule = None
+        if not package.mainModule and compileToExe:
+            message = "No main_module specified for exe %s" % (filename)
+            raise PackagerError, message
+
+        if package.mainModule:
+            if package.mainModule not in freezer.modules:
+                freezer.addModule(package.mainModule, newName = '__main__')
+            else:
+                freezer.modules['__main__'] = freezer.modules[package.mainModule]
         freezer.done()
 
         if not package.dryRun:
@@ -966,12 +1124,16 @@ class Packager:
             if '/' in basename:
                 dirname, basename = filename.rsplit('/', 1)
                 dirname += '/'
+
             basename = freezer.generateCode(basename, compileToExe = compileToExe)
 
-            package.files.append(self.PackFile(Filename(basename), newName = dirname + basename, deleteTemp = True))
+            package.files.append(self.PackFile(Filename(basename), newName = dirname + basename, deleteTemp = True, extract = True))
+            if not package.platform:
+                package.platform = PandaSystem.getPlatform()
 
         # Reset the freezer for more Python files.
         freezer.reset()
+        package.mainModule = None
 
 
     def file(self, filename, newNameOrDir = None):

+ 33 - 27
direct/src/showutil/runp3d.py

@@ -22,7 +22,7 @@ See pack3d.py for a script that generates these p3d files.
 import sys
 from direct.showbase import VFSImporter
 from direct.showbase.DirectObject import DirectObject
-from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties
+from pandac.PandaModules import VirtualFileSystem, Filename, Multifile, loadPrcFileData, unloadPrcFile, getModelPath, HTTPClient, Thread, WindowProperties, readXmlStream
 from direct.stdpy import file
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase.MessengerGlobal import messenger
@@ -192,7 +192,25 @@ class AppRunner(DirectObject):
             # Hang a hook so we know when the window is actually opened.
             self.acceptOnce('window-event', self.windowEvent)
 
-            import main
+            # Look for the startup Python file.  This may be a magic
+            # filename (like "__main__", or any filename that contains
+            # invalid module characters), so we can't just import it
+            # directly; instead, we go through the low-level importer.
+
+            # If there's no p3d_info.xml file, we look for "main".
+            moduleName = 'main'
+            if self.p3dPackage:
+                mainName = self.p3dPackage.Attribute('main_module')
+                if mainName:
+                    moduleName = mainName
+            
+            v = VFSImporter.VFSImporter(MultifileRoot)
+            loader = v.find_module(moduleName)
+            if not loader:
+                message = "No %s.py found in application." % (mainName)
+                raise StandardError, message
+            
+            main = loader.load_module(moduleName)
             if hasattr(main, 'main') and callable(main.main):
                 main.main()
 
@@ -233,31 +251,6 @@ class AppRunner(DirectObject):
 
         # Now go load the applet.
         fname = Filename.fromOsSpecific(p3dFilename)
-        if not p3dFilename:
-            # If we didn't get a literal filename, we have to download it
-            # from the URL.  TODO: make this a smarter temporary filename?
-            fname = Filename.temporary('', 'p3d_')
-            fname.setExtension('p3d')
-            p3dFilename = fname.toOsSpecific()
-            src = self.tokenDict.get('src', None)
-            if not src:
-                raise ArgumentError, "No Panda app specified."
-
-            http = HTTPClient.getGlobalPtr()
-            hc = http.getDocument(src)
-            if not hc.downloadToFile(fname):
-                fname.unlink()
-                raise ArgumentError, "Couldn't download %s" % (src)
-
-            # Set a hook on sys.exit to delete the temporary file.
-            oldexitfunc = getattr(sys, 'exitfunc', None)
-            def deleteTempFile(fname = fname, oldexitfunc = oldexitfunc):
-                fname.unlink()
-                if oldexitfunc:
-                    oldexitfunc()
-
-            sys.exitfunc = deleteTempFile
-
         vfs = VirtualFileSystem.getGlobalPtr()
 
         if not vfs.exists(fname):
@@ -285,6 +278,19 @@ class AppRunner(DirectObject):
                 data = open(pathname, 'r').read()
                 loadPrcFileData(pathname, data)
 
+        # Now load the p3dInfo file.
+        self.p3dInfo = None
+        self.p3dPackage = None
+        i = mf.findSubfile('p3d_info.xml')
+        if i >= 0:
+            stream = mf.openReadSubfile(i)
+            self.p3dInfo = readXmlStream(stream)
+            mf.closeReadSubfile(stream)
+        if self.p3dInfo:
+            p3dPackage = self.p3dInfo.FirstChild('package')
+            if p3dPackage:
+                self.p3dPackage = p3dPackage.ToElement()
+
         self.gotP3DFilename = True
 
         # Send this call to the main thread; don't call it directly.