mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 10:22:45 -04:00
pdeploy is working for standalone executables now
This commit is contained in:
parent
2b0acc9fcc
commit
b9821f1df8
399
direct/src/p3d/DeploymentTools.py
Normal file
399
direct/src/p3d/DeploymentTools.py
Normal 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)
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user