Commit d72aa879 authored by talljosh's avatar talljosh

Added script to build a stand-alone Trosnoth bundle that runs on linux (including Trosnoth server).

parent d9f1fed8b503
#!/usr/bin/python3
from os import mkdir, rename, symlink
from pathlib import Path
from importlib.util import spec_from_file_location, module_from_spec
from subprocess import run
here = Path(__file__).parent.absolute()
dist = here / 'dist'
# Get the Trosnoth version. This used to be one line in Python 2.x.
version_module = module_from_spec(
spec_from_file_location('trosnoth.version', here / 'trosnoth' / 'version.py'))
version_module.__spec__.loader.exec_module(version_module)
trosnoth_version = version_module.version
# Build the bundle
run(['pyinstaller', here / 'everything.spec'])
# Create a nice directory structure with only the scripts and a lib folder
target_folder = dist / 'trosnoth-{}'.format(trosnoth_version)
mkdir(target_folder)
rename(dist / 'trosnoth', target_folder / 'lib')
symlink('lib/trosnothapp', target_folder / 'trosnoth')
symlink('lib/trosnoth-server', target_folder / 'trosnoth-server')
symlink('lib/manage-db', target_folder / 'manage-db')
# Create the .tar.gz file
run(
['tar', 'cvzf', 'trosnoth-linux-{}.tar.gz'.format(trosnoth_version), target_folder.name],
cwd=dist)
# -*- mode: python -*-
import glob
import importlib.util
import os
import sys
from PyInstaller.utils.hooks import (
collect_submodules, get_module_file_attribute,
)
def set_django_environment():
import django
sys.path.insert(0, SPECPATH)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'trosnoth.server.settings')
django.setup()
def load_py_file(name, filename):
spec = importlib.util.spec_from_file_location(name, filename)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def django_import_finder():
set_django_environment()
from django.apps import apps
from django.conf import settings
from django.urls.resolvers import URLPattern, RegexPattern
from trosnoth.server import urls
hiddenimports = [settings.ROOT_URLCONF]
for app_config in apps.get_app_configs():
hiddenimports.append(app_config.module.__name__)
if app_config.models_module:
hiddenimports.append(app_config.models_module.__name__)
# Templates is a dict:
for templ in settings.TEMPLATES:
# Include context_processors.
# Context processors are functions - strip last word.
mods = templ['OPTIONS']['context_processors']
mods = ['.'.join(class_name.split('.')[:-1]) for class_name in mods]
hiddenimports += mods
# Include database backends - it is a dict.
for v in settings.DATABASES.values():
hiddenimports.append(v['ENGINE'])
def find_url_callbacks(urls_module):
if isinstance(urls_module, list):
urlpatterns = urls_module
hid_list = []
else:
urlpatterns = urls_module.urlpatterns
hid_list = [urls_module.__name__]
for pattern in urlpatterns:
if isinstance(pattern, (RegexPattern, URLPattern)):
hid_list.append(pattern.callback.__module__)
return hid_list
# Add templatetags and context processors for each installed app.
# for app in settings.INSTALLED_APPS:
# app_templatetag_module = app + '.templatetags'
# app_ctx_proc_module = app + '.context_processors'
# hiddenimports.append(app_templatetag_module)
# hiddenimports += collect_submodules(app_templatetag_module)
# hiddenimports.append(app_ctx_proc_module)
# Find url imports.
hiddenimports += find_url_callbacks(urls)
# Deduplicate imports.
hiddenimports = list(set(hiddenimports))
return hiddenimports
def get_django_bits():
# The Django hook shipped with PyInstaller doesn't support Django
# version 2.x yet, and also doesn't work so nicely with something
# non-standard like Trosnoth's use of Django.
hiddenimports = [
# Django config files
'trosnoth.server.settings',
'trosnoth.server.urls',
'trosnoth.server.wsgi',
# Django hiddenimports from the standard Python library.
'http.cookies',
'html.parser',
]
# Include imports and submodules from trosnoth.server.settings
for req in django_import_finder():
hiddenimports.append(req)
hiddenimports += collect_submodules(req)
# Bundle django DB schema migration scripts as data files.
# They are necessary for some commands.
from django.apps import apps
installed_apps = [
app_config.module.__name__ for app_config in apps.get_app_configs()]
migration_modules = [
'django.conf.app_template.migrations',
'django.contrib.admin.migrations',
'django.contrib.auth.migrations',
'django.contrib.contenttypes.migrations',
'django.contrib.flatpages.migrations',
'django.contrib.redirects.migrations',
'django.contrib.sessions.migrations',
'django.contrib.sites.migrations',
] + list(set(app + '.migrations' for app in installed_apps))
# Copy migration files.
datas = []
for mod in migration_modules:
mod_name, bundle_name = mod.split('.', 1)
mod_dir = os.path.dirname(get_module_file_attribute(mod_name))
bundle_dir = bundle_name.replace('.', os.sep)
pattern = os.path.join(mod_dir, bundle_dir, '*.py')
files = glob.glob(pattern)
for f in files:
datas.append((f, os.path.join(mod_name, bundle_dir)))
# Include templates and d
cwd = os.getcwd()
os.chdir(os.path.abspath(SPECPATH))
for path in glob.glob('trosnoth/djangoapp/templates/**', recursive=True):
datas.append((path, os.path.dirname(path)))
for path in glob.glob('trosnoth/djangoapp/static/**', recursive=True):
datas.append((path, os.path.dirname(path)))
os.chdir(cwd)
return hiddenimports, datas
def build_exe(script_name, datas=(), hiddenimports=(), excludes=(), exe_name=None, console=True):
a = Analysis(
['scripts/' + script_name],
pathex=[base_dir],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
runtime_hooks=[],
excludes=['numpy', 'PIL', 'sqlparse', 'matplotlib'] + list(excludes),
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name=exe_name or script_name,
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=console,
icon='wininstall/icon.ico',
)
return [exe, a.binaries, a.zipfiles, a.datas]
django_imports, django_data = get_django_bits()
# ----------------------------
block_cipher = None
base_dir = os.path.abspath('.')
bot_imports = [
'trosnoth.bots.balance',
'trosnoth.bots.john',
'trosnoth.bots.puppet',
'trosnoth.bots.ranger',
'trosnoth.bots.sirrobin',
'trosnoth.bots.terminator',
]
bits = build_exe(
'trosnoth-server',
datas=[
('trosnoth/data/blocks/*.block', 'trosnoth/data/blocks'),
('trosnoth/data/blocks/*.trosblock', 'trosnoth/data/blocks'),
('trosnoth/data/config/*.cfg', 'trosnoth/data/config'),
('trosnoth/data/statGeneration/*.htm', 'trosnoth/data/statGeneration'),
('trosnoth/data/achievements/*.png', 'trosnoth/data/achievements'),
('trosnoth/data/web/*.png', 'trosnoth/data/web'),
('trosnoth/data/*.db', 'trosnoth/data'),
('trosnoth/gpl.txt', 'trosnoth'),
] + django_data,
hiddenimports=django_imports,
)
bits.extend(build_exe(
'support',
hiddenimports=bot_imports + django_imports,
))
bits.extend(build_exe(
'manage-db',
hiddenimports=django_imports,
))
bits.extend(build_exe(
'trosnoth',
# Avoid name collision with trosnoth folder
exe_name='trosnoth' if os.name == 'nt' else 'trosnothapp',
datas=[
('trosnoth/data/blocks/*.block', 'trosnoth/data/blocks'),
('trosnoth/data/blocks/*.trosblock', 'trosnoth/data/blocks'),
('trosnoth/data/config/*.cfg', 'trosnoth/data/config'),
('trosnoth/data/fonts/*.ttf', 'trosnoth/data/fonts'),
('trosnoth/data/fonts/*.TTF', 'trosnoth/data/fonts'),
('trosnoth/data/fonts/*.txt', 'trosnoth/data/fonts'),
('trosnoth/data/music/*.ogg', 'trosnoth/data/music'),
('trosnoth/data/sound/*.ogg', 'trosnoth/data/sound'),
('trosnoth/data/sprites/*.png', 'trosnoth/data/sprites'),
('trosnoth/data/startupMenu/*.png', 'trosnoth/data/startupMenu'),
('trosnoth/data/startupMenu/*.txt', 'trosnoth/data/startupMenu'),
('trosnoth/data/statGeneration/*.htm', 'trosnoth/data/statGeneration'),
('trosnoth/data/achievements/*.png', 'trosnoth/data/achievements'),
('trosnoth/data/web/*.png', 'trosnoth/data/web'),
('trosnoth/data/*.db', 'trosnoth/data'),
('trosnoth/gpl.txt', 'trosnoth'),
],
excludes=['django'],
console=False,
))
coll = COLLECT(
*bits,
strip=False,
upx=True,
name='trosnoth')
......@@ -9,7 +9,7 @@ base_dir = os.path.abspath('.')
a = Analysis(
['scripts\\trosnoth'],
['scripts/trosnoth'],
pathex=[base_dir],
binaries=[],
datas=[
......@@ -17,6 +17,7 @@ a = Analysis(
('trosnoth/data/blocks/*.trosblock', 'trosnoth/data/blocks'),
('trosnoth/data/config/*.cfg', 'trosnoth/data/config'),
('trosnoth/data/fonts/*.ttf', 'trosnoth/data/fonts'),
('trosnoth/data/fonts/*.TTF', 'trosnoth/data/fonts'),
('trosnoth/data/fonts/*.txt', 'trosnoth/data/fonts'),
('trosnoth/data/music/*.ogg', 'trosnoth/data/music'),
('trosnoth/data/sound/*.ogg', 'trosnoth/data/sound'),
......@@ -46,7 +47,8 @@ exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='trosnoth',
# Avoid name collision with trosnoth folder
name='trosnoth' if os.name == 'nt' else 'trosnothapp',
debug=False,
bootloader_ignore_signals=False,
strip=False,
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment