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')
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
"""
usageText = """
This command will help you to distribute your Panda game, consisting
of a .p3d file, into an installable package or an HTML webpage.
This command will help you to distribute your Panda application,
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:
%s [opts] app.p3d installer|web
%(prog)s [opts] app.p3d standalone|installer|html
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
In this mode, installable packages will be created for as many
platforms as possible. To create Windows installers on
non-Windows platforms, you need to have the "makensis" utility
on your system PATH environment variable.
web
html
An HTML webpage will be generated that can be used to view
the provided p3d file in a browser.
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
Short, lowercase name of the application or game. Can only
contain alphanumeric characters, underscore or dash. This
@ -37,112 +37,182 @@ Options:
If omitted, the basename of the p3d file is used.
-N "Your Application"
Full name of the application or game. This one will be used
to display to the end-user.
Full name of the application or game. This value will
be used to display to the end-user.
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"
Specifies the name of the software license that the game
or application is licensed under.
Only relevant when generating a graphical installer.
-L licensefile.txt
This should point to a file that contains the full text
describing the software license that the game or application
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 os
import getopt
from direct.p3d import InstallerMaker
from pandac.PandaModules import Filename
from direct.p3d.DeploymentTools import Standalone, Installer
from pandac.PandaModules import Filename, PandaSystem
class ArgumentError(StandardError):
pass
def usage(code, msg = ''):
print >> sys.stderr, usageText % {'prog' : os.path.split(sys.argv[0])[1]}
print >> sys.stderr, msg
sys.exit(code)
def deployApp(args):
opts, args = getopt.getopt(args, 'l:L:n:N:v:h')
version = ""
shortname = ""
fullname = ""
licensename = ""
licensefile = Filename()
for option, value in opts:
if option == '-v':
version = value.strip()
if option == '-n':
shortname = value.strip()
elif option == '-L':
fullname = value.strip()
if option == '-l':
licensename = value
elif option == '-L':
licensefile = Filename.fromOsSpecific(value)
elif option == '-h':
print __doc__ % (os.path.split(sys.argv[0])[1])
sys.exit(1)
shortname = ""
fullname = ""
version = ""
outputDir = Filename("./")
tokens = {}
platforms = []
currentPlatform = False
licensename = ""
licensefile = Filename()
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))
try:
opts, args = getopt.getopt(sys.argv[1:], 'n:N:v:o:t:P:cl:L:h')
except getopt.error, msg:
usage(1, msg)
if len(args) > 2:
raise ArgumentError, "Too many arguments."
appFilename = Filename.fromOsSpecific(args[0])
if appFilename.getExtension().lower() != 'p3d':
raise ArgumentError, 'Application filename must end in ".p3d".'
deploy_mode = args[1].lower()
if deploy_mode not in DEPLOY_MODES:
raise ArgumentError, 'Invalid deploy type, must be one of "%s".' % '", "'.join(DEPLOY_MODES)
if shortname.lower() != shortname or ' ' in shortname:
raise ArgumentError, 'Provided short name should be lowercase, and may not contain spaces!'
if shortname == '':
shortname = appFilename.getBasenameWoExtension()
if fullname == '':
fullname = shortname
if version == '' and deploy_mode == 'installer':
raise ArgumentError, 'A version number is required in "installer" mode!'
try:
if deploy_mode == 'installer':
im = InstallerMaker.InstallerMaker(shortname, fullname, appFilename, version)
im.licensename = licensename
im.licensefile = licensefile
im.build()
elif deploy_mode == 'web':
print "Creating %s.html..." % shortname
html = open(shortname + ".html", "w")
html.write("<html>\n")
html.write(" <head>\n")
html.write(" <title>%s</title>\n" % fullname)
html.write(" </head>\n")
html.write(" <body>\n")
html.write(" <object data=\"%s\" type=\"application/x-panda3d\"></object>\n" % appFilename.getBasename())
html.write(" </body>\n")
html.write("</html>\n")
html.close()
for opt, arg in opts:
if opt == '-n':
shortname = arg.strip()
elif opt == '-N':
fullname = arg.strip()
elif opt == '-v':
version = arg.strip()
elif opt == '-o':
outputDir = Filename.fromOsSpecific(arg)
elif opt == '-t':
token = arg.strip().split("=", 1)
tokens[token[0]] = token[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)
except: raise
#except InstallerMaker.InstallerMakerError:
# # Just print the error message and exit gracefully.
# 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]
elif opt == '-h':
usage(0)
else:
print 'illegal option: ' + flag
sys.exit(1)
if not args or len(args) != 2:
usage(1)
appFilename = Filename.fromOsSpecific(args[0])
if appFilename.getExtension().lower() != 'p3d':
print 'Application filename must end in ".p3d".'
sys.exit(1)
deploy_mode = args[1].lower()
if not appFilename.exists():
print 'Application filename does not exist!'
sys.exit(1)
if shortname.lower() != shortname or ' ' in shortname:
print '\nProvided short name should be lowercase, and may not contain spaces!\n'
if shortname == '':
shortname = appFilename.getBasenameWoExtension()
if fullname == '':
fullname = shortname
if version == '' and deploy_mode == 'installer':
print '\nA version number is required in "installer" mode.\n'
sys.exit(1)
if not outputDir:
print '\nYou must name the output directory with the -o parameter.\n'
sys.exit(1)
if deploy_mode == 'standalone':
s = Standalone(appFilename, tokens)
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
html = open(shortname + ".html", "w")
html.write("<html>\n")
html.write(" <head>\n")
html.write(" <title>%s</title>\n" % fullname)
html.write(" </head>\n")
html.write(" <body>\n")
html.write(" <object data=\"%s\" type=\"application/x-panda3d\"></object>\n" % appFilename.getBasename())
html.write(" </body>\n")
html.write("</html>\n")
html.close()
else:
usage(1, 'Invalid deployment mode!')
# An explicit call to exit() is required to exit the program, when
# this module is packaged in a p3d file.
sys.exit(0)