Browse Source

More pdeploy stuff.

rdb 16 years ago
parent
commit
a737a35314
3 changed files with 94 additions and 52 deletions
  1. 87 45
      direct/src/p3d/DeploymentTools.py
  2. 1 1
      direct/src/p3d/coreapi.pdef
  3. 6 6
      direct/src/p3d/pdeploy.py

+ 87 - 45
direct/src/p3d/DeploymentTools.py

@@ -9,6 +9,7 @@ from direct.directnotify.DirectNotifyGlobal import *
 from pandac.PandaModules import PandaSystem, HTTPClient, Filename, VirtualFileSystem
 from direct.p3d.HostInfo import HostInfo
 from direct.showbase.AppRunnerGlobal import appRunner
+import glob
 
 class CachedFile:
     def __init__(self): self.str = ""
@@ -48,6 +49,8 @@ class Standalone:
         if len(platforms) == 0:
             Standalone.notify.error("No platforms found to build for!")
         
+        outputDir = Filename(outputDir + "/")
+        outputDir.makeDir()
         for platform in platforms:
             if platform.startswith("win"):
                 self.build(Filename(outputDir, platform + "/" + self.basename + ".exe"), platform)
@@ -82,8 +85,7 @@ class Standalone:
                 Standalone.notify.error("  -> %s failed for platform %s" % (package.packageName, package.platform))
                 continue
             
-            self.embed(output, p3dembed)
-            return
+            return self.embed(output, p3dembed)
         
         Standalone.notify.error("Failed to build standalone for platform %s" % platform)
     
@@ -123,6 +125,28 @@ class Standalone:
         
         os.chmod(output.toOsSpecific(), 0755)
 
+    def getExtraFiles(self, platform):
+        """ Returns a list of extra files that will need to be included
+        with the standalone executable in order for it to run, such as
+        dependent libraries. The returned paths will be absolute. """
+        
+        for package in self.host.getPackages(name = "p3dembed", platform = platform):
+            if not package.downloadDescFile(self.http):
+                Standalone.notify.error("  -> %s failed for platform %s" % (package.packageName, package.platform))
+                continue
+            if not package.downloadPackage(self.http):
+                Standalone.notify.error("  -> %s failed for platform %s" % (package.packageName, package.platform))
+                continue
+            
+            directory = Filename(self.host.hostDir, "p3dembed/%s/p3dembed.exe" % package.platform)
+            directory.makeAbsolute()
+            filelist = []
+            for f in glob.glob(os.path.join(directory.toOsSpecific(), "*")):
+                if not f.endswith("p3dembed") and not f.endswith("p3dembed.exe"):
+                    filelist.append(Filename.fromOsSpecific(f))
+            return filelist
+        return []
+
 class Installer:
     """ This class creates a (graphical) installer from a given .p3d file. """
     notify = directNotify.newCategory("Installer")
@@ -142,15 +166,17 @@ class Installer:
         Call this after you have set the desired parameters. """
         
         platforms = set()
-        for package in self.host.getPackages(name = "p3dembed"):
+        for package in self.standalone.host.getPackages(name = "p3dembed"):
             platforms.add(package.platform)
         if len(platforms) == 0:
             Installer.notify.error("No platforms found to build for!")
         
-        outputDir = Filename(outputDir)
-        assert outputDir.isDirectory()
+        outputDir = Filename(outputDir + "/")
+        outputDir.makeDir()
         for platform in platforms:
-            self.build(outputDir, platform)
+            output = Filename(outputDir, platform + "/")
+            output.makeDir()
+            self.build(output, platform)
     
     def build(self, output, platform = None):
         """ Builds a (graphical) installer and stores it into the path
@@ -162,21 +188,24 @@ class Installer:
             platform = PandaSystem.getPlatform()
         
         if platform == "win32":
-            return self.buildNSIS()
+            return self.buildNSIS(output, platform)
         elif "_" in platform:
-            os, arch = platform.rsplit("_", 1)
+            os, arch = platform.split("_", 1)
             if os == "linux":
-                return self.buildDEB(output, arch)
+                return self.buildDEB(output, platform)
             elif os == "osx":
-                return self.buildDMG(output, arch)
+                return self.buildPKG(output, platform)
+            elif os == "freebsd":
+                return self.buildDEB(output, platform)
         Installer.notify.info("Ignoring unknown platform " + platform)
 
-    def buildDEB(self, output, arch):
+    def buildDEB(self, output, platform):
         """ Builds a .deb archive and stores it in the path indicated
         by the 'output' argument. It will be built for the architecture
         specified by the 'arch' argument.
         If 'output' is a directory, the deb file will be stored in it. """
         
+        arch = platform.rsplit("_", 1)[-1]
         output = Filename(output)
         if output.isDirectory():
             output = Filename(output, "%s_%s_%s.deb" % (self.shortname.lower(), self.version, arch))
@@ -194,7 +223,7 @@ class Installer:
         controlfile.write("Description: %s\n" % self.fullname)
         controlfile.close()
         Filename(tempdir, "usr/bin/").makeDir()
-        self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), "linux_" + arch)
+        self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform)
         if not self.licensefile.empty():
             Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
             shutil.copyfile(self.licensefile.toOsSpecific(), Filename(tempdir, "usr/share/doc/%s/copyright" % self.shortname.lower()).toOsSpecific())
@@ -231,33 +260,22 @@ class Installer:
         debfile.close()
         shutil.rmtree(tempdir.toOsSpecific())
 
-    def __buildAPP(self, plugin_standalone):
-        pkgfn = "%s %s.pkg" % (self.shortname, self.version)
-        appname = "/Applications/%s.app" % self.longname
-        Installer.notify.info("Creating %s..." % pkgfn)
+    def buildAPP(self, output, platform):
         
-        # Create a temporary directory to hold the application in
-        tempdir = Filename.temporary("", self.shortname.lower() + "_app_", "") + "/"
-        tempdir = tempdir.toOsSpecific()
-        if os.path.exists(tempdir):
-            shutil.rmtree(tempdir)
-        os.makedirs(tempdir)
-        contents = os.path.join(tempdir, appname.lstrip("/"), "Contents")
-        os.makedirs(os.path.join(contents, "MacOS"))
-        os.makedirs(os.path.join(contents, "Resources"))
+        output = Filename(output)
+        if output.isDirectory() and output.getExtension() != 'app':
+            output = Filename(output, "%s.app" % self.fullname)
+        Installer.notify.info("Creating %s..." % output)
         
-        # Create the "launch" script used to run the game.
-        launch = open(os.path.join(contents, "MacOS", "launch"), "w")
-        print >>launch, '#!/bin/sh'
-        print >>launch, 'panda3d_mac ../Resources/%s' % self.p3dfile.getBasename()
-        launch.close()
-        shutil.copyfile(self.p3dfile.toOsSpecific(), os.path.join(contents, "Resources", self.p3dfile.getBasename()))
-        shutil.copyfile(target, os.path.join(contents, "MacOS", "panda3d_mac"))
+        # Create the executable for the application bundle
+        exefile = Filename(output, "Contents/MacOS/" + self.shortname)
+        exefile.makeDir()
+        self.standalone.build(exefile, platform)
         
         # Create the application plist file.
         # Although it might make more sense to use Python's plistlib module here,
         # it is not available on non-OSX systems before Python 2.6.
-        plist = open(os.path.join(tempdir, appname.lstrip("/"), "Contents", "Info.plist"), "w")
+        plist = open(Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
         print >>plist, '<?xml version="1.0" encoding="UTF-8"?>'
         print >>plist, '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
         print >>plist, '<plist version="1.0">'
@@ -267,7 +285,7 @@ class Installer:
         print >>plist, '\t<key>CFBundleDisplayName</key>'
         print >>plist, '\t<string>%s</string>' % self.fullname
         print >>plist, '\t<key>CFBundleExecutable</key>'
-        print >>plist, '\t<string>launch</string>'
+        print >>plist, '\t<string>%s</string>' % exefile.getBasename()
         print >>plist, '\t<key>CFBundleIdentifier</key>'
         print >>plist, '\t<string>%s.%s</string>' % (self.authorid, self.shortname)
         print >>plist, '\t<key>CFBundleInfoDictionaryVersion</key>'
@@ -290,7 +308,15 @@ class Installer:
         print >>plist, '</plist>'
         plist.close()
 
-    def buildNSIS(self):
+    def buildPKG(self, output, platform):
+        output = Filename(output)
+        #if output.isDirectory():
+        #    output = Filename(output, "%s %s.pkg" % (self.fullname, self.version))
+        
+        self.buildAPP(output, platform)
+        return
+
+    def buildNSIS(self, output, platform):
         # Check if we have makensis first
         makensis = None
         if (sys.platform.startswith("win")):
@@ -308,16 +334,26 @@ class Installer:
                     makensis = os.path.join(p, "makensis")
         
         if makensis == None:
-            Installer.notify.warning("Makensis utility not found, no Windows installer will be built!")
+            Installer.notify.error("Makensis utility not found, no Windows installer will be built!")
             return
-        Installer.notify.info("Creating %s.exe..." % self.shortname)
+        
+        output = Filename(output)
+        if output.isDirectory():
+            output = Filename(output, "%s %s.exe" % (self.fullname, self.version))
+        Installer.notify.info("Creating %s..." % output)
+        output.makeAbsolute()
+
+        exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
+        exefile.unlink()
+        self.standalone.build(exefile, platform)
 
-        tempfile = self.shortname + ".nsi"
-        nsi = open(tempfile, "w")
+        nsifile = Filename(Filename.getTempDirectory(), self.shortname + ".nsi")
+        nsifile.unlink()
+        nsi = open(nsifile.toOsSpecific(), "w")
 
         # Some global info
         nsi.write('Name "%s"\n' % self.fullname)
-        nsi.write('OutFile "%s.exe"\n' % self.shortname)
+        nsi.write('OutFile "%s"\n' % output.toOsSpecific())
         nsi.write('InstallDir "$PROGRAMFILES\%s"\n' % self.fullname)
         nsi.write('SetCompress auto\n')
         nsi.write('SetCompressor lzma\n')
@@ -329,7 +365,7 @@ class Installer:
         nsi.write('RequestExecutionLevel admin\n')
         nsi.write('\n')
         nsi.write('Function launch\n')
-        nsi.write('  ExecShell "open" "$INSTDIR\%s.bat"\n' % self.shortname)
+        nsi.write('  ExecShell "open" "$INSTDIR\%s.exe"\n' % self.shortname)
         nsi.write('FunctionEnd\n')
         nsi.write('\n')
         nsi.write('!include "MUI2.nsh"\n')
@@ -353,8 +389,11 @@ class Installer:
         nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
 
         # This section defines the installer.
-        nsi.write('Section "Install"\n')
+        nsi.write('Section "" SecCore\n')
         nsi.write('  SetOutPath "$INSTDIR"\n')
+        nsi.write('  File "%s"\n' % exefile.toOsSpecific())
+        for f in self.standalone.getExtraFiles(platform):
+            nsi.write('  File "%s"\n' % f.toOsSpecific())
         nsi.write('  WriteUninstaller "$INSTDIR\Uninstall.exe"\n')
         nsi.write('  ; Start menu items\n')
         nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
@@ -364,7 +403,10 @@ class Installer:
         nsi.write('SectionEnd\n')
 
         # This section defines the uninstaller.
-        nsi.write('Section "Uninstall"\n')
+        nsi.write('Section Uninstall\n')
+        nsi.write('  Delete "$INSTDIR\%s.exe"\n' % self.shortname)
+        for f in self.standalone.getExtraFiles(platform):
+            nsi.write('  Delete "%s"\n' % f.getBasename())
         nsi.write('  Delete "$INSTDIR\Uninstall.exe"\n')
         nsi.write('  RMDir "$INSTDIR"\n')
         nsi.write('  ; Start menu items\n')
@@ -381,7 +423,7 @@ class Installer:
                 cmd += " /" + o
             else:
                 cmd += " -" + o
-        cmd += " " + tempfile
+        cmd += " " + nsifile.toOsSpecific()
         os.system(cmd)
 
-        os.remove(tempfile)
+        nsifile.unlink()

+ 1 - 1
direct/src/p3d/coreapi.pdef

@@ -99,7 +99,7 @@ class p3dcert(package):
         file('p3dcert.exe')
 
 
-class p3dembed(solo):
+class p3dembed(package):
     # This contains the p3dembed executable, that is used by
     # pdeploy to generate a self-extracting .p3d executable.
 

+ 6 - 6
direct/src/p3d/pdeploy.py

@@ -31,7 +31,7 @@ Modes:
 Options:
 
   -n your_app
-     Short, lowercase name of the application or game. Can only
+     Short, lowercase name of the application or game. May only
      contain alphanumeric characters, underscore or dash. This
      name will also define the output file(s) of the process.
      If omitted, the basename of the p3d file is used.
@@ -94,7 +94,7 @@ DEPLOY_MODES = ["standalone", "installer", "html"]
 import sys
 import os
 import getopt
-from DeploymentTools import Standalone, Installer
+from direct.p3d.DeploymentTools import Standalone, Installer
 from pandac.PandaModules import Filename, PandaSystem
 
 def usage(code, msg = ''):
@@ -208,10 +208,10 @@ elif deploy_mode == 'installer':
         i.buildAll(outputDir)
     else:
         for platform in platforms:
-            if platform.startswith("win"):
-                i.build(Filename(outputDir, platform + "/"), platform)
-            else:
-                i.build(Filename(outputDir, platform + "/"), platform)
+            output = Filename(outputDir, platform + "/")
+            output.makeDir()
+            i.build(output, platform)
+
 elif deploy_mode == 'html':
     print "Creating %s.html..." % shortname
     html = open(shortname + ".html", "w")