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__)
                 co = self.mf.replace_paths_in_code(module.__code__)
                 module.__code__ = co;
                 module.__code__ = co;
 
 
-    def __addPyc(self, multifile, filename, code):
+    def __addPyc(self, multifile, filename, code, compressionLevel):
         if code:
         if code:
             data = imp.get_magic() + '\0\0\0\0' + \
             data = imp.get_magic() + '\0\0\0\0' + \
                    marshal.dumps(code)
                    marshal.dumps(code)
 
 
             stream = StringStream(data)
             stream = StringStream(data)
-            multifile.addSubfile(filename, stream, 0)
+            multifile.addSubfile(filename, stream, compressionLevel)
             multifile.flush()
             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. """
         """ Adds all of the names on dirnames as a module directory. """
         if not dirnames:
         if not dirnames:
             return
             return
@@ -785,17 +785,18 @@ class Freezer:
                 else:
                 else:
                     filename += '.pyo'
                     filename += '.pyo'
                 code = compile('', moduleName, 'exec')
                 code = compile('', moduleName, 'exec')
-                self.__addPyc(multifile, filename, code)
+                self.__addPyc(multifile, filename, code, compressionLevel)
 
 
             moduleDirs[str] = True
             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. """
         """ Adds the named module to the multifile as a .pyc file. """
 
 
         # First, split the module into its subdirectory names.
         # First, split the module into its subdirectory names.
         dirnames = moduleName.split('.')
         dirnames = moduleName.split('.')
-        self.__addPythonDirs(multifile, moduleDirs, dirnames[:-1])
+        self.__addPythonDirs(multifile, moduleDirs, dirnames[:-1], compressionLevel)
 
 
         filename = '/'.join(dirnames)
         filename = '/'.join(dirnames)
 
 
@@ -823,7 +824,7 @@ class Freezer:
         if self.storePythonSource:
         if self.storePythonSource:
             if sourceFilename and sourceFilename.exists():
             if sourceFilename and sourceFilename.exists():
                 filename += '.py'
                 filename += '.py'
-                multifile.addSubfile(filename, sourceFilename, 0)
+                multifile.addSubfile(filename, sourceFilename, compressionLevel)
                 return
                 return
 
 
         # If we can't find the source file, add the compiled pyc instead.
         # If we can't find the source file, add the compiled pyc instead.
@@ -844,16 +845,17 @@ class Freezer:
                     source = source + '\n'
                     source = source + '\n'
                 code = compile(source, sourceFilename.cStr(), 'exec')
                 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
         """ After a call to done(), this stores all of the accumulated
         python code into the indicated Multifile. """
         python code into the indicated Multifile. """
 
 
         moduleDirs = {}
         moduleDirs = {}
         for moduleName, mdef in self.getModuleDefs():
         for moduleName, mdef in self.getModuleDefs():
             if mdef.token != self.MTForbid:
             if mdef.token != self.MTForbid:
-                self.__addPythonFile(multifile, moduleDirs, moduleName, mdef)
+                self.__addPythonFile(multifile, moduleDirs, moduleName, mdef,
+                                     compressionLevel)
     
     
     def writeMultifile(self, mfname):
     def writeMultifile(self, mfname):
         """ After a call to done(), this stores all of the accumulated
         """ 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")
     notify = directNotify.newCategory("Packager")
 
 
     class PackFile:
     class PackFile:
-        def __init__(self, filename, newName = None, deleteTemp = False):
+        def __init__(self, filename, newName = None, deleteTemp = False,
+                     extract = False):
             assert isinstance(filename, Filename)
             assert isinstance(filename, Filename)
             self.filename = filename
             self.filename = filename
             self.newName = newName
             self.newName = newName
             self.deleteTemp = deleteTemp
             self.deleteTemp = deleteTemp
+            self.extract = extract
 
 
     class Package:
     class Package:
         def __init__(self, packageName, packager):
         def __init__(self, packageName, packager):
@@ -44,6 +46,8 @@ class Packager:
             self.files = []
             self.files = []
             self.compressionLevel = 0
             self.compressionLevel = 0
             self.importedMapsDir = 'imported_maps'
             self.importedMapsDir = 'imported_maps'
+            self.mainModule = None
+            self.requires = []
 
 
             # This is the set of files and modules, already included
             # This is the set of files and modules, already included
             # by required packages, that we can skip.
             # by required packages, that we can skip.
@@ -148,12 +152,23 @@ class Packager:
                         # Any other file.
                         # Any other file.
                         self.addComponent(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.
             # Pick up any unfrozen Python files.
             self.freezer.done()
             self.freezer.done()
 
 
             # Add known module names.
             # Add known module names.
             self.moduleNames = {}
             self.moduleNames = {}
             for moduleName in self.freezer.getAllModuleNames():
             for moduleName in self.freezer.getAllModuleNames():
+                if moduleName == '__main__':
+                    # Ignore this special case.
+                    continue
+                
                 self.moduleNames[moduleName] = True
                 self.moduleNames[moduleName] = True
 
 
                 xmodule = TiXmlElement('module')
                 xmodule = TiXmlElement('module')
@@ -161,7 +176,9 @@ class Packager:
                 self.components.append(xmodule)
                 self.components.append(xmodule)
 
 
             if not self.dryRun:
             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.repack()
                 self.multifile.close()
                 self.multifile.close()
 
 
@@ -176,6 +193,43 @@ class Packager:
                 if file.deleteTemp:
                 if file.deleteTemp:
                     file.filename.unlink()
                     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):
         def compressMultifile(self):
             """ Compresses the .mf file into an .mf.pz file. """
             """ Compresses the .mf file into an .mf.pz file. """
 
 
@@ -435,8 +489,14 @@ class Packager:
             ext = Filename(file.newName).getExtension()
             ext = Filename(file.newName).getExtension()
             if ext in self.packager.uncompressibleExtensions:
             if ext in self.packager.uncompressibleExtensions:
                 compressible = False
                 compressible = False
+
+            extract = file.extract
             if ext in self.packager.extractExtensions:
             if ext in self.packager.extractExtensions:
                 extract = True
                 extract = True
+
+            if ext in self.packager.platformSpecificExtensions:
+                if not self.platform:
+                    self.platform = PandaSystem.getPlatform()
                 
                 
             compressionLevel = 0
             compressionLevel = 0
             if compressible:
             if compressible:
@@ -451,6 +511,19 @@ class Packager:
             xcomponent.SetAttribute('filename', file.newName)
             xcomponent.SetAttribute('filename', file.newName)
             self.components.append(xcomponent)
             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):
     def __init__(self):
 
 
         # The following are config settings that the caller may adjust
         # The following are config settings that the caller may adjust
@@ -513,6 +586,9 @@ class Packager:
         # Files that should be extracted to disk.
         # Files that should be extracted to disk.
         self.extractExtensions = [ 'dll', 'so', 'dylib', 'exe' ]
         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
         # Binary files that are considered uncompressible, and are
         # copied without compression.
         # copied without compression.
         self.uncompressibleExtensions = [ 'mp3', 'ogg' ]
         self.uncompressibleExtensions = [ 'mp3', 'ogg' ]
@@ -633,30 +709,40 @@ class Packager:
 
 
         return packageList
         return packageList
 
 
-    def parse_setenv(self, lineList):
+    def parse_set(self, lineList):
         """
         """
-        setenv variable value
+        set variable=value
         """
         """
         
         
         try:
         try:
-            command, variable, value = lineList
+            command, assign = lineList
         except ValueError:
         except ValueError:
             raise ArgumentNumber
             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)
         ExecutionEnvironment.setEnvironmentVariable(variable, value)
 
 
     def parse_begin_package(self, lineList):
     def parse_begin_package(self, lineList):
         """
         """
-        begin_package packageName
+        begin_package packageName [version=v]
         """
         """
-        
+
+        args = self.parseArgs(lineList, ['version'])
+
         try:
         try:
             command, packageName = lineList
             command, packageName = lineList
         except ValueError:
         except ValueError:
             raise ArgumentNumber
             raise ArgumentNumber
 
 
-        self.beginPackage(packageName)
+        version = args.get('version', None)
+
+        self.beginPackage(packageName, version = version, p3dApplication = False)
 
 
     def parse_end_package(self, lineList):
     def parse_end_package(self, lineList):
         """
         """
@@ -668,11 +754,23 @@ class Packager:
         except ValueError:
         except ValueError:
             raise ArgumentError
             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:
         try:
@@ -680,12 +778,22 @@ class Packager:
         except ValueError:
         except ValueError:
             raise ArgumentError
             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):
     def parse_module(self, lineList):
         """
         """
@@ -703,6 +811,18 @@ class Packager:
 
 
         self.module(moduleName, newName = newName)
         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):
     def parse_freeze_exe(self, lineList):
         """
         """
         freeze_exe path/to/basename
         freeze_exe path/to/basename
@@ -760,8 +880,31 @@ class Packager:
             raise ArgumentError
             raise ArgumentError
 
 
         self.dir(dirname, newDir = newDir)
         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
         """ Begins a new package specification.  packageName is the
         basename of the package.  Follow this with a number of calls
         basename of the package.  Follow this with a number of calls
         to file() etc., and close the package with endPackage(). """
         to file() etc., and close the package with endPackage(). """
@@ -769,12 +912,27 @@ class Packager:
         if self.currentPackage:
         if self.currentPackage:
             raise PackagerError, 'unmatched end_package %s' % (self.currentPackage.packageName)
             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 = 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
         package.dryRun = self.dryRun
         
         
         self.currentPackage = package
         self.currentPackage = package
 
 
-    def endPackage(self, packageName):
+    def endPackage(self, packageName, p3dApplication = False):
         """ Closes a package specification.  This actually generates
         """ Closes a package specification.  This actually generates
         the package file.  The packageName must match the previous
         the package file.  The packageName must match the previous
         call to beginPackage(). """
         call to beginPackage(). """
@@ -784,6 +942,11 @@ class Packager:
         if self.currentPackage.packageName != packageName:
         if self.currentPackage.packageName != packageName:
             raise PackagerError, 'end_package %s where %s expected' % (
             raise PackagerError, 'end_package %s where %s expected' % (
                 packageName, self.currentPackage.packageName)
                 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 = self.currentPackage
         package.close()
         package.close()
@@ -807,105 +970,49 @@ class Packager:
 
 
         # Look on the searchlist.
         # Look on the searchlist.
         for path in self.installSearch:
         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
         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
         """ Scans a directory on disk, looking for _import.xml files
         that match the indicated packageName and option version.  If a
         that match the indicated packageName and option version.  If a
         suitable xml file is found, reads it and returns the assocated
         suitable xml file is found, reads it and returns the assocated
         Package definition. """
         Package definition. """
-        
-        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):
     def readPackageImportDescFile(self, filename):
         """ Reads the named xml file as a Package, and returns it if
         """ Reads the named xml file as a Package, and returns it if
@@ -917,26 +1024,51 @@ class Packager:
 
 
         return None
         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
         Attempts to install this package will implicitly install the
         named package also.  Files already included in the named
         named package also.  Files already included in the named
         package will be omitted from this one. """
         package will be omitted from this one. """
 
 
-        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):
     def module(self, moduleName, newName = None):
         """ Adds the indicated Python module to the current package. """
         """ Adds the indicated Python module to the current package. """
@@ -946,6 +1078,19 @@ class Packager:
 
 
         self.currentPackage.freezer.addModule(moduleName, newName = newName)
         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):
     def freeze(self, filename, compileToExe = False):
         """ Freezes all of the current Python code into either an
         """ Freezes all of the current Python code into either an
         executable (if compileToExe is true) or a dynamic library (if
         executable (if compileToExe is true) or a dynamic library (if
@@ -958,6 +1103,19 @@ class Packager:
 
 
         package = self.currentPackage
         package = self.currentPackage
         freezer = package.freezer
         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()
         freezer.done()
 
 
         if not package.dryRun:
         if not package.dryRun:
@@ -966,12 +1124,16 @@ class Packager:
             if '/' in basename:
             if '/' in basename:
                 dirname, basename = filename.rsplit('/', 1)
                 dirname, basename = filename.rsplit('/', 1)
                 dirname += '/'
                 dirname += '/'
+
             basename = freezer.generateCode(basename, compileToExe = compileToExe)
             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.
         # Reset the freezer for more Python files.
         freezer.reset()
         freezer.reset()
+        package.mainModule = None
 
 
 
 
     def file(self, filename, newNameOrDir = 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
 import sys
 from direct.showbase import VFSImporter
 from direct.showbase import VFSImporter
 from direct.showbase.DirectObject import DirectObject
 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.stdpy import file
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.task.TaskManagerGlobal import taskMgr
 from direct.showbase.MessengerGlobal import messenger
 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.
             # Hang a hook so we know when the window is actually opened.
             self.acceptOnce('window-event', self.windowEvent)
             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):
             if hasattr(main, 'main') and callable(main.main):
                 main.main()
                 main.main()
 
 
@@ -233,31 +251,6 @@ class AppRunner(DirectObject):
 
 
         # Now go load the applet.
         # Now go load the applet.
         fname = Filename.fromOsSpecific(p3dFilename)
         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()
         vfs = VirtualFileSystem.getGlobalPtr()
 
 
         if not vfs.exists(fname):
         if not vfs.exists(fname):
@@ -285,6 +278,19 @@ class AppRunner(DirectObject):
                 data = open(pathname, 'r').read()
                 data = open(pathname, 'r').read()
                 loadPrcFileData(pathname, data)
                 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
         self.gotP3DFilename = True
 
 
         # Send this call to the main thread; don't call it directly.
         # Send this call to the main thread; don't call it directly.