mirror of
https://gitlab.com/openlp/packaging.git
synced 2024-12-22 21:12:50 +00:00
357 lines
15 KiB
Python
357 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
|
|
|
###############################################################################
|
|
# OpenLP - Open Source Lyrics Projection #
|
|
# --------------------------------------------------------------------------- #
|
|
# Copyright (c) 2004-2016 OpenLP Developers #
|
|
# --------------------------------------------------------------------------- #
|
|
# This program is free software; you can redistribute it and/or modify it #
|
|
# under the terms of the GNU General Public License as published by the Free #
|
|
# Software Foundation; version 2 of the License. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT #
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
|
|
# more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License along #
|
|
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
|
|
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
|
|
###############################################################################
|
|
"""
|
|
Windows Build Script
|
|
--------------------
|
|
|
|
This script is used to build the Windows binary and the accompanying installer.
|
|
For this script to work out of the box, it depends on a number of things:
|
|
|
|
Python 3.7
|
|
|
|
PyQt5
|
|
You should already have this installed, OpenLP doesn't work without it. The
|
|
version the script expects is the packaged one available from pypi.
|
|
|
|
PyEnchant
|
|
This script expects the precompiled, installable version of PyEnchant to be
|
|
installed. You can find this on the PyEnchant site.
|
|
|
|
Inno Setup 5
|
|
Inno Setup should be installed into "C:\\%PROGRAMFILES%\\Inno Setup 5"
|
|
|
|
Sphinx
|
|
This is used to build the documentation. The documentation trunk must be at
|
|
the same directory level as OpenLP trunk and named "documentation".
|
|
|
|
HTML Help Workshop
|
|
This is used to create the help file.
|
|
|
|
PyInstaller
|
|
PyInstaller can be installed from pypi.
|
|
|
|
Bazaar
|
|
You need the command line "bzr" client installed.
|
|
|
|
OpenLP
|
|
A checkout of the latest code, in a branch directory, which is in a Bazaar
|
|
shared repository directory. This means your code should be in a directory
|
|
structure like this: "openlp\\branch-name".
|
|
|
|
windows-builder.py
|
|
This script, of course. It should be in the "windows-installer" directory
|
|
at the same level as OpenLP trunk.
|
|
|
|
psvince.dll
|
|
This dll is used during the actual install of OpenLP to check if OpenLP is
|
|
running on the users machine prior to the setup. If OpenLP is running,
|
|
the install will fail. The dll can be obtained from here:
|
|
|
|
http://www.vincenzo.net/isxkb/index.php?title=PSVince
|
|
|
|
The dll is presently included with this script.
|
|
|
|
Mako
|
|
Mako Templates for Python. This package is required for building the
|
|
remote plugin. It can be installed by going to your
|
|
python_directory\\scripts\\.. and running "easy_install Mako". If you do not
|
|
have easy_install, the Mako package can be obtained here:
|
|
|
|
http://www.makotemplates.org/download.html
|
|
|
|
MuPDF
|
|
Required for PDF support in OpenLP. Download the windows build from
|
|
mupdf.com, extract it, and set the mutoolbin option in the config file to
|
|
point to mutool.exe.
|
|
|
|
MediaInfo
|
|
Required for the media plugin. Download the 32-bit CLI windows build from
|
|
https://mediaarea.net/nn/MediaInfo/Download/Windows and set the
|
|
mediainfobin option in the config file to point to MediaInfo.exe.
|
|
|
|
Portable App Builds
|
|
The following are required if you are planning to make a portable build of
|
|
OpenLP. The portable build conforms to the standards published by
|
|
PortableApps.com:
|
|
|
|
http://portableapps.com/development/portableapps.com_format
|
|
|
|
PortableApps.com Installer:
|
|
|
|
http://portableapps.com/apps/development/portableapps.com_installer
|
|
|
|
PortableApps.com Launcher:
|
|
|
|
http://portableapps.com/apps/development/portableapps.com_launcher
|
|
|
|
NSIS Portable (Unicode version):
|
|
|
|
http://portableapps.com/apps/development/nsis_portable
|
|
"""
|
|
|
|
import os
|
|
import glob
|
|
import sys
|
|
from distutils import dir_util
|
|
from shutil import copy, move, rmtree
|
|
|
|
from builder import Builder
|
|
|
|
|
|
class WindowsBuilder(Builder):
|
|
"""
|
|
The :class:`WindowsBuilder` class encapsulates everything that is needed
|
|
to build a Windows installer.
|
|
"""
|
|
|
|
def _create_innosetup_file(self):
|
|
"""
|
|
Create an InnoSetup file pointing to the branch being built.
|
|
"""
|
|
self._print('Creating Inno Setup file...')
|
|
config_dir = os.path.dirname(self.config_path)
|
|
with open(os.path.join(config_dir, 'OpenLP.iss.default'), 'r') as input_file, \
|
|
open(os.path.join(config_dir, 'OpenLP.iss'), 'w') as output_file:
|
|
content = input_file.read()
|
|
content = content.replace('%(branch)s', self.branch_path)
|
|
content = content.replace('%(display_version)s', self.version.replace('-bzr', '.'))
|
|
content = content.replace('%(arch)s', self.arch)
|
|
output_file.write(content)
|
|
|
|
def _run_innosetup(self):
|
|
"""
|
|
Run InnoSetup to create an installer.
|
|
"""
|
|
self._print('Running Inno Setup...')
|
|
config_dir = os.path.dirname(self.config_path)
|
|
os.chdir(config_dir)
|
|
self._run_command([self.innosetup_exe, os.path.join(config_dir, 'OpenLP.iss'), '/q'],
|
|
'Error running InnoSetup')
|
|
|
|
def _create_portableapp_structure(self):
|
|
"""
|
|
Checks the PortableApp directory structure amd creates
|
|
missing subdirs
|
|
"""
|
|
self._print('... Checking PortableApps directory structure...')
|
|
launcher_path = os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'Launcher')
|
|
if not os.path.exists(launcher_path):
|
|
os.makedirs(launcher_path)
|
|
settings_path = os.path.join(self.portable_dest_path, 'Data', 'Settings')
|
|
if not os.path.exists(settings_path):
|
|
os.makedirs(settings_path)
|
|
|
|
def _create_portableapps_appinfo_file(self):
|
|
"""
|
|
Create a Portabbleapps appinfo.ini file.
|
|
"""
|
|
self._print_verbose('... Creating PortableApps appinfo file ...')
|
|
config_dir = os.path.dirname(self.config_path)
|
|
if '-bzr' in self.version:
|
|
version, revision = self.version.split('-bzr')
|
|
version = version + '.0' * (2 - version.count('.'))
|
|
self.portable_version = version + '.' + revision
|
|
else:
|
|
self.portable_version = self.version + '.0' * (3 - self.version.count('.'))
|
|
with open(os.path.join(config_dir, 'appinfo.ini.default'), 'r') as input_file, \
|
|
open(os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file:
|
|
content = input_file.read()
|
|
content = content.replace('%(display_version)s', self.portable_version)
|
|
content = content.replace('%(package_version)s', self.portable_version)
|
|
content = content.replace('%(arch)s', self.arch)
|
|
output_file.write(content)
|
|
|
|
def _run_portableapp_builder(self):
|
|
"""
|
|
Creates a portable installer.
|
|
1 Copies the distribution to the portable apps directory
|
|
2 Builds the PortableApps Launcher
|
|
3 Builds the PortableApps Install
|
|
"""
|
|
self._print('Running PortableApps Builder...')
|
|
self._print_verbose('... Clearing old files')
|
|
# Remove previous contents of portableapp build directory.
|
|
if os.path.exists(self.portable_dest_path):
|
|
rmtree(self.portable_dest_path)
|
|
self._print_verbose('... Creating PortableApps build directory')
|
|
# Copy the contents of the OpenLPPortable directory to the portable
|
|
# build directory.
|
|
dir_util.copy_tree(self.portable_source_path, self.portable_dest_path)
|
|
self._create_portableapp_structure()
|
|
self._create_portableapps_appinfo_file()
|
|
# Copy distribution files to portableapp build directory.
|
|
self._print_verbose('... Copying distribution files')
|
|
portable_app_path = os.path.join(self.portable_dest_path, 'App', 'OpenLP')
|
|
dir_util.copy_tree(self.dist_path, portable_app_path)
|
|
# Copy help files to portableapp build directory.
|
|
if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')):
|
|
self._print_verbose('... Copying help files')
|
|
dir_util.copy_tree(self.helpfile_path, os.path.join(portable_app_path, 'help'))
|
|
else:
|
|
self._print('... WARNING: Windows help file not found')
|
|
# Build the launcher.
|
|
self._print_verbose('... Building PortableApps Launcher')
|
|
self._run_command([self.portablelauncher_exe, self.portable_dest_path],
|
|
'Error creating PortableApps Launcher')
|
|
# Build the portable installer.
|
|
self._print_verbose('... Building PortableApps Installer')
|
|
self._run_command([self.portableinstaller_exe, self.portable_dest_path],
|
|
'Error running PortableApps Installer')
|
|
portable_exe_name = 'OpenLPPortable_%s.paf.exe' % self.portable_version
|
|
portable_exe_path = os.path.abspath(os.path.join(self.portable_dest_path, '..', portable_exe_name))
|
|
self._print_verbose('... Portable Build: {}'.format(portable_exe_path))
|
|
if os.path.exists(portable_exe_path):
|
|
move(portable_exe_path, os.path.join(self.dist_path, '..', portable_exe_name))
|
|
self._print('PortableApp build complete')
|
|
else:
|
|
raise Exception('PortableApp failed to build')
|
|
|
|
def get_platform(self):
|
|
"""
|
|
Return the platform we're building for
|
|
"""
|
|
return 'Windows'
|
|
|
|
def get_config_defaults(self):
|
|
"""
|
|
Build some default values for the config file
|
|
"""
|
|
config_defaults = super().get_config_defaults()
|
|
config_defaults.update({
|
|
'pyroot': self.python_root,
|
|
'progfiles': self.program_files,
|
|
'sitepackages': self.site_packages,
|
|
'projects': os.path.abspath(os.path.join(self.script_path, '..', '..'))
|
|
})
|
|
return config_defaults
|
|
|
|
def get_sphinx_build(self):
|
|
"""
|
|
Tell Sphinx we want to build HTML help
|
|
"""
|
|
return "htmlhelp"
|
|
|
|
def get_qt_translations_path(self):
|
|
"""
|
|
Return the path to Qt translation files on macOS
|
|
"""
|
|
return os.path.join(self.site_packages, 'PyQt5', 'Qt', 'translations')
|
|
|
|
def add_extra_args(self, parser):
|
|
"""
|
|
Add extra arguments to the command line argument parser
|
|
"""
|
|
parser.add_argument('--portable', action='store_true', default=False,
|
|
help='Build a PortableApps.com build of OpenLP too')
|
|
|
|
def setup_system_paths(self):
|
|
"""
|
|
Set up some system paths.
|
|
"""
|
|
super().setup_system_paths()
|
|
self.python_root = os.path.dirname(self.python)
|
|
self.site_packages = os.path.join(self.python_root, 'Lib', 'site-packages')
|
|
# Default program_files to 'Program Files (x86)' - the folder for 32-bit programs on 64-bit systems, if that
|
|
# does not exists the host system is 32-bit so fallback to 'Program Files'.
|
|
self.program_files = os.getenv('PROGRAMFILES(x86)')
|
|
if not self.program_files:
|
|
self.program_files = os.getenv('PROGRAMFILES')
|
|
self._print_verbose(' {:.<20}: {}'.format('site packages: ', self.site_packages))
|
|
self._print_verbose(' {:.<20}: {}'.format('program files: ', self.program_files))
|
|
|
|
def setup_paths(self):
|
|
"""
|
|
Set up a variety of paths that we use throughout the build process.
|
|
"""
|
|
super().setup_paths()
|
|
self.dist_path = os.path.join(self.work_path, 'dist', 'OpenLP')
|
|
self.helpfile_path = os.path.join(self.manual_build_path, 'htmlhelp')
|
|
self.winres_path = os.path.join(self.branch_path, 'resources', 'windows')
|
|
|
|
def setup_extra(self):
|
|
"""
|
|
Extra setup to run
|
|
"""
|
|
# Detect python instance bit size
|
|
self.arch = 'x86' if sys.maxsize == 0x7fffffff else 'x64'
|
|
|
|
def copy_extra_files(self):
|
|
"""
|
|
Copy all the Windows-specific files.
|
|
"""
|
|
self._print('Copying extra files for Windows...')
|
|
self._print_verbose('... OpenLP.ico')
|
|
copy(self.icon_path, os.path.join(self.dist_path, 'OpenLP.ico'))
|
|
self._print_verbose('... LICENSE.txt')
|
|
copy(self.license_path, os.path.join(self.dist_path, 'LICENSE.txt'))
|
|
self._print_verbose('... psvince.dll')
|
|
copy(self.psvince_exe, os.path.join(self.dist_path, 'psvince.dll'))
|
|
if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')):
|
|
self._print_verbose('... OpenLP.chm')
|
|
copy(os.path.join(self.helpfile_path, 'OpenLP.chm'), os.path.join(self.dist_path, 'OpenLP.chm'))
|
|
else:
|
|
self._print('... WARNING: Windows help file not found')
|
|
self._print_verbose('... mutool.exe')
|
|
if self.mutool_exe and os.path.isfile(self.mutool_exe):
|
|
copy(os.path.join(self.mutool_exe), os.path.join(self.dist_path, 'mutool.exe'))
|
|
else:
|
|
self._print('... WARNING: mutool.exe not found')
|
|
self._print_verbose('... MediaInfo.exe')
|
|
if self.mediainfo_exe and os.path.isfile(self.mediainfo_exe):
|
|
copy(os.path.join(self.mediainfo_exe), os.path.join(self.dist_path, 'MediaInfo.exe'))
|
|
else:
|
|
self._print('... WARNING: MediaInfo.exe not found')
|
|
|
|
def after_run_sphinx(self):
|
|
"""
|
|
Run HTML Help Workshop to convert the Sphinx output into a manual.
|
|
"""
|
|
self._print('Running HTML Help Workshop...')
|
|
os.chdir(os.path.join(self.manual_build_path, 'htmlhelp'))
|
|
self._run_command([self.htmlhelp_exe, 'OpenLP.chm'], 'Error running HTML Help Workshop', exit_code=1)
|
|
|
|
def build_package(self):
|
|
"""
|
|
Build the installer
|
|
"""
|
|
self._create_innosetup_file()
|
|
self._run_innosetup()
|
|
if self.args.portable:
|
|
self._run_portableapp_builder()
|
|
|
|
def get_extra_parameters(self):
|
|
"""
|
|
Return a list of any extra parameters we wish to use
|
|
"""
|
|
parameters = []
|
|
dll_path = '{pf}\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\{arch}\\*.dll'.format(pf=self.program_files,
|
|
arch=self.arch)
|
|
# Finds the UCRT DDLs available from the Windows 10 SDK
|
|
for binary in glob.glob(dll_path):
|
|
parameters.append('--add-binary')
|
|
parameters.append(binary + ";.")
|
|
return parameters
|
|
|
|
|
|
if __name__ == '__main__':
|
|
WindowsBuilder().main()
|