mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-28 07:48:37 -04:00
bdist_apps: Support user-defined installers
Custom installers can be added by specifying a build function in bdist_apps options: 'installer_functions': { 'custom': custom_func } Installer functions must accept three arguments: * command - the bdist_apps setuptools command function (can be used to gather build information) * basename - name of the output file (minus extension) * build_dir - path to the directory containing the built application
This commit is contained in:
parent
2461fc0917
commit
d015b7e2f1
191
direct/src/dist/commands.py
vendored
191
direct/src/dist/commands.py
vendored
@ -24,6 +24,7 @@ import distutils.log
|
||||
|
||||
from . import FreezeTool
|
||||
from . import pefile
|
||||
from . import installers
|
||||
from .icon import Icon
|
||||
import panda3d.core as p3d
|
||||
|
||||
@ -1313,6 +1314,14 @@ class bdist_apps(setuptools.Command):
|
||||
# Everything else defaults to ['zip']
|
||||
}
|
||||
|
||||
DEFAULT_INSTALLER_FUNCS = {
|
||||
'zip': installers.create_zip,
|
||||
'gztar': installers.create_gztar,
|
||||
'bztar': installers.create_bztar,
|
||||
'xztar': installers.create_xztar,
|
||||
'nsis': installers.create_nsis,
|
||||
}
|
||||
|
||||
description = 'bundle built Panda3D applications into distributable forms'
|
||||
user_options = build_apps.user_options + [
|
||||
('dist-dir=', 'd', 'directory to put final built distributions in'),
|
||||
@ -1326,6 +1335,8 @@ class bdist_apps(setuptools.Command):
|
||||
self.installers = {}
|
||||
self.dist_dir = os.path.join(os.getcwd(), 'dist')
|
||||
self.skip_build = False
|
||||
self.installer_functions = {}
|
||||
self._current_platform = None
|
||||
for opt in self._build_apps_options():
|
||||
setattr(self, opt, None)
|
||||
|
||||
@ -1337,145 +1348,15 @@ class bdist_apps(setuptools.Command):
|
||||
for key, value in _parse_dict(self.installers).items()
|
||||
}
|
||||
|
||||
def _get_archive_basedir(self):
|
||||
tmp = self.DEFAULT_INSTALLER_FUNCS.copy()
|
||||
tmp.update(self.installer_functions)
|
||||
self.installer_functions = tmp
|
||||
|
||||
def get_archive_basedir(self):
|
||||
return self.distribution.get_name()
|
||||
|
||||
def create_zip(self, basename, build_dir):
|
||||
import zipfile
|
||||
|
||||
base_dir = self._get_archive_basedir()
|
||||
|
||||
with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.write(build_dir, base_dir)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(build_dir):
|
||||
for name in sorted(dirnames):
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
zf.write(path, path.replace(build_dir, base_dir, 1))
|
||||
for name in filenames:
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
if os.path.isfile(path):
|
||||
zf.write(path, path.replace(build_dir, base_dir, 1))
|
||||
|
||||
def create_tarball(self, basename, build_dir, tar_compression):
|
||||
import tarfile
|
||||
|
||||
base_dir = self._get_archive_basedir()
|
||||
build_cmd = self.get_finalized_command('build_apps')
|
||||
binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
|
||||
|
||||
def tarfilter(tarinfo):
|
||||
if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
|
||||
tarinfo.mode = 0o755
|
||||
else:
|
||||
tarinfo.mode = 0o644
|
||||
return tarinfo
|
||||
|
||||
with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
|
||||
tf.add(build_dir, base_dir, filter=tarfilter)
|
||||
|
||||
def create_nsis(self, basename, build_dir, is_64bit):
|
||||
# Get a list of build applications
|
||||
build_cmd = self.get_finalized_command('build_apps')
|
||||
apps = build_cmd.gui_apps.copy()
|
||||
apps.update(build_cmd.console_apps)
|
||||
apps = [
|
||||
'{}.exe'.format(i)
|
||||
for i in apps
|
||||
]
|
||||
|
||||
shortname = self.distribution.get_name()
|
||||
|
||||
# Create the .nsi installer script
|
||||
nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
|
||||
nsifile.unlink()
|
||||
nsi = open(nsifile.to_os_specific(), "w")
|
||||
|
||||
# Some global info
|
||||
nsi.write('Name "%s"\n' % shortname)
|
||||
nsi.write('OutFile "%s"\n' % os.path.join(self.dist_dir, basename+'.exe'))
|
||||
if is_64bit:
|
||||
nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
|
||||
else:
|
||||
nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
|
||||
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')
|
||||
|
||||
# TODO offer run and desktop shortcut after we figure out how to deal
|
||||
# with multiple apps
|
||||
|
||||
nsi.write('!include "MUI2.nsh"\n')
|
||||
nsi.write('!define MUI_ABORTWARNING\n')
|
||||
nsi.write('\n')
|
||||
nsi.write('Var StartMenuFolder\n')
|
||||
nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
|
||||
# TODO license file
|
||||
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 "" SecCore\n')
|
||||
nsi.write(' SetOutPath "$INSTDIR"\n')
|
||||
curdir = ""
|
||||
nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
|
||||
build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
|
||||
for root, dirs, files in os.walk(build_dir):
|
||||
for name in files:
|
||||
basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
|
||||
file = p3d.Filename(basefile)
|
||||
file.makeAbsolute()
|
||||
file.makeRelativeTo(nsi_dir)
|
||||
outdir = p3d.Filename(basefile)
|
||||
outdir.makeAbsolute()
|
||||
outdir.makeRelativeTo(build_root_dir)
|
||||
outdir = outdir.getDirname().replace('/', '\\')
|
||||
if curdir != outdir:
|
||||
nsi.write(' SetOutPath "$INSTDIR\\%s"\n' % outdir)
|
||||
curdir = outdir
|
||||
nsi.write(' File "%s"\n' % (file.toOsSpecific()))
|
||||
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')
|
||||
for app in apps:
|
||||
nsi.write(' CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
|
||||
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(' RMDir /r "$INSTDIR"\n')
|
||||
nsi.write(' ; Desktop icon\n')
|
||||
nsi.write(' Delete "$DESKTOP\\%s.lnk"\n' % shortname)
|
||||
nsi.write(' ; Start menu items\n')
|
||||
nsi.write(' !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
|
||||
nsi.write(' RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
|
||||
nsi.write('SectionEnd\n')
|
||||
nsi.close()
|
||||
|
||||
cmd = ['makensis']
|
||||
for flag in ["V2"]:
|
||||
cmd.append(
|
||||
'{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
|
||||
)
|
||||
cmd.append(nsifile.to_os_specific())
|
||||
subprocess.check_call(cmd)
|
||||
def get_current_platform(self):
|
||||
return self._current_platform
|
||||
|
||||
def run(self):
|
||||
build_cmd = self.distribution.get_command_obj('build_apps')
|
||||
@ -1497,35 +1378,15 @@ class bdist_apps(setuptools.Command):
|
||||
build_dir = os.path.join(build_base, platform)
|
||||
basename = '{}_{}'.format(self.distribution.get_fullname(), platform)
|
||||
installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip']))
|
||||
self._current_platform = platform
|
||||
|
||||
for installer in installers:
|
||||
self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO)
|
||||
if installer not in self.installer_functions:
|
||||
self.announce(
|
||||
'\tUnknown installer: {}'.format(installer),
|
||||
distutils.log.ERROR
|
||||
)
|
||||
continue
|
||||
|
||||
if installer == 'zip':
|
||||
self.create_zip(basename, build_dir)
|
||||
elif installer in ('gztar', 'bztar', 'xztar'):
|
||||
compress = installer.replace('tar', '')
|
||||
if compress == 'bz':
|
||||
compress = 'bz2'
|
||||
|
||||
self.create_tarball(basename, build_dir, compress)
|
||||
elif installer == 'nsis':
|
||||
if not platform.startswith('win'):
|
||||
self.announce(
|
||||
'\tNSIS installer not supported for platform: {}'.format(platform),
|
||||
distutils.log.ERROR
|
||||
)
|
||||
continue
|
||||
try:
|
||||
subprocess.call(['makensis', '--version'])
|
||||
except OSError:
|
||||
self.announce(
|
||||
'\tCould not find makensis tool that is required to build NSIS installers',
|
||||
distutils.log.ERROR
|
||||
)
|
||||
# continue
|
||||
is_64bit = platform == 'win_amd64'
|
||||
self.create_nsis(basename, build_dir, is_64bit)
|
||||
|
||||
else:
|
||||
self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR)
|
||||
self.installer_functions[installer](self, basename, build_dir)
|
||||
|
173
direct/src/dist/installers.py
vendored
Normal file
173
direct/src/dist/installers.py
vendored
Normal file
@ -0,0 +1,173 @@
|
||||
import distutils.log
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import zipfile
|
||||
|
||||
import panda3d.core as p3d
|
||||
|
||||
def create_zip(command, basename, build_dir):
|
||||
base_dir = command.get_archive_basedir()
|
||||
|
||||
with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.write(build_dir, base_dir)
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(build_dir):
|
||||
for name in sorted(dirnames):
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
zf.write(path, path.replace(build_dir, base_dir, 1))
|
||||
for name in filenames:
|
||||
path = os.path.normpath(os.path.join(dirpath, name))
|
||||
if os.path.isfile(path):
|
||||
zf.write(path, path.replace(build_dir, base_dir, 1))
|
||||
|
||||
|
||||
def create_tarball(command, basename, build_dir, tar_compression):
|
||||
base_dir = command.get_archive_basedir()
|
||||
build_cmd = command.get_finalized_command('build_apps')
|
||||
binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
|
||||
|
||||
def tarfilter(tarinfo):
|
||||
if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
|
||||
tarinfo.mode = 0o755
|
||||
else:
|
||||
tarinfo.mode = 0o644
|
||||
return tarinfo
|
||||
|
||||
with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
|
||||
tf.add(build_dir, base_dir, filter=tarfilter)
|
||||
|
||||
|
||||
def create_gztar(command, basename, build_dir):
|
||||
return create_tarball(command, basename, build_dir, 'gz')
|
||||
|
||||
|
||||
def create_bztar(command, basename, build_dir):
|
||||
return create_tarball(command, basename, build_dir, 'bz2')
|
||||
|
||||
|
||||
def create_xztar(command, basename, build_dir):
|
||||
return create_tarball(command, basename, build_dir, 'xz')
|
||||
|
||||
|
||||
def create_nsis(command, basename, build_dir):
|
||||
platform = command.get_current_platform()
|
||||
if not platform.startswith('win'):
|
||||
command.announce(
|
||||
'\tNSIS installer not supported for platform: {}'.format(platform),
|
||||
distutils.log.ERROR
|
||||
)
|
||||
return
|
||||
try:
|
||||
subprocess.call(['makensis', '--version'])
|
||||
except OSError:
|
||||
command.announce(
|
||||
'\tCould not find makensis tool that is required to build NSIS installers',
|
||||
distutils.log.ERROR
|
||||
)
|
||||
return
|
||||
|
||||
is_64bit = platform == 'win_amd64'
|
||||
# Get a list of build applications
|
||||
build_cmd = command.get_finalized_command('build_apps')
|
||||
apps = build_cmd.gui_apps.copy()
|
||||
apps.update(build_cmd.console_apps)
|
||||
apps = [
|
||||
'{}.exe'.format(i)
|
||||
for i in apps
|
||||
]
|
||||
|
||||
shortname = command.distribution.get_name()
|
||||
|
||||
# Create the .nsi installer script
|
||||
nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
|
||||
nsifile.unlink()
|
||||
nsi = open(nsifile.to_os_specific(), "w")
|
||||
|
||||
# Some global info
|
||||
nsi.write('Name "%s"\n' % shortname)
|
||||
nsi.write('OutFile "%s"\n' % os.path.join(command.dist_dir, basename+'.exe'))
|
||||
if is_64bit:
|
||||
nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
|
||||
else:
|
||||
nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
|
||||
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')
|
||||
|
||||
# TODO offer run and desktop shortcut after we figure out how to deal
|
||||
# with multiple apps
|
||||
|
||||
nsi.write('!include "MUI2.nsh"\n')
|
||||
nsi.write('!define MUI_ABORTWARNING\n')
|
||||
nsi.write('\n')
|
||||
nsi.write('Var StartMenuFolder\n')
|
||||
nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
|
||||
# TODO license file
|
||||
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 "" SecCore\n')
|
||||
nsi.write(' SetOutPath "$INSTDIR"\n')
|
||||
curdir = ""
|
||||
nsi_dir = p3d.Filename.fromOsSpecific(build_cmd.build_base)
|
||||
build_root_dir = p3d.Filename.fromOsSpecific(build_dir)
|
||||
for root, dirs, files in os.walk(build_dir):
|
||||
for name in files:
|
||||
basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
|
||||
file = p3d.Filename(basefile)
|
||||
file.makeAbsolute()
|
||||
file.makeRelativeTo(nsi_dir)
|
||||
outdir = p3d.Filename(basefile)
|
||||
outdir.makeAbsolute()
|
||||
outdir.makeRelativeTo(build_root_dir)
|
||||
outdir = outdir.getDirname().replace('/', '\\')
|
||||
if curdir != outdir:
|
||||
nsi.write(' SetOutPath "$INSTDIR\\%s"\n' % outdir)
|
||||
curdir = outdir
|
||||
nsi.write(' File "%s"\n' % (file.toOsSpecific()))
|
||||
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')
|
||||
for app in apps:
|
||||
nsi.write(' CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
|
||||
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(' RMDir /r "$INSTDIR"\n')
|
||||
nsi.write(' ; Desktop icon\n')
|
||||
nsi.write(' Delete "$DESKTOP\\%s.lnk"\n' % shortname)
|
||||
nsi.write(' ; Start menu items\n')
|
||||
nsi.write(' !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
|
||||
nsi.write(' RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
|
||||
nsi.write('SectionEnd\n')
|
||||
nsi.close()
|
||||
|
||||
cmd = ['makensis']
|
||||
for flag in ["V2"]:
|
||||
cmd.append(
|
||||
'{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
|
||||
)
|
||||
cmd.append(nsifile.to_os_specific())
|
||||
subprocess.check_call(cmd)
|
||||
|
Loading…
x
Reference in New Issue
Block a user