pdeploy is working for standalone executables now

This commit is contained in:
rdb 2009-12-23 10:35:04 +00:00
parent 2b0acc9fcc
commit b9821f1df8
4 changed files with 578 additions and 286 deletions

View File

@ -0,0 +1,399 @@
""" This module is used to build a graphical installer
or a standalone executable from a p3d file. It will try
to build for as many platforms as possible. """
__all__ = ["Standalone", "Installer"]
import os, sys, subprocess, tarfile, shutil, time
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
class CachedFile:
def __init__(self): self.str = ""
def write(self, data): self.str += data
# Make sure this matches with the magic in p3dEmbed.cxx.
P3DEMBED_MAGIC = "\xFF\x3D\x3D\x00"
class Standalone:
""" This class creates a standalone executable from a given .p3d file. """
notify = directNotify.newCategory("Standalone")
def __init__(self, p3dfile, tokens = {}):
if isinstance(p3dfile, Filename):
self.p3dfile = p3dfile
else:
self.p3dfile = Filename(p3dfile)
self.basename = self.p3dfile.getBasenameWoExtension()
self.tokens = tokens
if appRunner:
self.host = appRunner.getHost("http://runtime.panda3d.org")
else:
hostDir = Filename(Filename.getTempDirectory(), 'pdeploy/')
hostDir.makeDir()
self.host = HostInfo("http://runtime.panda3d.org", hostDir = hostDir, asMirror = True)
self.http = HTTPClient.getGlobalPtr()
if not self.host.downloadContentsFile(self.http):
Standalone.notify.error("couldn't read host")
return False
def buildAll(self, outputDir = "."):
""" Builds standalone executables for every known platform,
into the specified output directory. """
platforms = set()
for package in self.host.getPackages(name = "p3dembed"):
platforms.add(package.platform)
if len(platforms) == 0:
Standalone.notify.error("No platforms found to build for!")
for platform in platforms:
if platform.startswith("win"):
self.build(Filename(outputDir, platform + "/" + self.basename + ".exe"), platform)
else:
self.build(Filename(outputDir, platform + "/" + self.basename), platform)
def build(self, output, platform = None):
""" Builds a standalone executable and stores it into the path
indicated by the 'output' argument. You can specify to build for
other platforms by altering the 'platform' argument. """
if platform == None:
platform = PandaSystem.getPlatform()
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
# Figure out where p3dembed might be now.
if package.platform.startswith("win"):
p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed.exe" % package.platform)
else:
p3dembed = Filename(self.host.hostDir, "p3dembed/%s/p3dembed" % package.platform)
# We allow p3dembed to be pzipped.
if Filename(p3dembed + ".pz").exists():
p3dembed = Filename(p3dembed + ".pz")
if not p3dembed.exists():
Standalone.notify.error(" -> %s failed for platform %s" % (package.packageName, package.platform))
continue
self.embed(output, p3dembed)
return
Standalone.notify.error("Failed to build standalone for platform %s" % platform)
def embed(self, output, p3dembed):
""" Embeds the p3d file into the provided p3dembed executable.
This function is not really useful - use build() or buildAll() instead. """
# Load the p3dembed data into memory
size = p3dembed.getFileSize()
p3dembed_data = VirtualFileSystem.getGlobalPtr().readFile(p3dembed, True)
assert len(p3dembed_data) == size
# Find the magic size string and replace it with the real size,
# regardless of the endianness of the p3dembed executable.
hex_size = hex(size)[2:].rjust(8, "0")
enc_size = "".join([chr(int(hex_size[i] + hex_size[i + 1], 16)) for i in range(0, len(hex_size), 2)])
p3dembed_data = p3dembed_data.replace(P3DEMBED_MAGIC, enc_size)
p3dembed_data = p3dembed_data.replace(P3DEMBED_MAGIC[::-1], enc_size[::-1])
# Write the output file
Standalone.notify.info("Creating %s..." % output)
output.makeDir()
ohandle = open(output.toOsSpecific(), "wb")
ohandle.write(p3dembed_data)
for token in self.tokens.items():
ohandle.write("\0%s=%s" % token)
ohandle.write("\0\0")
# Buffer the p3d file to the output file. 1 MB buffer size.
phandle = open(self.p3dfile.toOsSpecific(), "rb")
buf = phandle.read(1024 * 1024)
while len(buf) != 0:
ohandle.write(buf)
buf = phandle.read(1024 * 1024)
ohandle.close()
phandle.close()
os.chmod(output.toOsSpecific(), 0755)
class Installer:
""" This class creates a (graphical) installer from a given .p3d file. """
notify = directNotify.newCategory("Installer")
def __init__(self, shortname, fullname, p3dfile, version):
self.shortname = shortname
self.fullname = fullname
self.version = str(version)
self.licensename = ""
self.authorid = "org.panda3d"
self.authorname = ""
self.licensefile = Filename()
self.builder = StandaloneBuilder(p3dfile)
def buildAll(self):
""" Creates a (graphical) installer for every known platform.
Call this after you have set the desired parameters. """
# Download the 'p3dembed' package
tempdir = Filename.temporary("", self.shortname + "_p3d_", "") + "/"
tempdir.makeDir()
host = HostInfo("http://runtime.panda3d.org", hostDir = tempdir, asMirror = True)
tempdir = tempdir.toOsSpecific()
http = HTTPClient.getGlobalPtr()
if not host.downloadContentsFile(http):
Installer.notify.error("couldn't read host")
return False
for package in host.getPackages(name = "p3dembed"):
print package.packageName, package.packageVersion, package.platform
if not package.downloadDescFile(http):
Installer.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
continue
if not package.downloadPackage(http):
Installer.notify.warning(" -> %s failed for platform %s" % (package.packageName, package.platform))
continue
if package.platform.startswith("linux_"):
plugin_standalone = os.path.join(tempdir, "plugin_standalone", package.platform, "panda3d")
assert os.path.isfile(plugin_standalone)
self.__buildDEB(plugin_standalone, arch = package.platform.replace("linux_", ""))
elif package.platform.startswith("win"):
plugin_standalone = os.path.join(tempdir, "plugin_standalone", package.platform, "panda3d.exe")
assert os.path.isfile(plugin_standalone)
self.__buildNSIS(plugin_standalone)
elif package.platform == "darwin":
plugin_standalone = os.path.join(tempdir, "plugin_standalone", package.platform, "panda3d_mac")
assert os.path.isfile(plugin_standalone)
self.__buildAPP(plugin_standalone)
else:
Installer.notify.info("Ignoring unknown platform " + package.platform)
#shutil.rmtree(tempdir)
return True
def __buildDEB(self, plugin_standalone, arch = "all"):
debfn = "%s_%s_all.deb" % (self.shortname.lower(), self.version)
Installer.notify.info("Creating %s..." % debfn)
# Create a temporary directory and write the control file + launcher to it
tempdir = Filename.temporary("", self.shortname.lower() + "_deb_", "") + "/"
tempdir.makeDir()
tempdir = tempdir.toOsSpecific()
controlfile = open(os.path.join(tempdir, "control"), "w")
controlfile.write("Package: %s\n" % self.shortname.lower())
controlfile.write("Version: %s\n" % self.version)
controlfile.write("Section: games\n")
controlfile.write("Priority: optional\n")
controlfile.write("Architecture: %s\n" % arch)
controlfile.write("Description: %s\n" % self.fullname)
controlfile.close()
os.makedirs(os.path.join(tempdir, "usr", "bin"))
os.makedirs(os.path.join(tempdir, "usr", "share", "games", self.shortname.lower()))
os.makedirs(os.path.join(tempdir, "usr", "libexec", self.shortname.lower()))
if not self.licensefile.empty():
os.makedirs(os.path.join(tempdir, "usr", "share", "doc", self.shortname.lower()))
launcherfile = open(os.path.join(tempdir, "usr", "bin", self.shortname.lower()), "w")
launcherfile.write("#!/bin/sh\n")
launcherfile.write("/usr/libexec/%s/panda3d /usr/share/games/%s/%s.p3d\n" % ((self.shortname.lower(),) * 3))
launcherfile.close()
os.chmod(os.path.join(tempdir, "usr", "bin", self.shortname.lower()), 0755)
shutil.copyfile(self.p3dfile.toOsSpecific(), os.path.join(tempdir, "usr", "share", "games", self.shortname.lower(), self.shortname.lower() + ".p3d"))
shutil.copyfile(plugin_standalone, os.path.join(tempdir, "usr", "libexec", self.shortname.lower(), "panda3d"))
if not self.licensefile.empty():
shutil.copyfile(self.licensefile.toOsSpecific(), os.path.join(tempdir, "usr", "share", "doc", self.shortname.lower(), "copyright"))
# Create a control.tar.gz file in memory
controltargz = CachedFile()
controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
controltarfile.add(os.path.join(tempdir, "control"), "control")
controltarfile.close()
os.remove(os.path.join(tempdir, "control"))
# Create the data.tar.gz file in the temporary directory
datatargz = CachedFile()
datatarfile = tarfile.TarFile.gzopen("data.tar.gz", "w", datatargz, 9)
datatarfile.add(tempdir + "/usr", "/usr")
datatarfile.close()
# Open the deb file and write to it. It's actually
# just an AR file, which is very easy to make.
modtime = int(time.time())
if os.path.isfile(debfn):
os.remove(debfn)
debfile = open(debfn, "wb")
debfile.write("!<arch>\x0A")
debfile.write("debian-binary %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, 4))
debfile.write("2.0\x0A")
debfile.write("control.tar.gz %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, len(controltargz.str)))
debfile.write(controltargz.str)
if (len(controltargz.str) & 1): debfile.write("\x0A")
debfile.write("data.tar.gz %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, len(datatargz.str)))
debfile.write(datatargz.str)
if (len(datatargz.str) & 1): debfile.write("\x0A")
debfile.close()
shutil.rmtree(tempdir)
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)
# 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"))
# 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 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")
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">'
print >>plist, '<dict>'
print >>plist, '\t<key>CFBundleDevelopmentRegion</key>'
print >>plist, '\t<string>English</string>'
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<key>CFBundleIdentifier</key>'
print >>plist, '\t<string>%s.%s</string>' % (self.authorid, self.shortname)
print >>plist, '\t<key>CFBundleInfoDictionaryVersion</key>'
print >>plist, '\t<string>6.0</string>'
print >>plist, '\t<key>CFBundleName</key>'
print >>plist, '\t<string>%s</string>' % self.shortname
print >>plist, '\t<key>CFBundlePackageType</key>'
print >>plist, '\t<string>APPL</string>'
print >>plist, '\t<key>CFBundleShortVersionString</key>'
print >>plist, '\t<string>%s</string>' % self.version
print >>plist, '\t<key>CFBundleVersion</key>'
print >>plist, '\t<string>%s</string>' % self.version
print >>plist, '\t<key>LSHasLocalizedDisplayName</key>'
print >>plist, '\t<false/>'
print >>plist, '\t<key>NSAppleScriptEnabled</key>'
print >>plist, '\t<false/>'
print >>plist, '\t<key>NSPrincipalClass</key>'
print >>plist, '\t<string>NSApplication</string>'
print >>plist, '</dict>'
print >>plist, '</plist>'
plist.close()
def __buildNSIS(self):
# Check if we have makensis first
makensis = None
if (sys.platform.startswith("win")):
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
else:
for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
if os.path.isfile(os.path.join(p, "makensis")):
makensis = os.path.join(p, "makensis")
if makensis == None:
Installer.notify.warning("Makensis utility not found, no Windows installer will be built!")
return
Installer.notify.info("Creating %s.exe..." % self.shortname)
tempfile = self.shortname + ".nsi"
nsi = open(tempfile, "w")
# Some global info
nsi.write('Name "%s"\n' % self.fullname)
nsi.write('OutFile "%s.exe"\n' % self.shortname)
nsi.write('InstallDir "$PROGRAMFILES\%s"\n' % self.fullname)
nsi.write('SetCompress auto\n')
nsi.write('SetCompressor lzma\n')
nsi.write('ShowInstDetails nevershow\n')
nsi.write('ShowUninstDetails nevershow\n')
nsi.write('InstType "Typical"\n')
# Tell Vista that we require admin rights
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('FunctionEnd\n')
nsi.write('\n')
nsi.write('!include "MUI2.nsh"\n')
nsi.write('!define MUI_ABORTWARNING\n')
nsi.write('!define MUI_FINISHPAGE_RUN\n')
nsi.write('!define MUI_FINISHPAGE_RUN_FUNCTION launch\n')
nsi.write('!define MUI_FINISHPAGE_RUN_TEXT "Play %s"\n' % self.fullname)
nsi.write('\n')
nsi.write('Var StartMenuFolder\n')
nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
if not self.licensefile.empty():
nsi.write('!insertmacro MUI_PAGE_LICENSE "%s"\n' % self.licensefile.toOsSpecific())
nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
nsi.write('!insertmacro MUI_PAGE_FINISH\n')
nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
# This section defines the installer.
nsi.write('Section "Install"\n')
nsi.write(' SetOutPath "$INSTDIR"\n')
nsi.write(' WriteUninstaller "$INSTDIR\Uninstall.exe"\n')
nsi.write(' ; Start menu items\n')
nsi.write(' !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
nsi.write(' CreateDirectory "$SMPROGRAMS\$StartMenuFolder"\n')
nsi.write(' CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe"\n')
nsi.write(' !insertmacro MUI_STARTMENU_WRITE_END\n')
nsi.write('SectionEnd\n')
# This section defines the uninstaller.
nsi.write('Section "Uninstall"\n')
nsi.write(' Delete "$INSTDIR\Uninstall.exe"\n')
nsi.write(' RMDir "$INSTDIR"\n')
nsi.write(' ; Start menu items\n')
nsi.write(' !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
nsi.write(' Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk"\n')
nsi.write(' RMDir "$SMPROGRAMS\$StartMenuFolder"\n')
nsi.write('SectionEnd')
nsi.close()
options = ["V2"]
cmd = makensis
for o in options:
if sys.platform.startswith("win"):
cmd += " /" + o
else:
cmd += " -" + o
cmd += " " + tempfile
os.system(cmd)
os.remove(tempfile)

View File

@ -1,189 +0,0 @@
""" This module is used to build a graphical installer
from a p3d file. It will try to build installers for as
many platforms as possible. """
__all__ = ["InstallerMaker"]
import os, sys, subprocess, tarfile, shutil, time
from direct.directnotify.DirectNotifyGlobal import *
from pandac.PandaModules import Filename
class CachedFile:
def __init__(self): self.str = ""
def write(self, data): self.str += data
class InstallerMaker:
notify = directNotify.newCategory("InstallerMaker")
def __init__(self, shortname, fullname, p3dfile, version):
self.shortname = shortname
self.fullname = fullname
self.version = str(version)
self.licensename = ""
# All paths given must be a Filename instance!
assert isinstance(p3dfile, Filename)
self.p3dfile = p3dfile
self.licensefile = Filename()
def build(self):
""" Creates the installer. Call this after you have set all the parameters. """
self.__buildDEB()
self.__buildNSIS()
def __buildDEB(self):
debfn = "%s_%s_all.deb" % (self.shortname, self.version)
InstallerMaker.notify.info("Creating %s..." % debfn)
# Create a temporary directory and write the control file + launcher to it
tempdir = Filename.temporary("", self.shortname + "_deb_", "") + "/"
tempdir.makeDir()
tempdir = tempdir.toOsSpecific()
controlfile = open(os.path.join(tempdir, "control"), "w")
controlfile.write("Package: %s\n" % self.shortname)
controlfile.write("Version: %s\n" % self.version)
controlfile.write("Section: games\n")
controlfile.write("Priority: optional\n")
controlfile.write("Architecture: all\n")
controlfile.write("Depends: panda3d-runtime\n")
controlfile.write("Description: %s\n" % self.fullname)
controlfile.close()
os.makedirs(os.path.join(tempdir, "usr", "bin"))
os.makedirs(os.path.join(tempdir, "usr", "share", "games", self.shortname))
if not self.licensefile.empty():
os.makedirs(os.path.join(tempdir, "usr", "share", "doc", self.shortname))
launcherfile = open(os.path.join(tempdir, "usr", "bin", self.shortname), "w")
launcherfile.write("#!/bin/sh\n")
launcherfile.write("/usr/bin/env panda3d /usr/share/games/%s/data.p3d\n" % self.shortname)
launcherfile.close()
os.chmod(os.path.join(tempdir, "usr", "bin", self.shortname), 0755)
shutil.copyfile(self.p3dfile.toOsSpecific(), os.path.join(tempdir, "usr", "share", "games", self.shortname, "data.p3d"))
if not self.licensefile.empty():
shutil.copyfile(self.licensefile.toOsSpecific(), os.path.join(tempdir, "usr", "share", "doc", self.shortname, "copyright"))
# Create a control.tar.gz file in memory
controltargz = CachedFile()
controltarfile = tarfile.TarFile.gzopen("control.tar.gz", "w", controltargz, 9)
controltarfile.add(os.path.join(tempdir, "control"), "control")
controltarfile.close()
os.remove(os.path.join(tempdir, "control"))
# Create the data.tar.gz file in the temporary directory
datatargz = CachedFile()
datatarfile = tarfile.TarFile.gzopen("data.tar.gz", "w", datatargz, 9)
datatarfile.add(tempdir + "/usr", "/usr")
datatarfile.close()
# Open the deb file and write to it. It's actually
# just an AR file, which is very easy to make.
modtime = int(time.time())
if os.path.isfile(debfn):
os.remove(debfn)
debfile = open(debfn, "wb")
debfile.write("!<arch>\x0A")
debfile.write("debian-binary %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, 4))
debfile.write("2.0\x0A")
debfile.write("control.tar.gz %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, len(controltargz.str)))
debfile.write(controltargz.str)
if (len(controltargz.str) & 1): debfile.write("\x0A")
debfile.write("data.tar.gz %-12lu0 0 100644 %-10ld\x60\x0A" % (modtime, len(datatargz.str)))
debfile.write(datatargz.str)
if (len(datatargz.str) & 1): debfile.write("\x0A")
debfile.close()
shutil.rmtree(tempdir)
def __buildNSIS(self):
# Check if we have makensis first
makensis = None
if (sys.platform.startswith("win")):
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
else:
for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
if os.path.isfile(os.path.join(p, "makensis")):
makensis = os.path.join(p, "makensis")
if makensis == None:
InstallerMaker.notify.warning("Makensis utility not found, no Windows installer will be built!")
return
InstallerMaker.notify.info("Creating %s.exe..." % self.shortname)
tempfile = self.shortname + ".nsi"
nsi = open(tempfile, "w")
# Some global info
nsi.write('Name "%s"\n' % self.fullname)
nsi.write('OutFile "%s.exe"\n' % self.shortname)
nsi.write('InstallDir "$PROGRAMFILES\%s"\n' % self.fullname)
nsi.write('SetCompress auto\n')
nsi.write('SetCompressor lzma\n')
nsi.write('ShowInstDetails nevershow\n')
nsi.write('ShowUninstDetails nevershow\n')
nsi.write('InstType "Typical"\n')
# Tell Vista that we require admin rights
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('FunctionEnd\n')
nsi.write('\n')
nsi.write('!include "MUI2.nsh"\n')
nsi.write('!define MUI_ABORTWARNING\n')
nsi.write('!define MUI_FINISHPAGE_RUN\n')
nsi.write('!define MUI_FINISHPAGE_RUN_FUNCTION launch\n')
nsi.write('!define MUI_FINISHPAGE_RUN_TEXT "Play %s"\n' % self.fullname)
nsi.write('\n')
nsi.write('Var StartMenuFolder\n')
nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
if not self.licensefile.empty():
nsi.write('!insertmacro MUI_PAGE_LICENSE "%s"\n' % self.licensefile.toOsSpecific())
nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
nsi.write('!insertmacro MUI_PAGE_FINISH\n')
nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
# This section defines the installer.
nsi.write('Section "Install"\n')
nsi.write(' SetOutPath "$INSTDIR"\n')
nsi.write(' WriteUninstaller "$INSTDIR\Uninstall.exe"\n')
nsi.write(' ; Start menu items\n')
nsi.write(' !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
nsi.write(' CreateDirectory "$SMPROGRAMS\$StartMenuFolder"\n')
nsi.write(' CreateShortCut "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk" "$INSTDIR\Uninstall.exe"\n')
nsi.write(' !insertmacro MUI_STARTMENU_WRITE_END\n')
nsi.write('SectionEnd\n')
# This section defines the uninstaller.
nsi.write('Section "Uninstall"\n')
nsi.write(' Delete "$INSTDIR\Uninstall.exe"\n')
nsi.write(' RMDir "$INSTDIR"\n')
nsi.write(' ; Start menu items\n')
nsi.write(' !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
nsi.write(' Delete "$SMPROGRAMS\$StartMenuFolder\Uninstall.lnk"\n')
nsi.write(' RMDir "$SMPROGRAMS\$StartMenuFolder"\n')
nsi.write('SectionEnd')
nsi.close()
options = ["V2"]
cmd = makensis
for o in options:
if sys.platform.startswith("win"):
cmd += " /" + o
else:
cmd += " -" + o
cmd += " " + tempfile
os.system(cmd)
os.remove(tempfile)

View File

@ -320,3 +320,15 @@ class pmerge(p3d):
require('panda3d') require('panda3d')
mainModule('direct.p3d.pmerge') mainModule('direct.p3d.pmerge')
class pdeploy(p3d):
# This utility can distribute a game in the form of
# a standalone executable or a graphical installer.
config(display_name = "Panda3D Deployment Tool",
hidden = True, platform_specific = False,
keep_user_env = True)
require('panda3d')
mainModule('direct.p3d.pdeploy')

View File

@ -1,35 +1,35 @@
#! /usr/bin/env python #! /usr/bin/env python
""" usageText = """
This command will help you to distribute your Panda game, consisting This command will help you to distribute your Panda application,
of a .p3d file, into an installable package or an HTML webpage. consisting of a .p3d package, into a standalone executable, graphical
installer or an HTML webpage. It will attempt to create packages
for every platform, if possible.
Usage: Usage:
%s [opts] app.p3d installer|web %(prog)s [opts] app.p3d standalone|installer|html
Modes: Modes:
standalone
A standalone executable will be created that embeds the given
p3d file. The resulting executable will require an
internet connection in order to run properly.
installer installer
In this mode, installable packages will be created for as many In this mode, installable packages will be created for as many
platforms as possible. To create Windows installers on platforms as possible. To create Windows installers on
non-Windows platforms, you need to have the "makensis" utility non-Windows platforms, you need to have the "makensis" utility
on your system PATH environment variable. on your system PATH environment variable.
web html
An HTML webpage will be generated that can be used to view An HTML webpage will be generated that can be used to view
the provided p3d file in a browser. the provided p3d file in a browser.
Options: Options:
-v version_number
This should define the version number of your application
or game. In some deploy modes, this argument is required.
This should only contain alphanumeric characters, dots and
dashes, as the result of the deployment may be in valid
on some platforms otherwise.
-n your_app -n your_app
Short, lowercase name of the application or game. Can only Short, lowercase name of the application or game. Can only
contain alphanumeric characters, underscore or dash. This contain alphanumeric characters, underscore or dash. This
@ -37,89 +37,168 @@ Options:
If omitted, the basename of the p3d file is used. If omitted, the basename of the p3d file is used.
-N "Your Application" -N "Your Application"
Full name of the application or game. This one will be used Full name of the application or game. This value will
to display to the end-user. be used to display to the end-user.
If omitted, the short name is used. If omitted, the short name is used.
-v version_number
This should define the version number of your application
or game. In some deploy modes, this argument is required.
This should only contain alphanumeric characters, dots and
dashes, as otherwise the result of the deployment may be
invalid on some platforms.
-o output_dir
Indicates the directory where the output will be stored.
Within this directory, subdirectories will be created
for every platform, unless -t is provided.
If omitted, the current working directory is assumed.
-t token=value
Defines a web token or parameter to pass to the application.
Use this to configure how the application will be run.
You can pass as many -t options as you need. Examples of
tokens are width, height, log_basename, auto_start, hidden.
-P platform
If this option is provided, it should specify a comma-
separated list of platforms that the p3d package will be
deployed for. If omitted, it will be built for all platforms.
This option may be specified multiple times.
Examples of valid platforms are win32, linux_amd64 and osx_ppc.
-c
If this option is provided, the -p option is ignored and
the p3d package is only deployed for the current platform.
Furthermore, no per-platform subdirectories will be created
inside the output dirctory.
-l "License Name" -l "License Name"
Specifies the name of the software license that the game Specifies the name of the software license that the game
or application is licensed under. or application is licensed under.
Only relevant when generating a graphical installer.
-L licensefile.txt -L licensefile.txt
This should point to a file that contains the full text This should point to a file that contains the full text
describing the software license that the game or application describing the software license that the game or application
is licensed under. is licensed under.
Only relevant when generating a graphical installer.
-h
Display this help
""" """
DEPLOY_MODES = ["installer", "web"] DEPLOY_MODES = ["standalone", "installer", "html"]
import sys import sys
import os import os
import getopt import getopt
from direct.p3d import InstallerMaker from direct.p3d.DeploymentTools import Standalone, Installer
from pandac.PandaModules import Filename from pandac.PandaModules import Filename, PandaSystem
class ArgumentError(StandardError): def usage(code, msg = ''):
pass print >> sys.stderr, usageText % {'prog' : os.path.split(sys.argv[0])[1]}
print >> sys.stderr, msg
sys.exit(code)
def deployApp(args): shortname = ""
opts, args = getopt.getopt(args, 'l:L:n:N:v:h') fullname = ""
version = ""
outputDir = Filename("./")
tokens = {}
platforms = []
currentPlatform = False
licensename = ""
licensefile = Filename()
version = "" try:
shortname = "" opts, args = getopt.getopt(sys.argv[1:], 'n:N:v:o:t:P:cl:L:h')
fullname = "" except getopt.error, msg:
licensename = "" usage(1, msg)
licensefile = Filename()
for option, value in opts: for opt, arg in opts:
if option == '-v': if opt == '-n':
version = value.strip() shortname = arg.strip()
if option == '-n': elif opt == '-N':
shortname = value.strip() fullname = arg.strip()
elif option == '-L': elif opt == '-v':
fullname = value.strip() version = arg.strip()
if option == '-l': elif opt == '-o':
licensename = value outputDir = Filename.fromOsSpecific(arg)
elif option == '-L': elif opt == '-t':
licensefile = Filename.fromOsSpecific(value) token = arg.strip().split("=", 1)
elif option == '-h': tokens[token[0]] = token[1]
print __doc__ % (os.path.split(sys.argv[0])[1]) elif opt == '-P':
platforms.append(arg)
elif opt == '-c':
currentPlatform = True
elif opt == '-l':
licensename = arg.strip()
elif opt == '-L':
licensefile = Filename.fromOsSpecific(arg)
elif opt == '-h':
usage(0)
else:
print 'illegal option: ' + flag
sys.exit(1) sys.exit(1)
if not args or len(args) < 2: if not args or len(args) != 2:
raise ArgumentError, "No target app and/or deploy type specified. Use:\n%s app.p3d %s" % (os.path.split(sys.argv[0])[1], '|'.join(DEPLOY_MODES)) usage(1)
if len(args) > 2: appFilename = Filename.fromOsSpecific(args[0])
raise ArgumentError, "Too many arguments." if appFilename.getExtension().lower() != 'p3d':
print 'Application filename must end in ".p3d".'
sys.exit(1)
deploy_mode = args[1].lower()
appFilename = Filename.fromOsSpecific(args[0]) if not appFilename.exists():
if appFilename.getExtension().lower() != 'p3d': print 'Application filename does not exist!'
raise ArgumentError, 'Application filename must end in ".p3d".' sys.exit(1)
deploy_mode = args[1].lower() if shortname.lower() != shortname or ' ' in shortname:
if deploy_mode not in DEPLOY_MODES: print '\nProvided short name should be lowercase, and may not contain spaces!\n'
raise ArgumentError, 'Invalid deploy type, must be one of "%s".' % '", "'.join(DEPLOY_MODES)
if shortname.lower() != shortname or ' ' in shortname: if shortname == '':
raise ArgumentError, 'Provided short name should be lowercase, and may not contain spaces!'
if shortname == '':
shortname = appFilename.getBasenameWoExtension() shortname = appFilename.getBasenameWoExtension()
if fullname == '': if fullname == '':
fullname = shortname fullname = shortname
if version == '' and deploy_mode == 'installer': if version == '' and deploy_mode == 'installer':
raise ArgumentError, 'A version number is required in "installer" mode!' print '\nA version number is required in "installer" mode.\n'
sys.exit(1)
try: if not outputDir:
if deploy_mode == 'installer': print '\nYou must name the output directory with the -o parameter.\n'
im = InstallerMaker.InstallerMaker(shortname, fullname, appFilename, version) sys.exit(1)
im.licensename = licensename
im.licensefile = licensefile if deploy_mode == 'standalone':
im.build() s = Standalone(appFilename, tokens)
elif deploy_mode == 'web': s.basename = shortname
if currentPlatform:
platform = PandaSystem.getPlatform()
if platform.startswith("win"):
s.build(Filename(outputDir, shortname + ".exe"), platform)
else:
s.build(Filename(outputDir, shortname), platform)
elif len(platforms) == 0:
s.buildAll(outputDir)
else:
for platform in platforms:
if platform.startswith("win"):
s.build(Filename(outputDir, platform + "/" + shortname + ".exe"), platform)
else:
s.build(Filename(outputDir, platform + "/" + shortname), platform)
elif deploy_mode == 'installer':
i = Installer(shortname, fullname, appFilename, version)
i.licensename = licensename
i.licensefile = licensefile
i.build()
elif deploy_mode == 'html':
print "Creating %s.html..." % shortname print "Creating %s.html..." % shortname
html = open(shortname + ".html", "w") html = open(shortname + ".html", "w")
html.write("<html>\n") html.write("<html>\n")
@ -131,18 +210,9 @@ def deployApp(args):
html.write(" </body>\n") html.write(" </body>\n")
html.write("</html>\n") html.write("</html>\n")
html.close() html.close()
else:
usage(1, 'Invalid deployment mode!')
except: raise # An explicit call to exit() is required to exit the program, when
#except InstallerMaker.InstallerMakerError: # this module is packaged in a p3d file.
# # Just print the error message and exit gracefully. sys.exit(0)
# inst = sys.exc_info()[1]
# print inst.args[0]
# sys.exit(1)
if __name__ == '__main__':
try:
deployApp(sys.argv[1:])
except ArgumentError, e:
print e.args[0]
sys.exit(1)