Browse Source

Installer package creation for OSX

rdb 16 years ago
parent
commit
80196acac8
1 changed files with 190 additions and 110 deletions
  1. 190 110
      direct/src/plugin_installer/make_installer.py

+ 190 - 110
direct/src/plugin_installer/make_installer.py

@@ -2,13 +2,15 @@
 
 
 import os
 import os
 import sys
 import sys
+import shutil
+import platform
 from optparse import OptionParser
 from optparse import OptionParser
 import subprocess
 import subprocess
 import direct
 import direct
 from pandac.PandaModules import *
 from pandac.PandaModules import *
 
 
 usage = """
 usage = """
-This command invokes NSIS to construct a Windows installer for the
+This command creates a graphical installer for the
 Panda3D plugin and runtime environment.
 Panda3D plugin and runtime environment.
 
 
   %prog [opts]"""
   %prog [opts]"""
@@ -20,11 +22,14 @@ parser.add_option('-n', '--short', dest = 'short_name',
 parser.add_option('-N', '--long', dest = 'long_name',
 parser.add_option('-N', '--long', dest = 'long_name',
                   help = 'The product long name',
                   help = 'The product long name',
                   default = 'Panda3D Game Engine')
                   default = 'Panda3D Game Engine')
+parser.add_option('-v', '--version', dest = 'version',
+                  help = 'The product version',
+                  default = PandaSystem.getVersionString())
 parser.add_option('-p', '--publisher', dest = 'publisher',
 parser.add_option('-p', '--publisher', dest = 'publisher',
                   help = 'The name of the publisher',
                   help = 'The name of the publisher',
                   default = 'Carnegie Mellon Entertainment Technology Center')
                   default = 'Carnegie Mellon Entertainment Technology Center')
 parser.add_option('-i', '--install', dest = 'install_dir',
 parser.add_option('-i', '--install', dest = 'install_dir',
-                  help = "The install directory on the user's machine",
+                  help = "The install directory on the user's machine (Windows-only)",
                   default = '$PROGRAMFILES\\Panda3D')
                   default = '$PROGRAMFILES\\Panda3D')
 parser.add_option('-l', '--license', dest = 'license',
 parser.add_option('-l', '--license', dest = 'license',
                   help = 'A file containing the license or EULA text',
                   help = 'A file containing the license or EULA text',
@@ -35,15 +40,15 @@ parser.add_option('-w', '--website', dest = 'website',
 parser.add_option('', '--start', dest = 'start',
 parser.add_option('', '--start', dest = 'start',
                   help = 'Specify this option to add a start menu',
                   help = 'Specify this option to add a start menu',
                   action = 'store_false', default = False)
                   action = 'store_false', default = False)
-parser.add_option('', '--nsis', dest = 'nsis',
-                  help = 'The directory containing NSIS',
-                  default = None)
 parser.add_option('', '--welcome_image', dest = 'welcome_image',
 parser.add_option('', '--welcome_image', dest = 'welcome_image',
                   help = 'The image to display on the installer',
                   help = 'The image to display on the installer',
                   default = None)
                   default = None)
 parser.add_option('', '--install_icon', dest = 'install_icon',
 parser.add_option('', '--install_icon', dest = 'install_icon',
                   help = 'The icon to give to the installer',
                   help = 'The icon to give to the installer',
                   default = None)
                   default = None)
+parser.add_option('', '--nsis', dest = 'nsis',
+                  help = 'The directory containing NSIS',
+                  default = None)
 
 
 (options, args) = parser.parse_args()
 (options, args) = parser.parse_args()
 
 
@@ -51,34 +56,34 @@ this_dir = os.path.split(sys.argv[0])[0]
 
 
 ##############################################################################
 ##############################################################################
 #
 #
-# Locate the relevant trees.
+# Locate the relevant files.
 #
 #
 ##############################################################################
 ##############################################################################
 
 
-if not options.nsis or not options.license:
-    PANDA=None
-    for dir in sys.path:
-        if (dir != "") and os.path.exists(os.path.join(dir,"direct")) and os.path.exists(os.path.join(dir,"pandac")):
-            PANDA=os.path.abspath(dir)
-    if (PANDA is None):
-      sys.exit("Cannot locate the panda root directory in the python path (cannot locate directory containing direct and pandac).")
-    print "PANDA located at "+PANDA
-
-    if (os.path.exists(os.path.join(PANDA,"..","makepanda","makepanda.py"))) and (sys.platform != "win32" or os.path.exists(os.path.join(PANDA,"..","thirdparty","win-nsis","makensis.exe"))):
-      PSOURCE=os.path.abspath(os.path.join(PANDA,".."))
-      if (sys.platform == "win32"):
-        NSIS=os.path.abspath(os.path.join(PANDA,"..","thirdparty","win-nsis"))
+if not options.nsis:
+    if sys.platform == "win32":
+        makensis = None
+        for p in os.defpath.split(";") + os.environ["PATH"].split(";"):
+            if os.path.isfile(os.path.join(p, "makensis.exe")):
+                makensis = os.path.join(p, "makensis.exe")
+        if not makensis:
+            import pandac
+            makensis = os.path.dirname(os.path.dirname(pandac.__file__))
+            makensis = os.path.join(makensis, "nsis", "makensis.exe")
+            if not os.path.isfile(makensis):
+                makensis = None
+        options.nsis = makensis
     else:
     else:
-      PSOURCE=PANDA
-      if (sys.platform == "win32"):
-        NSIS=os.path.join(PANDA,"nsis")
+        for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
+            if os.path.isfile(os.path.join(p, "makensis")):
+                options.nsis = os.path.join(p, "makensis")
 
 
-    if not options.nsis:
-        options.nsis = NSIS
-    if not options.license:
-        options.license = os.path.join(PANDA, 'LICENSE')
+if not options.license:
+    import pandac
+    options.license = os.path.join(os.path.dirname(os.path.dirname(pandac.__file__)), "LICENSE")
+    if not os.path.isfile(options.license): options.license = None
 
 
-if not options.welcome_image:
+if sys.platform == "win32" and not options.welcome_image:
     filename = Filename('plugin_images/download.png')
     filename = Filename('plugin_images/download.png')
     found = filename.resolveFilename(getModelPath().getValue())
     found = filename.resolveFilename(getModelPath().getValue())
     if not found:
     if not found:
@@ -125,13 +130,30 @@ def parseDependenciesWindows(tempFile):
     # At least we got some data.
     # At least we got some data.
     return filenames
     return filenames
 
 
+def parseDependenciesUnix(tempFile):
+    """ Reads the indicated temporary file, the output from
+    otool -XL or ldd, to determine the list of dll's this
+    executable file depends on. """
+    
+    lines = open(tempFile.toOsSpecific(), 'rU').readlines()
+    filenames = []
+    for l in lines:
+        filenames.append(l.strip().split(' ', 1)[0])
+    return filenames
+
 def addDependencies(path, pathname, file, pluginDependencies, dependentFiles):
 def addDependencies(path, pathname, file, pluginDependencies, dependentFiles):
     """ Checks the named file for DLL dependencies, and adds any
     """ Checks the named file for DLL dependencies, and adds any
     appropriate dependencies found into pluginDependencies and
     appropriate dependencies found into pluginDependencies and
     dependentFiles. """
     dependentFiles. """
     
     
     tempFile = Filename.temporary('', 'p3d_', '.txt')
     tempFile = Filename.temporary('', 'p3d_', '.txt')
-    command = 'dumpbin /dependents "%s" >"%s"' % (
+    if sys.platform == "darwin":
+        command = 'otool -XL "%s" >"%s"'
+    elif sys.platform == "win32":
+        command = 'dumpbin /dependents "%s" >"%s"'
+    else:
+        command = 'ldd "%s" >"%s"'
+    command = command % (
         pathname.toOsSpecific(),
         pathname.toOsSpecific(),
         tempFile.toOsSpecific())
         tempFile.toOsSpecific())
     try:
     try:
@@ -141,7 +163,10 @@ def addDependencies(path, pathname, file, pluginDependencies, dependentFiles):
     filenames = None
     filenames = None
 
 
     if tempFile.exists():
     if tempFile.exists():
-        filenames = parseDependenciesWindows(tempFile)
+        if sys.platform == "win32":
+            filenames = parseDependenciesWindows(tempFile)
+        else:
+            filenames = parseDependenciesUnix(tempFile)
         tempFile.unlink()
         tempFile.unlink()
     if filenames is None:
     if filenames is None:
         sys.exit("Unable to determine dependencies from %s" % (pathname))
         sys.exit("Unable to determine dependencies from %s" % (pathname))
@@ -175,16 +200,28 @@ def makeInstaller():
     pluginDependencies = {}
     pluginDependencies = {}
     dependentFiles = {}
     dependentFiles = {}
 
 
-    # These are the four primary files that make up the
-    # plugin/runtime.
-    ocx = 'p3dactivex.ocx'
-    npapi = 'nppanda3d.dll'
-    panda3d = 'panda3d.exe'
-    panda3dw = 'panda3dw.exe'
+    # These are the primary files that make
+    # up the plugin/runtime.
+    if sys.platform == "darwin":
+        npapi = 'nppanda3d.plugin'
+        panda3d = 'panda3d'
+        baseFiles = [npapi, panda3d]
+    else:
+        ocx = 'p3dactivex.ocx'
+        npapi = 'nppanda3d.dll'
+        panda3d = 'panda3d.exe'
+        panda3dw = 'panda3dw.exe'
+        baseFiles = [ocx, npapi, panda3d, panda3dw]
 
 
     path = DSearchPath()
     path = DSearchPath()
-    path.appendPath(os.environ['PATH'])
-    for file in [ocx, npapi, panda3d, panda3dw]:
+    if 'PATH' in os.environ:
+        path.appendPath(os.environ['PATH'])
+    if sys.platform != "win32" and 'LD_LIBRARY_PATH' in os.environ:
+        path.appendPath(os.environ['LD_LIBRARY_PATH'])
+    if sys.platform == "darwin" and 'DYLD_LIBRARY_PATH' in os.environ:
+        path.appendPath(os.environ['DYLD_LIBRARY_PATH'])
+    path.appendPath(os.defpath)
+    for file in baseFiles:
         pathname = path.findFile(file)
         pathname = path.findFile(file)
         if not pathname:
         if not pathname:
             sys.exit("Couldn't find %s." % (file))
             sys.exit("Couldn't find %s." % (file))
@@ -192,78 +229,121 @@ def makeInstaller():
         pluginFiles[file] = pathname.toOsSpecific()
         pluginFiles[file] = pathname.toOsSpecific()
         pluginDependencies[file] = []
         pluginDependencies[file] = []
 
 
-        # Also look for the dll's that these plugins reference.
-        addDependencies(path, pathname, file, pluginDependencies, dependentFiles)
-
-    welcomeBitmap = None
-    if options.welcome_image:
-        # Convert the image from its current format to a bmp file, for NSIS.
-        p = PNMImage()
-        if not p.read(options.welcome_image):
-            sys.exit("Couldn't read %s" % (options.welcome_image))
-
-        # We also must scale it to fit within 170x312, the space
-        # allotted within the installer window.
-        size = (170, 312)
-        xscale = float(size[0]) / p.getXSize()
-        yscale = float(size[1]) / p.getYSize()
-        scale = min(xscale, yscale, 1)
-        resized = PNMImage((int)(scale * p.getXSize() + 0.5),
-                           (int)(scale * p.getYSize() + 0.5))
-        resized.quickFilterFrom(p)
-
-        # Now center it within the full window.
-        result = PNMImage(size[0], size[1])
-        result.fill(1, 1, 1)
-        xc = (size[0] - resized.getXSize()) / 2
-        yc = (size[1] - resized.getYSize()) / 2
-        result.copySubImage(resized, xc, yc)
-
-        welcomeBitmap = Filename.temporary('', 'p3d_', '.bmp')
-        result.write(welcomeBitmap)
-
-    # Now build the NSIS command.
-    CMD = "\"" + options.nsis + "\\makensis.exe\" /V3 "
-    CMD += '/DPRODUCT_NAME="' + options.long_name + '" '
-    CMD += '/DPRODUCT_NAME_SHORT="' + options.short_name + '" '
-    CMD += '/DPRODUCT_PUBLISHER="' + options.publisher + '" '
-    CMD += '/DPRODUCT_WEB_SITE="' + options.website + '" '
-    CMD += '/DINSTALL_DIR="' + options.install_dir + '" '
-    CMD += '/DLICENSE_FILE="' + options.license + '" '
-    CMD += '/DOCX="' + ocx + '" '
-    CMD += '/DOCX_PATH="' + pluginFiles[ocx] + '" '
-    CMD += '/DNPAPI="' + npapi + '" '
-    CMD += '/DNPAPI_PATH="' + pluginFiles[npapi] + '" '
-    CMD += '/DPANDA3D="' + panda3d + '" '
-    CMD += '/DPANDA3D_PATH="' + pluginFiles[panda3d] + '" '
-    CMD += '/DPANDA3DW="' + panda3dw + '" '
-    CMD += '/DPANDA3DW_PATH="' + pluginFiles[panda3dw] + '" '
-
-    dependencies = dependentFiles.items()
-    for i in range(len(dependencies)):
-        CMD += '/DDEP%s="%s" ' % (i, dependencies[i][0])
-        CMD += '/DDEP%sP="%s" ' % (i, dependencies[i][1])
-    dependencies = pluginDependencies[npapi]
-    for i in range(len(dependencies)):
-        CMD += '/DNPAPI_DEP%s="%s" ' % (i, dependencies[i])
-
-    if options.start:
-        CMD += '/DADD_START_MENU '
-    
-    if welcomeBitmap:
-        CMD += '/DMUI_WELCOMEFINISHPAGE_BITMAP="' + welcomeBitmap.toOsSpecific() + '" '
-        CMD += '/DMUI_UNWELCOMEFINISHPAGE_BITMAP="' + welcomeBitmap.toOsSpecific() + '" '
-    if options.install_icon:
-        CMD += '/DINSTALL_ICON="' + options.install_icon + '" '
-
-    CMD += '"' + this_dir + '\\p3d_installer.nsi"' 
-    
-    print ""
-    print CMD
-    print "packing..."
-    subprocess.call(CMD)
-
-    if welcomeBitmap:
-        welcomeBitmap.unlink()
+        if sys.platform == "win32":
+            # Also look for the dll's that these plugins reference.
+            addDependencies(path, pathname, file, pluginDependencies, dependentFiles)
+
+    if sys.platform == "darwin":
+        tmproot = Filename("/var/tmp/Panda3D Runtime/")
+        if tmproot.exists():
+            shutil.rmtree(tmproot.toOsSpecific())
+        tmproot.makeDir()
+        dst_npapi = Filename(tmproot, Filename("Library/Internet Plug-Ins", npapi))
+        dst_panda3d = Filename(tmproot, Filename("usr/bin", panda3d))
+        dst_npapi.makeDir()
+        dst_panda3d.makeDir()
+        shutil.copytree(pluginFiles[npapi], dst_npapi.toOsSpecific())
+        shutil.copyfile(pluginFiles[panda3d], dst_panda3d.toOsSpecific())
+        CMD = "/Developer/usr/bin/packagemaker"
+        CMD += ' --id org.panda3d.runtime' #TODO: make this customizable
+        CMD += ' --version "%s"' % options.version
+        CMD += ' --title "%s"' % options.long_name
+        CMD += ' --out p3d-setup.pkg'
+        CMD += ' --target %s' % platform.mac_ver()[0][:4]
+        CMD += ' --domain system'
+        CMD += ' --root "%s"' % tmproot.toOsSpecific()
+        
+        print ""
+        print CMD
+        subprocess.call(CMD, shell = True)
+        shutil.rmtree(tmproot.toOsSpecific())
+        
+        # Pack the .pkg into a .dmg
+        tmproot.makeDir()
+        shutil.copyfile("p3d-setup.pkg", Filename(tmproot, "p3d-setup.pkg").toOsSpecific())
+        tmpdmg = Filename.temporary("", "p3d-setup", "").toOsSpecific() + ".dmg"
+        CMD = 'hdiutil create "%s" -srcfolder "%s"' % (tmpdmg, tmproot)
+        print ""
+        print CMD
+        subprocess.call(CMD, shell = True)
+        shutil.rmtree(tmproot.toOsSpecific())
+        
+        # Compress the .dmg (and make it read-only)
+        CMD = 'hdiutil convert "%s" -format UDBZ -o "p3d-setup.dmg"' % tmpdmg
+        print ""
+        print CMD
+        subprocess.call(CMD, shell = True)
+        
+    else:
+        welcomeBitmap = None
+        if options.welcome_image:
+            # Convert the image from its current format to a bmp file, for NSIS.
+            p = PNMImage()
+            if not p.read(options.welcome_image):
+                sys.exit("Couldn't read %s" % (options.welcome_image))
+
+            # We also must scale it to fit within 170x312, the space
+            # allotted within the installer window.
+            size = (170, 312)
+            xscale = float(size[0]) / p.getXSize()
+            yscale = float(size[1]) / p.getYSize()
+            scale = min(xscale, yscale, 1)
+            resized = PNMImage((int)(scale * p.getXSize() + 0.5),
+                               (int)(scale * p.getYSize() + 0.5))
+            resized.quickFilterFrom(p)
+
+            # Now center it within the full window.
+            result = PNMImage(size[0], size[1])
+            result.fill(1, 1, 1)
+            xc = (size[0] - resized.getXSize()) / 2
+            yc = (size[1] - resized.getYSize()) / 2
+            result.copySubImage(resized, xc, yc)
+
+            welcomeBitmap = Filename.temporary('', 'p3d_', '.bmp')
+            result.write(welcomeBitmap)
+
+        # Now build the NSIS command.
+        CMD = "\"" + options.nsis + "\\makensis.exe\" /V3 "
+        CMD += '/DPRODUCT_NAME="' + options.long_name + '" '
+        CMD += '/DPRODUCT_NAME_SHORT="' + options.short_name + '" '
+        CMD += '/DPRODUCT_PUBLISHER="' + options.publisher + '" '
+        CMD += '/DPRODUCT_WEB_SITE="' + options.website + '" '
+        CMD += '/DINSTALL_DIR="' + options.install_dir + '" '
+        CMD += '/DLICENSE_FILE="' + options.license + '" '
+        CMD += '/DOCX="' + ocx + '" '
+        CMD += '/DOCX_PATH="' + pluginFiles[ocx] + '" '
+        CMD += '/DNPAPI="' + npapi + '" '
+        CMD += '/DNPAPI_PATH="' + pluginFiles[npapi] + '" '
+        CMD += '/DPANDA3D="' + panda3d + '" '
+        CMD += '/DPANDA3D_PATH="' + pluginFiles[panda3d] + '" '
+        CMD += '/DPANDA3DW="' + panda3dw + '" '
+        CMD += '/DPANDA3DW_PATH="' + pluginFiles[panda3dw] + '" '
+
+        dependencies = dependentFiles.items()
+        for i in range(len(dependencies)):
+            CMD += '/DDEP%s="%s" ' % (i, dependencies[i][0])
+            CMD += '/DDEP%sP="%s" ' % (i, dependencies[i][1])
+        dependencies = pluginDependencies[npapi]
+        for i in range(len(dependencies)):
+            CMD += '/DNPAPI_DEP%s="%s" ' % (i, dependencies[i])
+
+        if options.start:
+            CMD += '/DADD_START_MENU '
+        
+        if welcomeBitmap:
+            CMD += '/DMUI_WELCOMEFINISHPAGE_BITMAP="' + welcomeBitmap.toOsSpecific() + '" '
+            CMD += '/DMUI_UNWELCOMEFINISHPAGE_BITMAP="' + welcomeBitmap.toOsSpecific() + '" '
+        if options.install_icon:
+            CMD += '/DINSTALL_ICON="' + options.install_icon + '" '
+
+        CMD += '"' + this_dir + '\\p3d_installer.nsi"' 
+        
+        print ""
+        print CMD
+        print "packing..."
+        subprocess.call(CMD)
+
+        if welcomeBitmap:
+            welcomeBitmap.unlink()
 
 
 makeInstaller()
 makeInstaller()