mirror of
https://gitlab.com/openlp/packaging.git
synced 2024-12-22 13:02:50 +00:00
Refactor the builders so that we can inherit as much common code as possible
This commit is contained in:
parent
86629d4e42
commit
dbd59b9a3c
443
builders/builder.py
Normal file
443
builders/builder.py
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
# -*- 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 #
|
||||||
|
###############################################################################
|
||||||
|
"""
|
||||||
|
Base class for the Windows and macOS builders.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from shutil import copy, rmtree
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
BUILDER_DESCRIPTION = 'Build OpenLP for {platform}. Options are provided on both the command line and a ' \
|
||||||
|
'configuration file. Options in the configuration file are overridden by the command line options.\n\n' \
|
||||||
|
'This build system can produce either development or release builds. A development release uses the code ' \
|
||||||
|
'as-is in the specified branch directory. The release build exports a tag from bzr and uses the exported ' \
|
||||||
|
'code for building. The two modes are invoked by the presence or absence of the --release option. If this ' \
|
||||||
|
'option is omitted, a development build is built, while including the --release option with a version ' \
|
||||||
|
'number will produce a build of that exact version.'
|
||||||
|
|
||||||
|
|
||||||
|
def _which(command):
|
||||||
|
"""
|
||||||
|
Return absolute path to a command found on system PATH.
|
||||||
|
"""
|
||||||
|
if command.startswith('/'):
|
||||||
|
return command
|
||||||
|
for path in os.environ["PATH"].split(os.pathsep):
|
||||||
|
if os.access(os.path.join(path, command), os.X_OK):
|
||||||
|
return "%s/%s" % (path, command)
|
||||||
|
|
||||||
|
|
||||||
|
class Builder(object):
|
||||||
|
"""
|
||||||
|
A Generic class to base other operating system specific builders on
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.setup_args()
|
||||||
|
self.setup_system_paths()
|
||||||
|
self.read_config()
|
||||||
|
self.setup_executables()
|
||||||
|
self.setup_paths()
|
||||||
|
self.setup_extra()
|
||||||
|
|
||||||
|
def _print(self, text, *args):
|
||||||
|
"""
|
||||||
|
Print stuff out. Later we might want to use a log file.
|
||||||
|
"""
|
||||||
|
if len(args) > 0:
|
||||||
|
text = text % tuple(args)
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
def _print_verbose(self, text, *args):
|
||||||
|
"""
|
||||||
|
Print output, obeying "verbose" mode.
|
||||||
|
"""
|
||||||
|
if self.args.verbose:
|
||||||
|
self._print(text, *args)
|
||||||
|
|
||||||
|
def _run_command(self, cmd, err_msg):
|
||||||
|
"""
|
||||||
|
Run command in subprocess and print error message in case of Exception.
|
||||||
|
|
||||||
|
Return text from stdout.
|
||||||
|
"""
|
||||||
|
proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||||
|
output, error = proc.communicate()
|
||||||
|
code = proc.wait()
|
||||||
|
if code != 0:
|
||||||
|
self._print(output)
|
||||||
|
self._print(error)
|
||||||
|
raise Exception(err_msg)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def _bzr(self, command, work_path, args=[], err_msg='There was an error running bzr'):
|
||||||
|
"""
|
||||||
|
Update the code in the branch.
|
||||||
|
"""
|
||||||
|
os.chdir(work_path)
|
||||||
|
output = self._run_command(['bzr', command] + args, err_msg)
|
||||||
|
return output
|
||||||
|
|
||||||
|
def get_platform(self):
|
||||||
|
"""
|
||||||
|
Return the platform we're building for
|
||||||
|
"""
|
||||||
|
return 'unspecified'
|
||||||
|
|
||||||
|
def get_config_defaults(self):
|
||||||
|
"""
|
||||||
|
Build some default values for the config file
|
||||||
|
"""
|
||||||
|
return {'here': self.script_path}
|
||||||
|
|
||||||
|
def get_sphinx_build(self):
|
||||||
|
"""
|
||||||
|
Get the type of build we should be running for Sphinx. Defaults to html.
|
||||||
|
"""
|
||||||
|
return 'html'
|
||||||
|
|
||||||
|
def setup_args(self):
|
||||||
|
"""
|
||||||
|
Set up an argument parser and parse the command line arguments.
|
||||||
|
"""
|
||||||
|
parser = ArgumentParser(description=BUILDER_DESCRIPTION.format(platform=self.get_platform()))
|
||||||
|
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False,
|
||||||
|
help='Print out additional information')
|
||||||
|
parser.add_argument('-c', '--config', metavar='FILENAME', required=True,
|
||||||
|
help='Specify the path to the configuration file')
|
||||||
|
parser.add_argument('-b', '--branch', metavar='PATH', help='Specify the path to the branch you wish to build')
|
||||||
|
parser.add_argument('-r', '--release', metavar='VERSION', default=None,
|
||||||
|
help='Build a release version of OpenLP with the version specified')
|
||||||
|
parser.add_argument('-d', '--documentation', metavar='PATH', default=None,
|
||||||
|
help='Specify the path to the documentation branch')
|
||||||
|
parser.add_argument('-t', '--update-translations', action='store_true', default=False,
|
||||||
|
help='Update the translations from Transifex')
|
||||||
|
parser.add_argument('-u', '--transifex-user', metavar='USERNAME', default=None, help='Transifex username')
|
||||||
|
parser.add_argument('-p', '--transifex-pass', metavar='PASSWORD', default=None, help='Transifex password')
|
||||||
|
parser.add_argument('--skip-update', action='store_true', default=False,
|
||||||
|
help='Do NOT update the branch before building')
|
||||||
|
parser.add_argument('--skip-translations', action='store_true', default=False,
|
||||||
|
help='Do NOT update the language translation files')
|
||||||
|
self.add_extra_args(parser)
|
||||||
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
|
def add_extra_args(self, parser):
|
||||||
|
"""
|
||||||
|
Add extra arguments to the argument parser
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read_config(self):
|
||||||
|
"""
|
||||||
|
Read the configuration from the configuration file.
|
||||||
|
"""
|
||||||
|
self.config = ConfigParser(defaults=self.get_config_defaults())
|
||||||
|
self.config.read(os.path.abspath(self.args.config))
|
||||||
|
|
||||||
|
def setup_system_paths(self):
|
||||||
|
"""
|
||||||
|
Set up some system paths.
|
||||||
|
"""
|
||||||
|
self.script_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
self.python = sys.executable
|
||||||
|
|
||||||
|
def setup_executables(self):
|
||||||
|
"""
|
||||||
|
Set up the paths to the executables we use.
|
||||||
|
"""
|
||||||
|
self._print_verbose('Executables:')
|
||||||
|
for option in self.config.options('executables'):
|
||||||
|
value = _which(self.config.get('executables', option))
|
||||||
|
setattr(self, '{option}_exe'.format(option=option), value)
|
||||||
|
self._print_verbose(' {option:.<30}: {value}'.format(option=option + ' ', value=value))
|
||||||
|
|
||||||
|
def setup_paths(self):
|
||||||
|
"""
|
||||||
|
Set up a variety of paths that we use throughout the build process.
|
||||||
|
"""
|
||||||
|
for option in self.config.options('paths'):
|
||||||
|
setattr(self, '{path}_path'.format(path=option), os.path.abspath(self.config.get('paths', option)))
|
||||||
|
# Make any command line options override the config file
|
||||||
|
if self.args.branch:
|
||||||
|
self.branch_path = os.path.abspath(self.args.branch)
|
||||||
|
if self.args.documentation:
|
||||||
|
self.documentation_path = os.path.abspath(self.args.documentation)
|
||||||
|
if self.args.release:
|
||||||
|
self.version = self.args.release
|
||||||
|
self.work_path = os.path.abspath(os.path.join(self.branch_path, '..', 'OpenLP-' + self.version))
|
||||||
|
else:
|
||||||
|
self.version = None
|
||||||
|
self.work_path = self.branch_path
|
||||||
|
self.openlp_script = os.path.abspath(os.path.join(self.work_path, 'openlp-run.py'))
|
||||||
|
self.source_path = os.path.join(self.work_path, 'openlp')
|
||||||
|
self.manual_path = os.path.join(self.documentation_path, 'manual')
|
||||||
|
self.manual_build_path = os.path.join(self.manual_path, 'build')
|
||||||
|
self.i18n_utils = os.path.join(self.work_path, 'scripts', 'translation_utils.py')
|
||||||
|
self.i18n_path = os.path.join(self.work_path, 'resources', 'i18n')
|
||||||
|
self.build_path = os.path.join(self.work_path, 'build')
|
||||||
|
|
||||||
|
# Path to Qt translation files.
|
||||||
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
|
qt_plug_dir = str(list(QCoreApplication.libraryPaths())[0])
|
||||||
|
self.qt_translations_path = os.path.join(os.path.dirname(qt_plug_dir), 'translations')
|
||||||
|
|
||||||
|
def setup_extra(self):
|
||||||
|
"""
|
||||||
|
Extra setup to run
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_code(self):
|
||||||
|
"""
|
||||||
|
Update the code in the branch.
|
||||||
|
"""
|
||||||
|
self._print('Reverting any changes to the code...')
|
||||||
|
self._bzr('revert', self.branch_path, err_msg='Error reverting the code')
|
||||||
|
self._print('Updating the code...')
|
||||||
|
self._bzr('update', self.branch_path, err_msg='Error updating the code')
|
||||||
|
|
||||||
|
def export_release(self):
|
||||||
|
"""
|
||||||
|
Export a particular release
|
||||||
|
"""
|
||||||
|
if os.path.exists(self.work_path):
|
||||||
|
rmtree(self.work_path)
|
||||||
|
self._print('Exporting the release version...')
|
||||||
|
self._bzr('export', self.branch_path, ['-r', 'tag:' + self.version, self.work_path],
|
||||||
|
'Error exporting the code')
|
||||||
|
|
||||||
|
def run_pyinstaller(self):
|
||||||
|
"""
|
||||||
|
Run PyInstaller on the branch to build an executable.
|
||||||
|
"""
|
||||||
|
self._print('Running PyInstaller...')
|
||||||
|
os.chdir(self.work_path)
|
||||||
|
cmd = [self.python,
|
||||||
|
self.pyinstaller_exe,
|
||||||
|
'--clean',
|
||||||
|
'--noconfirm',
|
||||||
|
'--windowed',
|
||||||
|
'--noupx',
|
||||||
|
'--additional-hooks-dir', self.hooks_path,
|
||||||
|
'--runtime-hook', os.path.join(self.hooks_path, 'rthook_ssl.py'),
|
||||||
|
'-i', self.icon_path,
|
||||||
|
'-n', 'OpenLP',
|
||||||
|
self.openlp_script]
|
||||||
|
if not self.args.verbose:
|
||||||
|
cmd.append('--log-level=ERROR')
|
||||||
|
else:
|
||||||
|
cmd.append('--log-level=DEBUG')
|
||||||
|
if self.args.devel:
|
||||||
|
cmd.append('-d')
|
||||||
|
self._run_command(cmd, 'Error running PyInstaller')
|
||||||
|
|
||||||
|
def write_version_file(self):
|
||||||
|
"""
|
||||||
|
Write the version number to a file for reading once installed.
|
||||||
|
"""
|
||||||
|
self._print('Writing version file...')
|
||||||
|
if not self.args.release:
|
||||||
|
# This is a development build, get the tag and revision
|
||||||
|
output = self._bzr('tags', self.branch_path, err_msg='Error running bzr tags')
|
||||||
|
lines = output.splitlines()
|
||||||
|
if len(lines) == 0:
|
||||||
|
tag = '0.0.0'
|
||||||
|
revision = '0'
|
||||||
|
else:
|
||||||
|
tag, revision = lines[-1].decode('utf-8').split()
|
||||||
|
output = self._bzr('log', self.branch_path, ['--line', '-r', '-1'], 'Error running bzr log')
|
||||||
|
revision = output.decode('utf-8').split(':')[0]
|
||||||
|
self.version = '{tag}-bzr{revision}'.format(tag=tag, revision=revision)
|
||||||
|
# Write the version to the version file
|
||||||
|
with open(os.path.join(self.dist_path, '.version'), 'w') as version_file:
|
||||||
|
version_file.write(str(self.version))
|
||||||
|
|
||||||
|
def copy_default_theme(self):
|
||||||
|
"""
|
||||||
|
Copy the default theme to the correct directory for OpenLP.
|
||||||
|
"""
|
||||||
|
self._print('Copying default theme...')
|
||||||
|
source = os.path.join(self.source_path, 'core', 'lib', 'json')
|
||||||
|
dest = os.path.join(self.dist_path, 'core', 'lib', 'json')
|
||||||
|
for root, dirs, files in os.walk(source):
|
||||||
|
for filename in files:
|
||||||
|
if filename.endswith('.json'):
|
||||||
|
dest_path = os.path.join(dest, root[len(source) + 1:])
|
||||||
|
if not os.path.exists(dest_path):
|
||||||
|
os.makedirs(dest_path)
|
||||||
|
self._print_verbose('... %s', filename)
|
||||||
|
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
||||||
|
|
||||||
|
def copy_plugins(self):
|
||||||
|
"""
|
||||||
|
Copy all the plugins to the correct directory so that OpenLP sees that
|
||||||
|
it has plugins.
|
||||||
|
"""
|
||||||
|
self._print('Copying plugins...')
|
||||||
|
source = os.path.join(self.source_path, 'plugins')
|
||||||
|
dest = os.path.join(self.dist_path, 'plugins')
|
||||||
|
for root, dirs, files in os.walk(source):
|
||||||
|
for filename in files:
|
||||||
|
if not filename.endswith('.pyc'):
|
||||||
|
dest_path = os.path.join(dest, root[len(source) + 1:])
|
||||||
|
if not os.path.exists(dest_path):
|
||||||
|
os.makedirs(dest_path)
|
||||||
|
self._print_verbose('... %s', filename)
|
||||||
|
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
||||||
|
|
||||||
|
def copy_media_player(self):
|
||||||
|
"""
|
||||||
|
Copy the media players to the correct directory for OpenLP.
|
||||||
|
"""
|
||||||
|
self._print('Copying media player...')
|
||||||
|
source = os.path.join(self.source_path, 'core', 'ui', 'media')
|
||||||
|
dest = os.path.join(self.dist_path, 'core', 'ui', 'media')
|
||||||
|
for root, dirs, files in os.walk(source):
|
||||||
|
for filename in files:
|
||||||
|
if not filename.endswith('.pyc'):
|
||||||
|
dest_path = os.path.join(dest, root[len(source) + 1:])
|
||||||
|
if not os.path.exists(dest_path):
|
||||||
|
os.makedirs(dest_path)
|
||||||
|
self._print_verbose('... %s', filename)
|
||||||
|
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
||||||
|
|
||||||
|
def copy_extra_files(self):
|
||||||
|
"""
|
||||||
|
Copy any extra files which are particular to a platform
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def update_translations(self):
|
||||||
|
"""
|
||||||
|
Update the translations.
|
||||||
|
"""
|
||||||
|
self._print('Updating translations...')
|
||||||
|
username = None
|
||||||
|
password = None
|
||||||
|
if self.args.transifex_user:
|
||||||
|
username = self.args.transifex_user
|
||||||
|
if self.args.transifex_password:
|
||||||
|
password = self.args.transifex_pass
|
||||||
|
if (not username or not password) and not self.config.has_section('transifex'):
|
||||||
|
raise Exception('No section named "transifex" found.')
|
||||||
|
elif not username and not self.config.has_option('transifex', 'username'):
|
||||||
|
raise Exception('No option named "username" found.')
|
||||||
|
elif not password and not self.config.has_option('transifex', 'password'):
|
||||||
|
raise Exception('No option named "password" found.')
|
||||||
|
if not username:
|
||||||
|
username = self.config.get('transifex', 'username')
|
||||||
|
if not password:
|
||||||
|
password = self.config.get('transifex', 'password')
|
||||||
|
os.chdir(os.path.split(self.i18n_utils)[0])
|
||||||
|
self._run_command([self.python, self.i18n_utils, '-qdpu', '-U', username, '-P', password],
|
||||||
|
err_msg='Error running translation_utils.py')
|
||||||
|
|
||||||
|
def compile_translations(self):
|
||||||
|
"""
|
||||||
|
Compile the translations for Qt.
|
||||||
|
"""
|
||||||
|
self._print('Compiling translations...')
|
||||||
|
files = os.listdir(self.i18n_path)
|
||||||
|
if not os.path.exists(os.path.join(self.dist_path, 'i18n')):
|
||||||
|
os.makedirs(os.path.join(self.dist_path, 'i18n'))
|
||||||
|
for file in files:
|
||||||
|
if file.endswith('.ts'):
|
||||||
|
self._print_verbose('... %s', file)
|
||||||
|
source_path = os.path.join(self.i18n_path, file)
|
||||||
|
dest_path = os.path.join(self.dist_path, 'i18n', file.replace('.ts', '.qm'))
|
||||||
|
self._run_command((self.lrelease_exe, '-compress', '-silent', source_path, '-qm', dest_path),
|
||||||
|
err_msg='Error running lconvert on %s' % source_path)
|
||||||
|
self._print('Copying qm files...')
|
||||||
|
source = self.qt_translations_path
|
||||||
|
files = os.listdir(source)
|
||||||
|
for filename in files:
|
||||||
|
if filename.startswith('qt_') and filename.endswith('.qm'):
|
||||||
|
self._print_verbose('... %s', filename)
|
||||||
|
copy(os.path.join(source, filename), os.path.join(self.dist_path, 'i18n', filename))
|
||||||
|
|
||||||
|
def run_sphinx(self):
|
||||||
|
"""
|
||||||
|
Run Sphinx to build the manual
|
||||||
|
"""
|
||||||
|
self._print('Deleting previous help manual build... %s', self.manual_build_path)
|
||||||
|
if os.path.exists(self.manual_build_path):
|
||||||
|
rmtree(self.manual_build_path)
|
||||||
|
self._print('Running Sphinx...')
|
||||||
|
os.chdir(self.manual_path)
|
||||||
|
sphinx_build = self.get_sphinx_build()
|
||||||
|
command = [self.sphinx_exe, '-b', sphinx_build, '-d', 'build/doctrees', 'source', 'build/{}'.format(sphinx_build)]
|
||||||
|
self._run_command(command, 'Error running Sphinx')
|
||||||
|
self.after_run_sphinx()
|
||||||
|
|
||||||
|
def after_run_sphinx(self):
|
||||||
|
"""
|
||||||
|
Run some extra commands after sphinx.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def build_package(self):
|
||||||
|
"""
|
||||||
|
Actually package the resultant build
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
"""
|
||||||
|
The main function to run the builder.
|
||||||
|
"""
|
||||||
|
self._print_verbose('OpenLP main script: ......%s', self.openlp_script)
|
||||||
|
self._print_verbose('Script path: .............%s', self.script_path)
|
||||||
|
self._print_verbose('Branch path: .............%s', self.branch_path)
|
||||||
|
self._print_verbose('')
|
||||||
|
if not self.args.skip_update:
|
||||||
|
self.update_code()
|
||||||
|
if self.args.release:
|
||||||
|
self.export_release()
|
||||||
|
self.run_pyinstaller()
|
||||||
|
self.write_version_file()
|
||||||
|
self.copy_default_theme()
|
||||||
|
self.copy_plugins()
|
||||||
|
self.copy_media_player()
|
||||||
|
self.copy_extra_files()
|
||||||
|
# TODO creating help on Mac
|
||||||
|
if os.path.exists(self.manual_path):
|
||||||
|
self.run_sphinx()
|
||||||
|
else:
|
||||||
|
self._print('')
|
||||||
|
self._print('WARNING: Documentation trunk not found')
|
||||||
|
self._print(' Help file will not be included in build')
|
||||||
|
self._print('')
|
||||||
|
self.copy_macosx_files()
|
||||||
|
if not self.args.skip_translations:
|
||||||
|
if self.args.update_translations:
|
||||||
|
self.update_translations()
|
||||||
|
self.compile_translations()
|
||||||
|
self.code_sign()
|
||||||
|
self.create_dmg_file()
|
||||||
|
|
||||||
|
self._print('Done.')
|
||||||
|
raise SystemExit()
|
||||||
|
|
||||||
|
|
288
builders/macosx-builder.py
Normal file
288
builders/macosx-builder.py
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
# -*- 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 #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Mac OS X Build Script
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
This script is used to build the Mac OS X app bundle and pack it into dmg file.
|
||||||
|
For this script to work out of the box, it depends on a number of things:
|
||||||
|
|
||||||
|
Python 3.4
|
||||||
|
|
||||||
|
PyQt5
|
||||||
|
You should already have this installed, OpenLP doesn't work without it. The
|
||||||
|
version the script expects is the packaged one available from River Bank
|
||||||
|
Computing.
|
||||||
|
|
||||||
|
PyEnchant
|
||||||
|
This script expects the precompiled, installable version of PyEnchant to be
|
||||||
|
installed. You can find this on the PyEnchant site.
|
||||||
|
|
||||||
|
Sphinx
|
||||||
|
This is used to build the documentation. The documentation trunk must be at
|
||||||
|
the same directory level as OpenLP trunk and named "documentation".
|
||||||
|
|
||||||
|
PyInstaller
|
||||||
|
PyInstaller should be a git clone of either
|
||||||
|
https://github.com/matysek/pyinstaller branch python3 or
|
||||||
|
https://github.com/pyinstaller/pyinstaller branch python3
|
||||||
|
|
||||||
|
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".
|
||||||
|
|
||||||
|
macosx-builder.py
|
||||||
|
This script, of course. It should be in the "osx-package" directory
|
||||||
|
at the same level as OpenLP trunk.
|
||||||
|
|
||||||
|
Mako
|
||||||
|
Mako Templates for Python. This package is required for building the
|
||||||
|
remote plugin.
|
||||||
|
|
||||||
|
Alembic
|
||||||
|
Required for upgrading the databases used in OpenLP.
|
||||||
|
|
||||||
|
MuPDF
|
||||||
|
Required for PDF support in OpenLP. Install using macports, or use the
|
||||||
|
mudrawbin option in the config file to point to the mudraw binary.
|
||||||
|
|
||||||
|
MachOLib
|
||||||
|
Python library to analyze and edit Mach-O headers, the executable format
|
||||||
|
used by Mac OS X. Used to relink the mudraw binary from MuPDF to the bundled
|
||||||
|
libraries. Install using macports or pip.
|
||||||
|
|
||||||
|
config.ini.default
|
||||||
|
The configuration file contains settings of the version string to include
|
||||||
|
in the bundle as well as directory and file settings for different
|
||||||
|
purposes (e.g. PyInstaller location or installer background image)
|
||||||
|
|
||||||
|
To install everything you need to install MacPorts. Once MacPorts is installed
|
||||||
|
and up-to-date, run the following command::
|
||||||
|
|
||||||
|
$ sudo port install python34 py34-pyqt4 py34-sphinx py34-sqlalchemy \
|
||||||
|
py34-macholib py34-mako py34-alembic py34-enchant \
|
||||||
|
py34-beautifulsoup4 py34-lxml py34-nose
|
||||||
|
|
||||||
|
You may need to install chardet via pip::
|
||||||
|
|
||||||
|
$ sudo pip install chardet
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import plistlib
|
||||||
|
import signal
|
||||||
|
from shutil import copy, copytree
|
||||||
|
|
||||||
|
from macholib.MachO import MachO
|
||||||
|
from macholib.util import flipwritable, in_system_path
|
||||||
|
|
||||||
|
from builder import Builder
|
||||||
|
|
||||||
|
class MacosxBuilder(Builder):
|
||||||
|
"""
|
||||||
|
The :class:`MacosxBuilder` class encapsulates everything that is needed
|
||||||
|
to build a Mac OS X .dmg file.
|
||||||
|
"""
|
||||||
|
def _get_directory_size(self, directory):
|
||||||
|
"""
|
||||||
|
Return directory size - size of everything in the dir.
|
||||||
|
"""
|
||||||
|
dir_size = 0
|
||||||
|
for (path, dirs, files) in os.walk(directory):
|
||||||
|
for file in files:
|
||||||
|
filename = os.path.join(path, file)
|
||||||
|
dir_size += os.path.getsize(filename)
|
||||||
|
return dir_size
|
||||||
|
|
||||||
|
def setup_paths(self):
|
||||||
|
"""
|
||||||
|
Extra setup to run
|
||||||
|
"""
|
||||||
|
super().setup_paths()
|
||||||
|
if hasattr(self, 'mutoolbin'):
|
||||||
|
self.mutoollib = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(self.mutoolbin), '..', 'lib', 'libjbig2dec.0.dylib'))
|
||||||
|
self.dist_app_path = os.path.join(self.work_path, 'dist', 'OpenLP.app')
|
||||||
|
self.dist_path = os.path.join(self.work_path, 'dist', 'OpenLP.app', 'Contents', 'MacOS')
|
||||||
|
|
||||||
|
def copy_extra_files(self):
|
||||||
|
"""
|
||||||
|
Copy any extra files which are particular to a platform
|
||||||
|
"""
|
||||||
|
self._copy_bundle_files()
|
||||||
|
self._copy_macosx_files()
|
||||||
|
|
||||||
|
def _copy_bundle_files(self):
|
||||||
|
"""
|
||||||
|
Copy Info.plist and OpenLP.icns to app bundle.
|
||||||
|
"""
|
||||||
|
copy(self.icon_path, os.path.join(self.dist_app_path, 'Contents', 'Resources', os.path.basename(self.icon_path)))
|
||||||
|
# Add OpenLP version to Info.plist and put it to app bundle.
|
||||||
|
fr = open(self.bundle_info_path, 'r')
|
||||||
|
fw = open(os.path.join(self.dist_app_path, 'Contents', os.path.basename(self.bundle_info_path)), 'w')
|
||||||
|
text = fr.read()
|
||||||
|
text = text % {'openlp_version': self.version}
|
||||||
|
fw.write(text)
|
||||||
|
fr.close()
|
||||||
|
fw.close()
|
||||||
|
|
||||||
|
def _copy_macosx_files(self):
|
||||||
|
"""
|
||||||
|
Copy all the OSX-specific files.
|
||||||
|
"""
|
||||||
|
self._print('Copying extra files for Mac OS X...')
|
||||||
|
self._print_verbose('... LICENSE.txt')
|
||||||
|
copy(os.path.join(self.script_path, 'LICENSE.txt'), os.path.join(self.dist_path, 'LICENSE.txt'))
|
||||||
|
self._print_verbose('... mudraw')
|
||||||
|
if hasattr(self, 'mudrawbin') and os.path.isfile(self.mudrawbin):
|
||||||
|
copy(self.mudrawbin, os.path.join(self.dist_path, 'mudraw'))
|
||||||
|
self.relink_mudraw()
|
||||||
|
elif hasattr(self, 'mutoolbin') and os.path.isfile(self.mutoolbin):
|
||||||
|
copy(self.mutoolbin, os.path.join(self.dist_path, 'mutool'))
|
||||||
|
self.relink_mutool()
|
||||||
|
copy(self.mutoollib, os.path.join(self.dist_path, 'libjbig2dec.0.dylib'))
|
||||||
|
else:
|
||||||
|
self._print('... WARNING: mudraw and mutool not found')
|
||||||
|
|
||||||
|
def relink_mudraw(self):
|
||||||
|
"""
|
||||||
|
Relink mudraw to bundled libraries
|
||||||
|
"""
|
||||||
|
self.relink_mupdf('mudraw')
|
||||||
|
|
||||||
|
def relink_mutool(self):
|
||||||
|
"""
|
||||||
|
Relink mudraw to bundled libraries
|
||||||
|
"""
|
||||||
|
self.relink_mupdf('mutool')
|
||||||
|
|
||||||
|
def relink_mupdf(self, bin_name):
|
||||||
|
"""
|
||||||
|
Relink mupdf to bundled libraries
|
||||||
|
"""
|
||||||
|
self._print('Linking {bin_name} with bundled libraries...'.format(bin_name=bin_name))
|
||||||
|
libname = os.path.join(self.dist_path, bin_name)
|
||||||
|
distname = os.path.relpath(self.dist_path, libname)
|
||||||
|
self._print_verbose('... {bin_name} path {path}'.format(bin_name=bin_name, path=libname))
|
||||||
|
|
||||||
|
# Determine how many directories up is the directory with shared
|
||||||
|
# dynamic libraries. '../'
|
||||||
|
# E.g. ./qt4_plugins/images/ -> ./../../
|
||||||
|
parent_dir = ''
|
||||||
|
# Check if distname is not only base filename.
|
||||||
|
if os.path.dirname(distname):
|
||||||
|
parent_level = len(os.path.dirname(distname).split(os.sep))
|
||||||
|
parent_dir = parent_level * (os.pardir + os.sep)
|
||||||
|
|
||||||
|
def match_func(pth):
|
||||||
|
"""
|
||||||
|
For system libraries leave path unchanged.
|
||||||
|
"""
|
||||||
|
# Match non system dynamic libraries.
|
||||||
|
if not in_system_path(pth):
|
||||||
|
# Use relative path to dependend dynamic libraries bases on
|
||||||
|
# location of the executable.
|
||||||
|
pth = os.path.join('@loader_path', parent_dir, os.path.basename(pth))
|
||||||
|
self._print_verbose('... %s', pth)
|
||||||
|
return pth
|
||||||
|
|
||||||
|
# Rewrite mach headers with @loader_path.
|
||||||
|
dll = MachO(libname)
|
||||||
|
dll.rewriteLoadCommands(match_func)
|
||||||
|
|
||||||
|
# Write changes into file.
|
||||||
|
# Write code is based on macholib example.
|
||||||
|
try:
|
||||||
|
self._print_verbose('... writing new library paths')
|
||||||
|
with open(dll.filename, 'rb+') as dll_file:
|
||||||
|
for header in dll.headers:
|
||||||
|
dll_file.seek(0)
|
||||||
|
dll.write(dll_file)
|
||||||
|
dll_file.seek(0, 2)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def after_run_sphinx(self):
|
||||||
|
"""
|
||||||
|
Run Sphinx to build an HTML Help project.
|
||||||
|
"""
|
||||||
|
self._print('Copying help file...')
|
||||||
|
source = os.path.join(self.manual_build_path, 'applehelp')
|
||||||
|
files = os.listdir(source)
|
||||||
|
for filename in files:
|
||||||
|
if filename.endswith('.help'):
|
||||||
|
self._print_verbose('... %s', filename)
|
||||||
|
copytree(os.path.join(source, filename),
|
||||||
|
os.path.join(self.dist_app_path, 'Contents', 'Resources', filename))
|
||||||
|
|
||||||
|
def build_package(self):
|
||||||
|
"""
|
||||||
|
Build the actual DMG
|
||||||
|
"""
|
||||||
|
self.code_sign()
|
||||||
|
self.create_dmg()
|
||||||
|
|
||||||
|
def code_sign(self):
|
||||||
|
certificate = self.config.get('codesigning', 'certificate')
|
||||||
|
self._print('Checking for certificate...')
|
||||||
|
self._run_command(['security', 'find-certificate', '-c', certificate],
|
||||||
|
'Could not find certificate "{certificate}" in keychain, '.format(certificate=certificate) +
|
||||||
|
'codesigning will not work without a certificate')
|
||||||
|
self._print('Codesigning app...')
|
||||||
|
self._run_command(['codesign', '--deep', '-s', certificate, self.dist_app_path], 'Error running codesign')
|
||||||
|
|
||||||
|
def create_dmg(self):
|
||||||
|
"""
|
||||||
|
Create .dmg file.
|
||||||
|
"""
|
||||||
|
self._print('Creating dmg file...')
|
||||||
|
dmg_name = 'OpenLP-{version}.dmg'.format(version=self.version)
|
||||||
|
dmg_title = 'OpenLP {version}'.format(version=self.version)
|
||||||
|
|
||||||
|
self.dmg_file = os.path.join(self.work_path, 'dist', dmg_name)
|
||||||
|
# Remove dmg if it exists.
|
||||||
|
if os.path.exists(self.dmg_file):
|
||||||
|
os.remove(self.dmg_file)
|
||||||
|
# Get size of the directory in bytes, convert to MB, and add padding
|
||||||
|
size = self._get_directory_size(self.dist_app_path)
|
||||||
|
size = size / (1000 * 1000)
|
||||||
|
size += 10
|
||||||
|
|
||||||
|
self._print('... %s' % self.script_path)
|
||||||
|
os.chdir(self.script_path)
|
||||||
|
self._run_command([self.dmgbuild_exe, '-s', self.dmg_settings_path, '-D', 'size={size}M'.format(size=size),
|
||||||
|
'-D', 'icon={icon_path}'.format(icon_path=self.icon_path),
|
||||||
|
'-D', 'app={dist_app_path}'.format(dist_app_path=self.dist_app_path), dmg_title, self.dmg_file],
|
||||||
|
'Unable to run dmgbuild')
|
||||||
|
|
||||||
|
# Dmg done.
|
||||||
|
self._print('Finished creating dmg file, resulting file: %s' % self.dmg_file)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
MacosxBuilder().main()
|
386
builders/windows-builder.py
Executable file
386
builders/windows-builder.py
Executable file
@ -0,0 +1,386 @@
|
|||||||
|
# -*- 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.4
|
||||||
|
|
||||||
|
PyQt5
|
||||||
|
You should already have this installed, OpenLP doesn't work without it. The
|
||||||
|
version the script expects is the packaged one available from River Bank
|
||||||
|
Computing.
|
||||||
|
|
||||||
|
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 should be a git clone of
|
||||||
|
https://github.com/matysek/pyinstaller branch develop
|
||||||
|
|
||||||
|
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".
|
||||||
|
|
||||||
|
Visual C++ 2008 Express Edition
|
||||||
|
This is to build pptviewlib.dll, the library for controlling the
|
||||||
|
PowerPointViewer.
|
||||||
|
|
||||||
|
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 sys
|
||||||
|
from shutil import copy, rmtree, move
|
||||||
|
from distutils import dir_util
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
from builder import Builder
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsBuilder(Builder):
|
||||||
|
"""
|
||||||
|
The :class:`WindowsBuilder` class encapsulates everything that is needed
|
||||||
|
to build a Windows installer.
|
||||||
|
"""
|
||||||
|
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 add_extra_args(self, parser):
|
||||||
|
"""
|
||||||
|
Add extra arguments to the command line argument parser
|
||||||
|
"""
|
||||||
|
parser.add_argument('--portable', metavar='PATH', default=None,
|
||||||
|
help='Specify the path to build the portable installation.')
|
||||||
|
|
||||||
|
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')
|
||||||
|
self.program_files = os.getenv('PROGRAMFILES')
|
||||||
|
|
||||||
|
def setup_paths(self):
|
||||||
|
"""
|
||||||
|
Set up a variety of paths that we use throughout the build process.
|
||||||
|
"""
|
||||||
|
self.portable_path = None
|
||||||
|
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')
|
||||||
|
self.pptviewlib_path = os.path.join(self.source_path, 'plugins', 'presentations', 'lib', 'pptviewlib')
|
||||||
|
if self.args.portable:
|
||||||
|
self.portable_path = os.path.abspath(self.args.portable)
|
||||||
|
|
||||||
|
def copy_extra_files(self):
|
||||||
|
"""
|
||||||
|
Copy all the Windows-specific files.
|
||||||
|
"""
|
||||||
|
self._print('Copying extra files for Windows...')
|
||||||
|
self._print_verbose('... OpenLP.ico')
|
||||||
|
copy(os.path.join(self.script_path, 'OpenLP.ico'), os.path.join(self.dist_path, 'OpenLP.ico'))
|
||||||
|
self._print_verbose('... LICENSE.txt')
|
||||||
|
copy(os.path.join(self.script_path, 'LICENSE.txt'), os.path.join(self.dist_path, 'LICENSE.txt'))
|
||||||
|
self._print_verbose('... psvince.dll')
|
||||||
|
copy(self.psvince, 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.mutoolbin and os.path.isfile(self.mutoolbin):
|
||||||
|
copy(os.path.join(self.mutoolbin), os.path.join(self.dist_path, 'mutool.exe'))
|
||||||
|
else:
|
||||||
|
self._print('... WARNING: mutool.exe not found')
|
||||||
|
self._print_verbose('... MediaInfo.exe')
|
||||||
|
if self.mediainfo_bin and os.path.isfile(self.mediainfo_bin):
|
||||||
|
copy(os.path.join(self.mediainfo_bin), 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, 'OpenLP.chm'], 'Error running HTML Help Workshop')
|
||||||
|
|
||||||
|
def build_package(self):
|
||||||
|
"""
|
||||||
|
Build the installer
|
||||||
|
"""
|
||||||
|
self.build_pptviewlib()
|
||||||
|
self.create_innosetup_file()
|
||||||
|
self.run_innosetup()
|
||||||
|
if self.portable_path and os.path.exists(self.portable_path):
|
||||||
|
self.run_portableapp_builder()
|
||||||
|
|
||||||
|
def build_pptviewlib(self):
|
||||||
|
"""
|
||||||
|
Build the PowerPoint Viewer DLL using Visual Studio.
|
||||||
|
"""
|
||||||
|
self._print('Building PPTVIEWLIB.DLL...')
|
||||||
|
if not os.path.exists(self.vcbuild_exe):
|
||||||
|
self._print('... WARNING: vcbuild.exe was not found, skipping building pptviewlib.dll')
|
||||||
|
return
|
||||||
|
self._run_command([self.vcbuild, '/rebuild', os.path.join(self.pptviewlib_path, 'pptviewlib.vcproj'),
|
||||||
|
'Release|Win32'], 'Error building pptviewlib.dll')
|
||||||
|
copy(os.path.join(self.pptviewlib_path, 'Release', 'pptviewlib.dll'), self.pptviewlib_path)
|
||||||
|
|
||||||
|
def create_innosetup_file(self):
|
||||||
|
"""
|
||||||
|
Create an InnoSetup file pointing to the branch being built.
|
||||||
|
"""
|
||||||
|
self._print('Creating Inno Setup file...')
|
||||||
|
with open(os.path.join(self.script_path, 'OpenLP.iss.default'), 'r') as input_file, \
|
||||||
|
open(os.path.join(self.script_path, '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)
|
||||||
|
output_file.write(content)
|
||||||
|
|
||||||
|
def run_innosetup(self):
|
||||||
|
"""
|
||||||
|
Run InnoSetup to create an installer.
|
||||||
|
"""
|
||||||
|
self._print('Running Inno Setup...')
|
||||||
|
os.chdir(self.script_path)
|
||||||
|
self._run_command([self.innosetup_exe, os.path.join(self.script_path, 'OpenLP.iss'), '/q'],
|
||||||
|
'Error running InnoSetup')
|
||||||
|
|
||||||
|
def create_portableapp_directory(self):
|
||||||
|
"""
|
||||||
|
Checks the PortableApp directory structure amd creates
|
||||||
|
missing subdirs
|
||||||
|
"""
|
||||||
|
self._print(' Checking PortableApps directory structure...')
|
||||||
|
launcher_path = os.path.join(self.portable_path, 'App', 'Appinfo', 'Launcher')
|
||||||
|
if not os.path.exists(launcher_path):
|
||||||
|
os.makedirs(launcher_path)
|
||||||
|
settings_path = os.path.join(self.portable_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(' Creating PortableApps appinfo file ...')
|
||||||
|
portable_version = self.version.replace('-', '.') + '.0' * (3 - self.version.count('.'))
|
||||||
|
with open(os.path.join(self.script_path, 'appinfo.ini.default'), 'r') as input_file, \
|
||||||
|
open(os.path.join(self.portable_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file:
|
||||||
|
content = input_file.read()
|
||||||
|
content = content.replace('%(display_version)s', self.version)
|
||||||
|
content = content.replace('%(package_version)s', portable_version)
|
||||||
|
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(' Clearing old files')
|
||||||
|
# Remove previous contents of portableapp build directory.
|
||||||
|
if os.path.exists(self.portable_path):
|
||||||
|
rmtree(self.portable_path)
|
||||||
|
self._print(' Creating PortableApps build directory')
|
||||||
|
# Copy the contents of the OpenLPPortable directory to the portable
|
||||||
|
# build directory.
|
||||||
|
dir_util.copy_tree(os.path.join(self.script_path, 'OpenLPPortable'), self.portable_path)
|
||||||
|
self.check_portableapp_directory()
|
||||||
|
self.create_portableapps_appinfo_file()
|
||||||
|
# Copy distribution files to portableapp build directory.
|
||||||
|
self._print(' Copying distribution files')
|
||||||
|
portable_app_path = os.path.join(self.portable_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(' 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(' Building PortableApps Launcher')
|
||||||
|
portableapps = Popen((self.portablelauncher, self.portable_path), stdout=PIPE)
|
||||||
|
code = portableapps.wait()
|
||||||
|
if code != 0:
|
||||||
|
raise Exception('Error creating PortableAppa Launcher')
|
||||||
|
# Build the portable installer.
|
||||||
|
self._print(' Building PortableApps Installer')
|
||||||
|
self._run_command([self.portableinstaller, self.portable_path], 'Error running PortableApps Installer')
|
||||||
|
portable_app = os.path.abspath(os.path.join(self.portable_path, '..',
|
||||||
|
'OpenLPPortable_%s.paf.exe' % self.version.replace('-', '.')))
|
||||||
|
if os.path.exists(portable_app):
|
||||||
|
move(portable_app, os.path.abspath(os.path.join(self.dist_path, '..')))
|
||||||
|
self._print(' PortableApp build complete')
|
||||||
|
else:
|
||||||
|
raise Exception('PortableApp failed to build')
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
"""
|
||||||
|
The main function to run the Windows builder.
|
||||||
|
"""
|
||||||
|
self._print_verbose('OpenLP main script: ......%s', self.openlp_script)
|
||||||
|
self._print_verbose('Script path: .............%s', os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
self._print_verbose('Branch path: .............%s', self.branch_path)
|
||||||
|
self._print_verbose('Source path: .............%s', self.source_path)
|
||||||
|
self._print_verbose('Dist path: ...............%s', self.dist_path)
|
||||||
|
self._print_verbose('Portable path: ...........%s', self.portable_path)
|
||||||
|
self._print_verbose('PyInstaller: .............%s', self.pyinstaller)
|
||||||
|
self._print_verbose('Documentation branch path:%s', self.documentation_path)
|
||||||
|
self._print_verbose('Help file build path: ....%s', self.helpfile_path)
|
||||||
|
self._print_verbose('Inno Setup path: .........%s', self.innosetup)
|
||||||
|
self._print_verbose('PortableApp Launcher......%s', self.portablelauncher)
|
||||||
|
self._print_verbose('PortableApp Installer.....%s', self.portableinstaller)
|
||||||
|
self._print_verbose('Windows resources: .......%s', self.winres_path)
|
||||||
|
self._print_verbose('VCBuild path: ............%s', self.vcbuild)
|
||||||
|
self._print_verbose('PPTVIEWLIB path: .........%s', self.pptviewlib_path)
|
||||||
|
self._print_verbose('Mutool binary ............%s', self.mutoolbin)
|
||||||
|
self._print_verbose('')
|
||||||
|
if not self.args.skip_update:
|
||||||
|
self.update_code()
|
||||||
|
self.build_pptviewlib()
|
||||||
|
self.run_pyinstaller()
|
||||||
|
self.write_version_file()
|
||||||
|
self.copy_default_theme()
|
||||||
|
self.copy_plugins()
|
||||||
|
self.copy_media_player()
|
||||||
|
if os.path.exists(self.manual_path):
|
||||||
|
self.run_sphinx()
|
||||||
|
self.run_htmlhelp()
|
||||||
|
else:
|
||||||
|
self._print('')
|
||||||
|
self._print('WARNING: Documentation trunk not found. Windows')
|
||||||
|
self._print(' Help file will not be included in build')
|
||||||
|
self._print('')
|
||||||
|
self.copy_windows_files()
|
||||||
|
if not self.args.skip_translations:
|
||||||
|
self.update_translations()
|
||||||
|
self.compile_translations()
|
||||||
|
self.create_innosetup_file()
|
||||||
|
self.run_innosetup()
|
||||||
|
if self.args.portable:
|
||||||
|
self.run_portableapp_builder()
|
||||||
|
self._print('Done.')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WindowsBuilder().main()
|
@ -1,717 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Copyright (c) 2008-2015 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 #
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
Mac OS X Build Script
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
This script is used to build the Mac OS X app bundle and pack it into dmg file.
|
|
||||||
For this script to work out of the box, it depends on a number of things:
|
|
||||||
|
|
||||||
Python 3.4
|
|
||||||
|
|
||||||
PyQt5
|
|
||||||
You should already have this installed, OpenLP doesn't work without it. The
|
|
||||||
version the script expects is the packaged one available from River Bank
|
|
||||||
Computing.
|
|
||||||
|
|
||||||
PyEnchant
|
|
||||||
This script expects the precompiled, installable version of PyEnchant to be
|
|
||||||
installed. You can find this on the PyEnchant site.
|
|
||||||
|
|
||||||
Sphinx
|
|
||||||
This is used to build the documentation. The documentation trunk must be at
|
|
||||||
the same directory level as OpenLP trunk and named "documentation".
|
|
||||||
|
|
||||||
PyInstaller
|
|
||||||
PyInstaller should be a git clone of either
|
|
||||||
https://github.com/matysek/pyinstaller branch python3 or
|
|
||||||
https://github.com/pyinstaller/pyinstaller branch python3
|
|
||||||
|
|
||||||
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".
|
|
||||||
|
|
||||||
macosx-builder.py
|
|
||||||
This script, of course. It should be in the "osx-package" directory
|
|
||||||
at the same level as OpenLP trunk.
|
|
||||||
|
|
||||||
Mako
|
|
||||||
Mako Templates for Python. This package is required for building the
|
|
||||||
remote plugin.
|
|
||||||
|
|
||||||
Alembic
|
|
||||||
Required for upgrading the databases used in OpenLP.
|
|
||||||
|
|
||||||
MuPDF
|
|
||||||
Required for PDF support in OpenLP. Install using macports, or use the
|
|
||||||
mudrawbin option in the config file to point to the mudraw binary.
|
|
||||||
|
|
||||||
MachOLib
|
|
||||||
Python library to analyze and edit Mach-O headers, the executable format
|
|
||||||
used by Mac OS X. Used to relink the mudraw binary from MuPDF to the bundled
|
|
||||||
libraries. Install using macports or pip.
|
|
||||||
|
|
||||||
config.ini.default
|
|
||||||
The configuration file contains settings of the version string to include
|
|
||||||
in the bundle as well as directory and file settings for different
|
|
||||||
purposes (e.g. PyInstaller location or installer background image)
|
|
||||||
|
|
||||||
To install everything you need to install MacPorts. Once MacPorts is installed
|
|
||||||
and up-to-date, run the following command::
|
|
||||||
|
|
||||||
$ sudo port install python34 py34-pyqt4 py34-sphinx py34-sqlalchemy \
|
|
||||||
py34-macholib py34-mako py34-alembic py34-enchant \
|
|
||||||
py34-beautifulsoup4 py34-lxml py34-nose
|
|
||||||
|
|
||||||
You may need to install chardet via pip::
|
|
||||||
|
|
||||||
$ sudo pip install chardet
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import plistlib
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from shutil import copy, copytree, rmtree
|
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
from configparser import ConfigParser
|
|
||||||
from argparse import ArgumentParser
|
|
||||||
|
|
||||||
from macholib.MachO import MachO
|
|
||||||
from macholib.util import flipwritable, in_system_path
|
|
||||||
|
|
||||||
|
|
||||||
def _which(command):
|
|
||||||
"""
|
|
||||||
Return absolute path to a command found on system PATH.
|
|
||||||
"""
|
|
||||||
if command.startswith('/'):
|
|
||||||
return command
|
|
||||||
for path in os.environ["PATH"].split(os.pathsep):
|
|
||||||
if os.access(os.path.join(path, command), os.X_OK):
|
|
||||||
return "%s/%s" % (path, command)
|
|
||||||
|
|
||||||
|
|
||||||
class MacosxBuilder(object):
|
|
||||||
"""
|
|
||||||
The :class:`MacosxBuilder` class encapsulates everything that is needed
|
|
||||||
to build a Mac OS X .dmg file.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.setup_args()
|
|
||||||
self.setup_system_paths()
|
|
||||||
self.read_config()
|
|
||||||
self.setup_executables()
|
|
||||||
self.setup_paths()
|
|
||||||
|
|
||||||
def _print(self, text, *args):
|
|
||||||
"""
|
|
||||||
Print stuff out. Later we might want to use a log file.
|
|
||||||
"""
|
|
||||||
if len(args) > 0:
|
|
||||||
text = text % tuple(args)
|
|
||||||
print(text)
|
|
||||||
|
|
||||||
def _print_verbose(self, text, *args):
|
|
||||||
"""
|
|
||||||
Print output, obeying "verbose" mode.
|
|
||||||
"""
|
|
||||||
if self.args.verbose:
|
|
||||||
self._print(text, *args)
|
|
||||||
|
|
||||||
def _run_command(self, cmd, err_msg):
|
|
||||||
"""
|
|
||||||
Run command in subprocess and print error message in case of Exception.
|
|
||||||
|
|
||||||
Return text from stdout.
|
|
||||||
"""
|
|
||||||
proc = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
|
||||||
output, error = proc.communicate()
|
|
||||||
code = proc.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
self._print(error)
|
|
||||||
raise Exception(err_msg)
|
|
||||||
return output
|
|
||||||
|
|
||||||
def _get_directory_size(self, directory):
|
|
||||||
"""
|
|
||||||
Return directory size - size of everything in the dir.
|
|
||||||
"""
|
|
||||||
dir_size = 0
|
|
||||||
for (path, dirs, files) in os.walk(directory):
|
|
||||||
for file in files:
|
|
||||||
filename = os.path.join(path, file)
|
|
||||||
dir_size += os.path.getsize(filename)
|
|
||||||
return dir_size
|
|
||||||
|
|
||||||
def _get_mountpoints(self):
|
|
||||||
"""
|
|
||||||
Return list of mounted disks on Mac.
|
|
||||||
"""
|
|
||||||
# Get the output in plist format.
|
|
||||||
paths = []
|
|
||||||
output = self._run_command([self.hdiutil, 'info', '-plist'], 'Detecting mount points failed.')
|
|
||||||
pl = plistlib.readPlistFromBytes(output)
|
|
||||||
for image in pl['images']:
|
|
||||||
for se in image['system-entities']:
|
|
||||||
if se.get('mount-point'):
|
|
||||||
paths.append(se.get('mount-point'))
|
|
||||||
|
|
||||||
return paths
|
|
||||||
|
|
||||||
def setup_args(self):
|
|
||||||
"""
|
|
||||||
Set up an argument parser and parse the command line arguments.
|
|
||||||
"""
|
|
||||||
parser = ArgumentParser()
|
|
||||||
parser.add_argument('-b', '--branch', metavar='BRANCH', dest='branch',
|
|
||||||
help='Specify the path to the branch you wish to build.')
|
|
||||||
parser.add_argument('--devel', dest='devel', action='store_true', default=False,
|
|
||||||
help='Development build does not have set icons for .dmg file '
|
|
||||||
'and .dmg filename contains bzr revision number.')
|
|
||||||
parser.add_argument('--release', dest='release', metavar='VERSION',
|
|
||||||
help='Build a release version of OpenLP with the version specified')
|
|
||||||
parser.add_argument('-d', '--documentation', metavar='DOCS', dest='docs',
|
|
||||||
help='Specify the path to the documentation branch.')
|
|
||||||
parser.add_argument('-c', '--config', metavar='CONFIG', dest='config',
|
|
||||||
help='Specify the path to the configuration file.',
|
|
||||||
default=os.path.abspath(os.path.join('.', 'config.ini.default')))
|
|
||||||
parser.add_argument('-u', '--skip-update', dest='skip_update', action='store_true', default=False,
|
|
||||||
help='Do NOT update the branch before building.')
|
|
||||||
parser.add_argument('-t', '--skip-translations', dest='skip_translations', action='store_true', default=False,
|
|
||||||
help='Do NOT update the language translation files.')
|
|
||||||
parser.add_argument('--transifex', dest='update_translations', action='store_true', default=False,
|
|
||||||
help='Update the language translation from Transifex.')
|
|
||||||
parser.add_argument('--transifex-user', dest='transifex_user', help='Transifex username.')
|
|
||||||
parser.add_argument('--transifex-pass', dest='transifex_pass', help='Transifex password.')
|
|
||||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False,
|
|
||||||
help='Print out additional information.')
|
|
||||||
self.args = parser.parse_args()
|
|
||||||
|
|
||||||
def read_config(self):
|
|
||||||
"""
|
|
||||||
Read the configuration from the configuration file.
|
|
||||||
"""
|
|
||||||
self.config = ConfigParser(defaults={
|
|
||||||
'here': self.script_path,
|
|
||||||
'projects': os.path.abspath(os.path.join(self.script_path, '..', '..')), })
|
|
||||||
self.config.read(os.path.abspath(self.args.config))
|
|
||||||
|
|
||||||
def setup_system_paths(self):
|
|
||||||
"""
|
|
||||||
Set up some system paths.
|
|
||||||
"""
|
|
||||||
self.script_path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
self.python = sys.executable
|
|
||||||
|
|
||||||
def setup_executables(self):
|
|
||||||
"""
|
|
||||||
Set up the paths to the executables we use.
|
|
||||||
"""
|
|
||||||
self.sphinx = _which(self.config.get('executables', 'sphinx'))
|
|
||||||
self.pyinstaller = os.path.abspath(self.config.get('executables', 'pyinstaller'))
|
|
||||||
self.lrelease = self.config.get('executables', 'lrelease')
|
|
||||||
self.dmgbuild = _which(self.config.get('executables', 'dmgbuild'))
|
|
||||||
self.mudraw_bin = _which(self.config.get('executables', 'mudrawbin'))
|
|
||||||
self.mutool_bin = _which(self.config.get('executables', 'mutoolbin'))
|
|
||||||
if self.mutool_bin:
|
|
||||||
self.mutool_lib = os.path.abspath(
|
|
||||||
os.path.join(os.path.dirname(self.mutool_bin), '..', 'lib', 'libjbig2dec.0.dylib'))
|
|
||||||
|
|
||||||
def setup_paths(self):
|
|
||||||
"""
|
|
||||||
Set up a variety of paths that we use throughout the build process.
|
|
||||||
"""
|
|
||||||
if self.args.branch:
|
|
||||||
self.branch_path = os.path.abspath(self.args.branch)
|
|
||||||
else:
|
|
||||||
self.branch_path = self.config.get('paths', 'branch')
|
|
||||||
if self.args.docs:
|
|
||||||
self.docs_path = os.path.abspath(self.args.docs)
|
|
||||||
else:
|
|
||||||
self.docs_path = self.config.get('paths', 'documentation')
|
|
||||||
if self.args.release:
|
|
||||||
self.version_number = self.args.release
|
|
||||||
self.work_path = os.path.abspath(os.path.join(self.branch_path, '..', 'OpenLP-' + self.version_number))
|
|
||||||
else:
|
|
||||||
self.version_number = None
|
|
||||||
self.work_path = self.branch_path
|
|
||||||
self.openlp_script = os.path.abspath(os.path.join(self.work_path, 'openlp.py'))
|
|
||||||
self.hooks_path = os.path.abspath(os.path.join(self.work_path, self.config.get('paths', 'hooks')))
|
|
||||||
self.app_icon = os.path.abspath(self.config.get('paths', 'app_icon'))
|
|
||||||
self.bundle_info = os.path.abspath(self.config.get('paths', 'bundle_info'))
|
|
||||||
self.i18n_utils = os.path.join(self.work_path, 'scripts', 'translation_utils.py')
|
|
||||||
self.source_path = os.path.join(self.work_path, 'openlp')
|
|
||||||
self.manual_path = os.path.join(self.docs_path, 'manual')
|
|
||||||
self.manual_build_path = os.path.join(self.manual_path, 'build')
|
|
||||||
self.i18n_path = os.path.join(self.work_path, 'resources', 'i18n')
|
|
||||||
self.build_path = os.path.join(self.work_path, 'build')
|
|
||||||
self.dist_app_path = os.path.join(self.work_path, 'dist', 'OpenLP.app')
|
|
||||||
self.dist_path = os.path.join(self.work_path, 'dist', 'OpenLP.app', 'Contents', 'MacOS')
|
|
||||||
self.dmg_settings = os.path.abspath(self.config.get('paths', 'dmg_settings'))
|
|
||||||
|
|
||||||
# Path to Qt translation files.
|
|
||||||
from PyQt5.QtCore import QCoreApplication
|
|
||||||
|
|
||||||
qt_plug_dir = str(list(QCoreApplication.libraryPaths())[0])
|
|
||||||
self.qt_translations_path = os.path.join(os.path.dirname(qt_plug_dir), 'translations')
|
|
||||||
|
|
||||||
def update_code(self):
|
|
||||||
"""
|
|
||||||
Update the code in the branch.
|
|
||||||
"""
|
|
||||||
os.chdir(self.branch_path)
|
|
||||||
self._print('Reverting any changes to the code...')
|
|
||||||
bzr = Popen(('bzr', 'revert'), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error reverting the code')
|
|
||||||
self._print('Updating the code...')
|
|
||||||
bzr = Popen(('bzr', 'update'), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error updating the code')
|
|
||||||
|
|
||||||
def export_release(self):
|
|
||||||
"""
|
|
||||||
Export a particular release
|
|
||||||
"""
|
|
||||||
if os.path.exists(self.work_path):
|
|
||||||
rmtree(self.work_path)
|
|
||||||
os.chdir(self.branch_path)
|
|
||||||
self._print('Exporting the release version...')
|
|
||||||
bzr = Popen(('bzr', 'export', '-r', 'tag:' + self.version_number, self.work_path), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error exporting the code')
|
|
||||||
|
|
||||||
def run_pyinstaller(self):
|
|
||||||
"""
|
|
||||||
Run PyInstaller on the branch to build an executable.
|
|
||||||
"""
|
|
||||||
self._print('Running PyInstaller...')
|
|
||||||
os.chdir(self.work_path)
|
|
||||||
cmd = [self.python,
|
|
||||||
self.pyinstaller,
|
|
||||||
'--clean',
|
|
||||||
'--noconfirm',
|
|
||||||
'--windowed',
|
|
||||||
'--noupx',
|
|
||||||
'--additional-hooks-dir', self.hooks_path,
|
|
||||||
'--runtime-hook', os.path.join(self.hooks_path, 'rthook_ssl.py'),
|
|
||||||
'-i', self.app_icon,
|
|
||||||
'-n', 'OpenLP',
|
|
||||||
self.openlp_script]
|
|
||||||
if not self.args.verbose:
|
|
||||||
cmd.append('--log-level=ERROR')
|
|
||||||
else:
|
|
||||||
cmd.append('--log-level=DEBUG')
|
|
||||||
if self.args.devel:
|
|
||||||
cmd.append('-d')
|
|
||||||
pyinstaller = Popen(cmd)
|
|
||||||
code = pyinstaller.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running PyInstaller')
|
|
||||||
|
|
||||||
def write_version_file(self):
|
|
||||||
"""
|
|
||||||
Write the version number to a file for reading once installed.
|
|
||||||
"""
|
|
||||||
self._print('Writing version file...')
|
|
||||||
os.chdir(self.branch_path)
|
|
||||||
bzr = Popen(('bzr', 'tags'), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running bzr tags')
|
|
||||||
lines = output.splitlines()
|
|
||||||
if len(lines) == 0:
|
|
||||||
tag = '0.0.0'
|
|
||||||
revision = '0'
|
|
||||||
else:
|
|
||||||
tag, revision = lines[-1].decode('utf-8').split()
|
|
||||||
bzr = Popen(('bzr', 'log', '--line', '-r', '-1'), stdout=PIPE)
|
|
||||||
output, error = bzr.communicate()
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running bzr log')
|
|
||||||
latest = output.decode('utf-8').split(':')[0]
|
|
||||||
self.version_string = '%s-bzr%s' % (tag, latest)
|
|
||||||
self.version_tag = tag
|
|
||||||
version_file = open(os.path.join(self.dist_path, '.version'), 'w')
|
|
||||||
# Release version does not contain revision in .dmg name.
|
|
||||||
if self.args.devel:
|
|
||||||
version_file.write(str(self.version_string))
|
|
||||||
else:
|
|
||||||
version_file.write(str(self.version_tag))
|
|
||||||
version_file.close()
|
|
||||||
|
|
||||||
def copy_default_theme(self):
|
|
||||||
"""
|
|
||||||
Copy the default theme to the correct directory for OpenLP.
|
|
||||||
"""
|
|
||||||
self._print('Copying default theme...')
|
|
||||||
source = os.path.join(self.source_path, 'core', 'lib', 'json')
|
|
||||||
dest = os.path.join(self.dist_path, 'core', 'lib', 'json')
|
|
||||||
for root, dirs, files in os.walk(source):
|
|
||||||
for filename in files:
|
|
||||||
if filename.endswith('.json'):
|
|
||||||
dest_path = os.path.join(dest, root[len(source) + 1:])
|
|
||||||
if not os.path.exists(dest_path):
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
|
||||||
|
|
||||||
def copy_plugins(self):
|
|
||||||
"""
|
|
||||||
Copy all the plugins to the correct directory so that OpenLP sees that
|
|
||||||
it has plugins.
|
|
||||||
"""
|
|
||||||
self._print('Copying plugins...')
|
|
||||||
source = os.path.join(self.source_path, 'plugins')
|
|
||||||
dest = os.path.join(self.dist_path, 'plugins')
|
|
||||||
for root, dirs, files in os.walk(source):
|
|
||||||
for filename in files:
|
|
||||||
if not filename.endswith('.pyc'):
|
|
||||||
dest_path = os.path.join(dest, root[len(source) + 1:])
|
|
||||||
if not os.path.exists(dest_path):
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
|
||||||
|
|
||||||
def copy_media_player(self):
|
|
||||||
"""
|
|
||||||
Copy the media players to the correct directory for OpenLP.
|
|
||||||
"""
|
|
||||||
self._print('Copying media player...')
|
|
||||||
source = os.path.join(self.source_path, 'core', 'ui', 'media')
|
|
||||||
dest = os.path.join(self.dist_path, 'core', 'ui', 'media')
|
|
||||||
for root, dirs, files in os.walk(source):
|
|
||||||
for filename in files:
|
|
||||||
if not filename.endswith('.pyc'):
|
|
||||||
dest_path = os.path.join(dest, root[len(source) + 1:])
|
|
||||||
if not os.path.exists(dest_path):
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
|
||||||
|
|
||||||
def copy_mac_bundle_files(self):
|
|
||||||
"""
|
|
||||||
Copy Info.plist and OpenLP.icns to app bundle.
|
|
||||||
"""
|
|
||||||
copy(self.app_icon, os.path.join(self.dist_app_path, 'Contents', 'Resources', os.path.basename(self.app_icon)))
|
|
||||||
# Add OpenLP version to Info.plist and put it to app bundle.
|
|
||||||
fr = open(self.bundle_info, 'r')
|
|
||||||
fw = open(os.path.join(self.dist_app_path, 'Contents', os.path.basename(self.bundle_info)), 'w')
|
|
||||||
text = fr.read()
|
|
||||||
if self.args.devel:
|
|
||||||
text = text % {'openlp_version': self.version_string}
|
|
||||||
else:
|
|
||||||
text = text % {'openlp_version': self.version_tag}
|
|
||||||
fw.write(text)
|
|
||||||
fr.close()
|
|
||||||
fw.close()
|
|
||||||
|
|
||||||
def copy_macosx_files(self):
|
|
||||||
"""
|
|
||||||
Copy all the OSX-specific files.
|
|
||||||
"""
|
|
||||||
self._print('Copying extra files for Mac OS X...')
|
|
||||||
self._print_verbose('... LICENSE.txt')
|
|
||||||
copy(os.path.join(self.script_path, 'LICENSE.txt'), os.path.join(self.dist_path, 'LICENSE.txt'))
|
|
||||||
self._print_verbose('... mudraw')
|
|
||||||
if self.mudraw_bin and os.path.isfile(self.mudraw_bin):
|
|
||||||
copy(self.mudraw_bin, os.path.join(self.dist_path, 'mudraw'))
|
|
||||||
self.relink_mudraw()
|
|
||||||
elif self.mutool_bin and os.path.isfile(self.mutool_bin):
|
|
||||||
copy(self.mutool_bin, os.path.join(self.dist_path, 'mutool'))
|
|
||||||
self.relink_mutool()
|
|
||||||
copy(self.mutool_lib, os.path.join(self.dist_path, 'libjbig2dec.0.dylib'))
|
|
||||||
else:
|
|
||||||
self._print('... WARNING: mudraw and mutool not found')
|
|
||||||
|
|
||||||
def relink_mudraw(self):
|
|
||||||
"""
|
|
||||||
Relink mudraw to bundled libraries
|
|
||||||
"""
|
|
||||||
self.relink_mupdf('mudraw')
|
|
||||||
|
|
||||||
def relink_mutool(self):
|
|
||||||
"""
|
|
||||||
Relink mudraw to bundled libraries
|
|
||||||
"""
|
|
||||||
self.relink_mupdf('mutool')
|
|
||||||
|
|
||||||
def relink_mupdf(self, bin_name):
|
|
||||||
"""
|
|
||||||
Relink mupdf to bundled libraries
|
|
||||||
"""
|
|
||||||
self._print('Linking {bin_name} with bundled libraries...'.format(bin_name=bin_name))
|
|
||||||
libname = os.path.join(self.dist_path, bin_name)
|
|
||||||
distname = os.path.relpath(self.dist_path, libname)
|
|
||||||
self._print_verbose('... {bin_name} path {path}'.format(bin_name=bin_name, path=libname))
|
|
||||||
|
|
||||||
# Determine how many directories up is the directory with shared
|
|
||||||
# dynamic libraries. '../'
|
|
||||||
# E.g. ./qt4_plugins/images/ -> ./../../
|
|
||||||
parent_dir = ''
|
|
||||||
# Check if distname is not only base filename.
|
|
||||||
if os.path.dirname(distname):
|
|
||||||
parent_level = len(os.path.dirname(distname).split(os.sep))
|
|
||||||
parent_dir = parent_level * (os.pardir + os.sep)
|
|
||||||
|
|
||||||
def match_func(pth):
|
|
||||||
"""
|
|
||||||
For system libraries leave path unchanged.
|
|
||||||
"""
|
|
||||||
# Match non system dynamic libraries.
|
|
||||||
if not in_system_path(pth):
|
|
||||||
# Use relative path to dependend dynamic libraries bases on
|
|
||||||
# location of the executable.
|
|
||||||
pth = os.path.join('@loader_path', parent_dir, os.path.basename(pth))
|
|
||||||
self._print_verbose('... %s', pth)
|
|
||||||
return pth
|
|
||||||
|
|
||||||
# Rewrite mach headers with @loader_path.
|
|
||||||
dll = MachO(libname)
|
|
||||||
dll.rewriteLoadCommands(match_func)
|
|
||||||
|
|
||||||
# Write changes into file.
|
|
||||||
# Write code is based on macholib example.
|
|
||||||
try:
|
|
||||||
self._print_verbose('... writing new library paths')
|
|
||||||
f = open(dll.filename, 'rb+')
|
|
||||||
for header in dll.headers:
|
|
||||||
f.seek(0)
|
|
||||||
dll.write(f)
|
|
||||||
f.seek(0, 2)
|
|
||||||
f.flush()
|
|
||||||
f.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def update_translations(self):
|
|
||||||
"""
|
|
||||||
Update the translations.
|
|
||||||
"""
|
|
||||||
self._print('Updating translations...')
|
|
||||||
if not self.config.has_section('transifex'):
|
|
||||||
raise Exception('No section named "transifex" found.')
|
|
||||||
if not self.config.has_option('transifex', 'username'):
|
|
||||||
raise Exception('No option named "username" found.')
|
|
||||||
if not self.config.has_option('transifex', 'password'):
|
|
||||||
raise Exception('No option named "password" found.')
|
|
||||||
if self.args.transifex_user:
|
|
||||||
username = self.args.transifex_user
|
|
||||||
else:
|
|
||||||
username = self.config.get('transifex', 'username')
|
|
||||||
if self.args.transifex_pass:
|
|
||||||
password = self.args.transifex_pass
|
|
||||||
else:
|
|
||||||
password = self.config.get('transifex', 'password')
|
|
||||||
os.chdir(os.path.split(self.i18n_utils)[0])
|
|
||||||
translation_utils = Popen([self.python, self.i18n_utils, '-qdpu', '-U', username, '-P', password])
|
|
||||||
code = translation_utils.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running translation_utils.py')
|
|
||||||
|
|
||||||
def compile_translations(self):
|
|
||||||
"""
|
|
||||||
Compile the translations for Qt.
|
|
||||||
"""
|
|
||||||
self._print('Compiling translations...')
|
|
||||||
files = os.listdir(self.i18n_path)
|
|
||||||
if not os.path.exists(os.path.join(self.dist_path, 'i18n')):
|
|
||||||
os.makedirs(os.path.join(self.dist_path, 'i18n'))
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.ts'):
|
|
||||||
self._print_verbose('... %s', file)
|
|
||||||
source_path = os.path.join(self.i18n_path, file)
|
|
||||||
dest_path = os.path.join(self.dist_path, 'i18n', file.replace('.ts', '.qm'))
|
|
||||||
lconvert = Popen((self.lrelease, '-compress', '-silent', source_path, '-qm', dest_path))
|
|
||||||
code = lconvert.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running lconvert on %s' % source_path)
|
|
||||||
self._print('Copying qm files...')
|
|
||||||
source = self.qt_translations_path
|
|
||||||
files = os.listdir(source)
|
|
||||||
for filename in files:
|
|
||||||
if filename.startswith('qt_') and filename.endswith('.qm'):
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(source, filename), os.path.join(self.dist_path, 'i18n', filename))
|
|
||||||
|
|
||||||
def run_sphinx(self):
|
|
||||||
"""
|
|
||||||
Run Sphinx to build an HTML Help project.
|
|
||||||
"""
|
|
||||||
self._print('Deleting previous manual build... %s', self.manual_build_path)
|
|
||||||
if os.path.exists(self.manual_build_path):
|
|
||||||
rmtree(self.manual_build_path)
|
|
||||||
self._print('Running Sphinx...')
|
|
||||||
os.chdir(self.manual_path)
|
|
||||||
sphinx = Popen((self.sphinx, '-b', 'applehelp', '-d', 'build/doctrees', 'source', 'build/applehelp'),
|
|
||||||
stdout=PIPE)
|
|
||||||
output, error = sphinx.communicate()
|
|
||||||
code = sphinx.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error running Sphinx')
|
|
||||||
self._print('Copying help file...')
|
|
||||||
source = os.path.join(self.manual_build_path, 'applehelp')
|
|
||||||
files = os.listdir(source)
|
|
||||||
for filename in files:
|
|
||||||
if filename.endswith('.help'):
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copytree(os.path.join(source, filename),
|
|
||||||
os.path.join(self.dist_app_path, 'Contents', 'Resources', filename))
|
|
||||||
|
|
||||||
def code_sign(self):
|
|
||||||
certificate = self.config.get('codesigning', 'certificate')
|
|
||||||
self._print('Checking for certificate...')
|
|
||||||
security = Popen(('security', 'find-certificate', '-c', certificate),
|
|
||||||
stdout=PIPE)
|
|
||||||
output, error = security.communicate()
|
|
||||||
code = security.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print('Could not find certificate \"%s\" in Keychain...', certificate)
|
|
||||||
self._print('Codesigning will not work without a certificate!!')
|
|
||||||
self._print(output)
|
|
||||||
else:
|
|
||||||
self._print('Codesigning app...')
|
|
||||||
codesign = Popen(('codesign', '--deep', '-s', certificate, self.dist_app_path))
|
|
||||||
output, error = codesign.communicate()
|
|
||||||
code = codesign.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error running codesign')
|
|
||||||
|
|
||||||
def create_dmg_file(self):
|
|
||||||
"""
|
|
||||||
Create .dmg file.
|
|
||||||
"""
|
|
||||||
self._print('Creating dmg file...')
|
|
||||||
|
|
||||||
# Release version does not contain revision in .dmg name.
|
|
||||||
if self.args.devel:
|
|
||||||
dmg_name = 'OpenLP-' + str(self.version_string) + '.dmg'
|
|
||||||
dmg_title = 'OpenLP {version}'.format(version=self.version_string)
|
|
||||||
else:
|
|
||||||
dmg_name = 'OpenLP-' + str(self.version_tag) + '.dmg'
|
|
||||||
dmg_title = 'OpenLP {version}'.format(version=self.version_tag)
|
|
||||||
|
|
||||||
self.dmg_file = os.path.join(self.work_path, 'dist', dmg_name)
|
|
||||||
# Remove dmg if it exists.
|
|
||||||
if os.path.exists(self.dmg_file):
|
|
||||||
os.remove(self.dmg_file)
|
|
||||||
# Create empty dmg file.
|
|
||||||
size = self._get_directory_size(self.dist_app_path) # in bytes.
|
|
||||||
size = size / (1000 * 1000) # Convert to megabytes.
|
|
||||||
size += 10 # Additional space in .dmg for other files.
|
|
||||||
|
|
||||||
self._print('... %s' % self.script_path)
|
|
||||||
os.chdir(self.script_path)
|
|
||||||
self._run_command([self.dmgbuild, '-s', self.dmg_settings, '-D', 'size={size}M'.format(size=size),
|
|
||||||
'-D', 'icon={icon_path}'.format(icon_path=self.app_icon),
|
|
||||||
'-D', 'app={dist_app_path}'.format(dist_app_path=self.dist_app_path), dmg_title, self.dmg_file],
|
|
||||||
'Unable to run dmgbuild')
|
|
||||||
|
|
||||||
# Jenkins integration.
|
|
||||||
# Continuous integration server needs to know the filename of dmg.
|
|
||||||
# Write java property file. For uploading dmg to openlp.
|
|
||||||
if self.args.devel:
|
|
||||||
fpath = os.path.join(self.branch_path, 'openlp.properties')
|
|
||||||
self._print('... writing property file for jenkins: %s' % fpath)
|
|
||||||
f = open(fpath, 'w')
|
|
||||||
f.write('OPENLP_DMGNAME=' + os.path.basename(self.dmg_file) + '\n')
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
# Dmg done.
|
|
||||||
self._print('Finished creating dmg file, resulting file: %s' % self.dmg_file)
|
|
||||||
|
|
||||||
def main(self):
|
|
||||||
"""
|
|
||||||
The main function to run the Mac OS X builder.
|
|
||||||
"""
|
|
||||||
self._print_verbose('OpenLP main script: ......%s', self.openlp_script)
|
|
||||||
self._print_verbose('Script path: .............%s', self.script_path)
|
|
||||||
self._print_verbose('Branch path: .............%s', self.branch_path)
|
|
||||||
self._print_verbose('Source path: .............%s', self.source_path)
|
|
||||||
self._print_verbose('"dist.app" path: .........%s', self.dist_app_path)
|
|
||||||
self._print_verbose('"dist" path: .............%s', self.dist_path)
|
|
||||||
self._print_verbose('"hooks" path: ............%s', self.hooks_path)
|
|
||||||
self._print_verbose('PyInstaller: .............%s', self.pyinstaller)
|
|
||||||
self._print_verbose('dmgbuild: ................%s', self.dmgbuild)
|
|
||||||
self._print_verbose('Documentation branch path:%s', self.docs_path)
|
|
||||||
if self.mudraw_bin:
|
|
||||||
self._print_verbose('mudraw binary ............%s', self.mudraw_bin)
|
|
||||||
elif self.mutool_bin:
|
|
||||||
self._print_verbose('mutool binary ............%s', self.mutool_bin)
|
|
||||||
else:
|
|
||||||
self._print_verbose('mutool/mudraw ............Not found')
|
|
||||||
self._print_verbose('')
|
|
||||||
if not self.args.skip_update:
|
|
||||||
self.update_code()
|
|
||||||
if self.args.release:
|
|
||||||
self.export_release()
|
|
||||||
self.run_pyinstaller()
|
|
||||||
self.write_version_file()
|
|
||||||
self.copy_mac_bundle_files()
|
|
||||||
self.copy_default_theme()
|
|
||||||
self.copy_plugins()
|
|
||||||
self.copy_media_player()
|
|
||||||
# TODO creating help on Mac
|
|
||||||
if os.path.exists(self.manual_path):
|
|
||||||
self.run_sphinx()
|
|
||||||
else:
|
|
||||||
self._print('')
|
|
||||||
self._print('WARNING: Documentation trunk not found. Mac OS X')
|
|
||||||
self._print(' Help file will not be included in build')
|
|
||||||
self._print('')
|
|
||||||
self.copy_macosx_files()
|
|
||||||
if not self.args.skip_translations:
|
|
||||||
if self.args.update_translations:
|
|
||||||
self.update_translations()
|
|
||||||
self.compile_translations()
|
|
||||||
self.code_sign()
|
|
||||||
self.create_dmg_file()
|
|
||||||
|
|
||||||
self._print('Done.')
|
|
||||||
raise SystemExit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
MacosxBuilder().main()
|
|
@ -1,653 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# OpenLP - Open Source Lyrics Projection #
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Copyright (c) 2008-2015 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.4
|
|
||||||
|
|
||||||
PyQt5
|
|
||||||
You should already have this installed, OpenLP doesn't work without it. The
|
|
||||||
version the script expects is the packaged one available from River Bank
|
|
||||||
Computing.
|
|
||||||
|
|
||||||
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 should be a git clone of
|
|
||||||
https://github.com/matysek/pyinstaller branch develop
|
|
||||||
|
|
||||||
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".
|
|
||||||
|
|
||||||
Visual C++ 2008 Express Edition
|
|
||||||
This is to build pptviewlib.dll, the library for controlling the
|
|
||||||
PowerPointViewer.
|
|
||||||
|
|
||||||
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 sys
|
|
||||||
from shutil import copy, rmtree, move
|
|
||||||
from distutils import dir_util
|
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
from configparser import ConfigParser
|
|
||||||
from argparse import ArgumentParser
|
|
||||||
|
|
||||||
|
|
||||||
class WindowsBuilder(object):
|
|
||||||
"""
|
|
||||||
The :class:`WindowsBuilder` class encapsulates everything that is needed
|
|
||||||
to build a Windows installer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.setup_args()
|
|
||||||
self.setup_system_paths()
|
|
||||||
self.read_config()
|
|
||||||
self.setup_executables()
|
|
||||||
self.setup_paths()
|
|
||||||
self.version = ''
|
|
||||||
|
|
||||||
def _print(self, text, *args):
|
|
||||||
"""
|
|
||||||
Print stuff out. Later we might want to use a log file.
|
|
||||||
"""
|
|
||||||
if len(args) > 0:
|
|
||||||
text = text % tuple(args)
|
|
||||||
print(text)
|
|
||||||
|
|
||||||
def _print_verbose(self, text, *args):
|
|
||||||
"""
|
|
||||||
Print output, obeying "verbose" mode.
|
|
||||||
"""
|
|
||||||
if self.args.verbose:
|
|
||||||
self._print(text, *args)
|
|
||||||
|
|
||||||
def setup_args(self):
|
|
||||||
"""
|
|
||||||
Set up an argument parser and parse the command line arguments.
|
|
||||||
"""
|
|
||||||
parser = ArgumentParser()
|
|
||||||
parser.add_argument('-b', '--branch', metavar='BRANCH', dest='branch',
|
|
||||||
help='Specify the path to the branch you wish to build.', default=None)
|
|
||||||
parser.add_argument('-d', '--documentation', metavar='DOCS', dest='docs', default=None,
|
|
||||||
help='Specify the path to the documentation branch.')
|
|
||||||
parser.add_argument('-c', '--config', metavar='CONFIG', dest='config',
|
|
||||||
help='Specify the path to the configuration file.',
|
|
||||||
default=os.path.abspath(os.path.join('.', 'config.ini')))
|
|
||||||
parser.add_argument('-u', '--skip-update', dest='skip_update', action='store_true', default=False,
|
|
||||||
help='Do NOT update the branch before building.')
|
|
||||||
parser.add_argument('-p', '--portable', metavar='PORTABLE', dest='portable', default=None,
|
|
||||||
help='Specify the path to build the portable installation.')
|
|
||||||
parser.add_argument('-t', '--skip-translations', dest='skip_translations', action='store_true', default=False,
|
|
||||||
help='Do NOT update the language translation files.')
|
|
||||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False,
|
|
||||||
help='Print out additional information.')
|
|
||||||
self.args = parser.parse_args()
|
|
||||||
|
|
||||||
def read_config(self):
|
|
||||||
"""
|
|
||||||
Read the configuration from the configuration file.
|
|
||||||
"""
|
|
||||||
self.config = ConfigParser(defaults={
|
|
||||||
'pyroot': self.python_root,
|
|
||||||
'progfiles': self.program_files,
|
|
||||||
'sitepackages': self.site_packages,
|
|
||||||
'here': self.script_path,
|
|
||||||
'projects': os.path.abspath(os.path.join(self.script_path, '..', '..')),
|
|
||||||
})
|
|
||||||
self.config.read(os.path.abspath(self.args.config))
|
|
||||||
|
|
||||||
def setup_system_paths(self):
|
|
||||||
"""
|
|
||||||
Set up some system paths.
|
|
||||||
"""
|
|
||||||
self.script_path = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
self.python = sys.executable
|
|
||||||
self.python_root = os.path.dirname(self.python)
|
|
||||||
self.site_packages = os.path.join(self.python_root, 'Lib', 'site-packages')
|
|
||||||
self.program_files = os.getenv('PROGRAMFILES')
|
|
||||||
|
|
||||||
def setup_executables(self):
|
|
||||||
"""
|
|
||||||
Set up the paths to the executables we use.
|
|
||||||
"""
|
|
||||||
self.innosetup = os.path.abspath(self.config.get('executables', 'innosetup'))
|
|
||||||
self.sphinx = os.path.abspath(self.config.get('executables', 'sphinx'))
|
|
||||||
self.pyinstaller = os.path.abspath(self.config.get('executables', 'pyinstaller'))
|
|
||||||
self.vcbuild = os.path.abspath(self.config.get('executables', 'vcbuild'))
|
|
||||||
self.hhc = os.path.abspath(self.config.get('executables', 'htmlhelp'))
|
|
||||||
self.psvince = os.path.abspath(self.config.get('executables', 'psvince'))
|
|
||||||
self.portableinstaller = os.path.abspath(self.config.get('executables', 'portableinstaller'))
|
|
||||||
self.portablelauncher = os.path.abspath(self.config.get('executables', 'portablelauncher'))
|
|
||||||
self.mutool_bin = os.path.abspath(self.config.get('executables', 'mutoolbin'))
|
|
||||||
self.mediainfo_bin = os.path.abspath(self.config.get('executables', 'mediainfobin'))
|
|
||||||
if os.path.exists(os.path.join(self.site_packages, 'PyQt5', 'bin')):
|
|
||||||
# Older versions of the PyQt5 Windows installer put their binaries
|
|
||||||
# in the "bin" directory
|
|
||||||
self.lrelease = os.path.join(self.site_packages, 'PyQt5', 'bin', 'lrelease.exe')
|
|
||||||
else:
|
|
||||||
# Newer versions of the PyQt5 Windows installer put their binaries
|
|
||||||
# in the base directory of the installation
|
|
||||||
self.lrelease = os.path.join(self.site_packages, 'PyQt5', 'lrelease.exe')
|
|
||||||
|
|
||||||
def setup_paths(self):
|
|
||||||
"""
|
|
||||||
Set up a variety of paths that we use throughout the build process.
|
|
||||||
"""
|
|
||||||
if self.args.branch:
|
|
||||||
branch_path = self.args.branch
|
|
||||||
else:
|
|
||||||
branch_path = self.config.get('paths', 'branch')
|
|
||||||
self.branch_path = os.path.abspath(branch_path)
|
|
||||||
if self.args.docs:
|
|
||||||
docs_path = self.args.docs
|
|
||||||
else:
|
|
||||||
docs_path = self.config.get('paths', 'documentation')
|
|
||||||
self.docs_path = os.path.abspath(docs_path)
|
|
||||||
if self.args.portable:
|
|
||||||
portable_path = self.args.portable
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
portable_path = self.config.get('paths', 'portable')
|
|
||||||
except:
|
|
||||||
portable_path = ''
|
|
||||||
if portable_path:
|
|
||||||
self.portable_path = os.path.abspath(portable_path)
|
|
||||||
self.args.portable = self.portable_path
|
|
||||||
else:
|
|
||||||
self.portable_path = ''
|
|
||||||
self.openlp_script = os.path.abspath(os.path.join(branch_path, 'openlp.py'))
|
|
||||||
self.hooks_path = os.path.abspath(self.config.get('paths', 'hooks'))
|
|
||||||
self.win32_icon = os.path.abspath(self.config.get('paths', 'win32icon'))
|
|
||||||
self.i18n_utils = os.path.join(self.branch_path, 'scripts', 'translation_utils.py')
|
|
||||||
self.source_path = os.path.join(self.branch_path, 'openlp')
|
|
||||||
self.manual_path = os.path.join(self.docs_path, 'manual')
|
|
||||||
self.manual_build_path = os.path.join(self.manual_path, 'build')
|
|
||||||
self.helpfile_path = os.path.join(self.manual_build_path, 'htmlhelp')
|
|
||||||
self.i18n_path = os.path.join(self.branch_path, 'resources', 'i18n')
|
|
||||||
self.winres_path = os.path.join(self.branch_path, 'resources', 'windows')
|
|
||||||
self.build_path = os.path.join(self.branch_path, 'build')
|
|
||||||
self.dist_path = os.path.join(self.branch_path, 'dist', 'OpenLP')
|
|
||||||
self.dist_path_pyinst_arg = os.path.join(self.branch_path, 'dist')
|
|
||||||
self.pptviewlib_path = os.path.join(self.source_path, 'plugins', 'presentations', 'lib', 'pptviewlib')
|
|
||||||
|
|
||||||
def update_code(self):
|
|
||||||
"""
|
|
||||||
Update the code in the branch.
|
|
||||||
"""
|
|
||||||
os.chdir(self.branch_path)
|
|
||||||
self._print('Reverting any changes to the code...')
|
|
||||||
bzr = Popen(('bzr', 'revert'), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error reverting the code')
|
|
||||||
self._print('Updating the code...')
|
|
||||||
bzr = Popen(('bzr', 'update'), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error updating the code')
|
|
||||||
|
|
||||||
def run_pyinstaller(self):
|
|
||||||
"""
|
|
||||||
Run PyInstaller on the branch to build an executable.
|
|
||||||
"""
|
|
||||||
self._print('Running PyInstaller...')
|
|
||||||
os.chdir(self.branch_path)
|
|
||||||
cmd = [self.python,
|
|
||||||
self.pyinstaller,
|
|
||||||
'--clean',
|
|
||||||
'--noconfirm',
|
|
||||||
'--windowed',
|
|
||||||
'--noupx',
|
|
||||||
'--additional-hooks-dir', self.hooks_path,
|
|
||||||
'--distpath', self.dist_path_pyinst_arg,
|
|
||||||
'-i', self.win32_icon,
|
|
||||||
'-p', self.branch_path,
|
|
||||||
'-n', 'OpenLP',
|
|
||||||
self.openlp_script]
|
|
||||||
if not self.args.verbose:
|
|
||||||
cmd.append('--log-level=ERROR')
|
|
||||||
else:
|
|
||||||
cmd.append('--log-level=DEBUG')
|
|
||||||
pyinstaller = Popen(cmd)
|
|
||||||
code = pyinstaller.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running PyInstaller')
|
|
||||||
|
|
||||||
def write_version_file(self):
|
|
||||||
"""
|
|
||||||
Write the version number to a file for reading once installed.
|
|
||||||
"""
|
|
||||||
self._print('Writing version file...')
|
|
||||||
os.chdir(self.branch_path)
|
|
||||||
bzr = Popen(('bzr', 'tags'), stdout=PIPE)
|
|
||||||
output = bzr.communicate()[0]
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running bzr tags')
|
|
||||||
lines = output.splitlines()
|
|
||||||
if len(lines) == 0:
|
|
||||||
tag = '0.0.0'
|
|
||||||
revision = '0'
|
|
||||||
else:
|
|
||||||
tag, revision = lines[-1].decode('utf-8').split()
|
|
||||||
bzr = Popen(('bzr', 'log', '--line', '-r', '-1'), stdout=PIPE)
|
|
||||||
output, error = bzr.communicate()
|
|
||||||
code = bzr.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running bzr log')
|
|
||||||
latest = output.decode('utf-8').split(':')[0]
|
|
||||||
version_string = latest == revision and tag or '%s-bzr%s' % (tag, latest)
|
|
||||||
# Save decimal version in case we need to do a portable build.
|
|
||||||
self.version = latest == revision and tag or '%s.%s' % (tag, latest)
|
|
||||||
version_file = open(os.path.join(self.dist_path, '.version'), 'w')
|
|
||||||
version_file.write(str(version_string))
|
|
||||||
version_file.close()
|
|
||||||
|
|
||||||
def copy_default_theme(self):
|
|
||||||
"""
|
|
||||||
Copy the default theme to the correct directory for OpenLP.
|
|
||||||
"""
|
|
||||||
self._print('Copying default theme...')
|
|
||||||
source = os.path.join(self.source_path, 'core', 'lib', 'json')
|
|
||||||
dest = os.path.join(self.dist_path, 'core', 'lib', 'json')
|
|
||||||
for root, dirs, files in os.walk(source):
|
|
||||||
for filename in files:
|
|
||||||
if filename.endswith('.json'):
|
|
||||||
dest_path = os.path.join(dest, root[len(source) + 1:])
|
|
||||||
if not os.path.exists(dest_path):
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
|
||||||
|
|
||||||
def copy_plugins(self):
|
|
||||||
"""
|
|
||||||
Copy all the plugins to the correct directory so that OpenLP sees that
|
|
||||||
it has plugins.
|
|
||||||
"""
|
|
||||||
self._print('Copying plugins...')
|
|
||||||
source = os.path.join(self.source_path, 'plugins')
|
|
||||||
dest = os.path.join(self.dist_path, 'plugins')
|
|
||||||
for root, dirs, files in os.walk(source):
|
|
||||||
for filename in files:
|
|
||||||
if not filename.endswith('.pyc'):
|
|
||||||
dest_path = os.path.join(dest, root[len(source) + 1:])
|
|
||||||
if not os.path.exists(dest_path):
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
|
||||||
|
|
||||||
def copy_media_player(self):
|
|
||||||
"""
|
|
||||||
Copy the media players to the correct directory for OpenLP.
|
|
||||||
"""
|
|
||||||
self._print('Copying media player...')
|
|
||||||
source = os.path.join(self.source_path, 'core', 'ui', 'media')
|
|
||||||
dest = os.path.join(self.dist_path, 'core', 'ui', 'media')
|
|
||||||
for root, dirs, files in os.walk(source):
|
|
||||||
for filename in files:
|
|
||||||
if not filename.endswith('.pyc'):
|
|
||||||
dest_path = os.path.join(dest, root[len(source) + 1:])
|
|
||||||
if not os.path.exists(dest_path):
|
|
||||||
os.makedirs(dest_path)
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(root, filename), os.path.join(dest_path, filename))
|
|
||||||
|
|
||||||
def copy_windows_files(self):
|
|
||||||
"""
|
|
||||||
Copy all the Windows-specific files.
|
|
||||||
"""
|
|
||||||
self._print('Copying extra files for Windows...')
|
|
||||||
self._print_verbose('... OpenLP.ico')
|
|
||||||
copy(os.path.join(self.script_path, 'OpenLP.ico'), os.path.join(self.dist_path, 'OpenLP.ico'))
|
|
||||||
self._print_verbose('... LICENSE.txt')
|
|
||||||
copy(os.path.join(self.script_path, 'LICENSE.txt'), os.path.join(self.dist_path, 'LICENSE.txt'))
|
|
||||||
self._print_verbose('... psvince.dll')
|
|
||||||
copy(self.psvince, 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_bin and os.path.isfile(self.mutool_bin):
|
|
||||||
copy(os.path.join(self.mutool_bin), os.path.join(self.dist_path, 'mutool.exe'))
|
|
||||||
else:
|
|
||||||
self._print('... WARNING: mutool.exe not found')
|
|
||||||
self._print_verbose('... MediaInfo.exe')
|
|
||||||
if self.mediainfo_bin and os.path.isfile(self.mediainfo_bin):
|
|
||||||
copy(os.path.join(self.mediainfo_bin), os.path.join(self.dist_path, 'MediaInfo.exe'))
|
|
||||||
else:
|
|
||||||
self._print('... WARNING: MediaInfo.exe not found')
|
|
||||||
|
|
||||||
def update_translations(self):
|
|
||||||
"""
|
|
||||||
Update the translations.
|
|
||||||
"""
|
|
||||||
self._print('Updating translations...')
|
|
||||||
if not self.config.has_section('transifex'):
|
|
||||||
raise Exception('No section named "transifex" found.')
|
|
||||||
if not self.config.has_option('transifex', 'username'):
|
|
||||||
raise Exception('No option named "username" found.')
|
|
||||||
if not self.config.has_option('transifex', 'password'):
|
|
||||||
raise Exception('No option named "password" found.')
|
|
||||||
username = self.config.get('transifex', 'username')
|
|
||||||
password = self.config.get('transifex', 'password')
|
|
||||||
os.chdir(os.path.dirname(self.i18n_utils))
|
|
||||||
translation_utils = Popen([self.python, self.i18n_utils, '-qdpu', '-U', username, '-P', password])
|
|
||||||
code = translation_utils.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running translation_utils.py')
|
|
||||||
|
|
||||||
def compile_translations(self):
|
|
||||||
"""
|
|
||||||
Compile the translations for Qt.
|
|
||||||
"""
|
|
||||||
self._print('Compiling translations...')
|
|
||||||
files = os.listdir(self.i18n_path)
|
|
||||||
if not os.path.exists(os.path.join(self.dist_path, 'i18n')):
|
|
||||||
os.makedirs(os.path.join(self.dist_path, 'i18n'))
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.ts'):
|
|
||||||
self._print_verbose('... %s', file)
|
|
||||||
source_path = os.path.join(self.i18n_path, file)
|
|
||||||
dest_path = os.path.join(self.dist_path, 'i18n', file.replace('.ts', '.qm'))
|
|
||||||
lconvert = Popen((self.lrelease, '-compress', '-silent', source_path, '-qm', dest_path))
|
|
||||||
code = lconvert.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running lconvert on %s' % source_path)
|
|
||||||
self._print('Copying qm files...')
|
|
||||||
source = os.path.join(self.site_packages, 'PyQt5', 'translations')
|
|
||||||
files = os.listdir(source)
|
|
||||||
for filename in files:
|
|
||||||
if filename.startswith('qt_') and filename.endswith('.qm') and len(filename) == 8:
|
|
||||||
self._print_verbose('... %s', filename)
|
|
||||||
copy(os.path.join(source, filename), os.path.join(self.dist_path, 'i18n', filename))
|
|
||||||
|
|
||||||
def run_sphinx(self):
|
|
||||||
"""
|
|
||||||
Run Sphinx to build an HTML Help project.
|
|
||||||
"""
|
|
||||||
self._print('Deleting previous help manual build... %s', self.manual_build_path)
|
|
||||||
if os.path.exists(self.manual_build_path):
|
|
||||||
rmtree(self.manual_build_path)
|
|
||||||
self._print('Running Sphinx...')
|
|
||||||
os.chdir(self.manual_path)
|
|
||||||
sphinx = Popen((self.sphinx, '-b', 'htmlhelp', '-d', 'build/doctrees', 'source', 'build/htmlhelp'), stdout=PIPE)
|
|
||||||
output, error = sphinx.communicate()
|
|
||||||
code = sphinx.wait()
|
|
||||||
if code != 0:
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error running Sphinx')
|
|
||||||
|
|
||||||
def run_htmlhelp(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'))
|
|
||||||
hhc = Popen((self.hhc, 'OpenLP.chm'), stdout=PIPE)
|
|
||||||
output, error = hhc.communicate()
|
|
||||||
code = hhc.wait()
|
|
||||||
if code != 1:
|
|
||||||
self._print('Exit code:', code)
|
|
||||||
self._print(output)
|
|
||||||
raise Exception('Error running HTML Help Workshop')
|
|
||||||
|
|
||||||
def create_innosetup_file(self):
|
|
||||||
"""
|
|
||||||
Create an InnoSetup file pointing to the branch being built.
|
|
||||||
"""
|
|
||||||
self._print('Creating Inno Setup file...')
|
|
||||||
input = open(os.path.join(self.script_path, 'OpenLP.iss.default'), 'r').read()
|
|
||||||
output = input.replace('%(branch)s', self.branch_path)
|
|
||||||
output = output.replace('%(display_version)s', self.version)
|
|
||||||
outfile = open(os.path.join(self.script_path, 'OpenLP.iss'), 'w')
|
|
||||||
outfile.write(output)
|
|
||||||
outfile.close()
|
|
||||||
|
|
||||||
def check_portableapp_directory(self):
|
|
||||||
"""
|
|
||||||
Checks the PortableApp directory structure amd creates
|
|
||||||
missing subdirs
|
|
||||||
"""
|
|
||||||
self._print(' Checking PortableApps directory structure...')
|
|
||||||
launcher_path = os.path.join(self.portable_path, 'App', 'Appinfo', 'Launcher')
|
|
||||||
if not os.path.exists(launcher_path):
|
|
||||||
os.makedirs(launcher_path)
|
|
||||||
settings_path = os.path.join(self.portable_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(' Creating PortableApps appinfo file ...')
|
|
||||||
portable_version = self.version + '.0' * (3 - self.version.count('.'))
|
|
||||||
input = open(os.path.join(self.script_path, 'appinfo.ini.default'), 'r').read()
|
|
||||||
output = input.replace('%(display_version)s', self.version)
|
|
||||||
output = output.replace('%(package_version)s', portable_version)
|
|
||||||
outfile = open(os.path.join(self.portable_path, 'App', 'Appinfo', 'appinfo.ini'), 'w')
|
|
||||||
outfile.write(output)
|
|
||||||
outfile.close()
|
|
||||||
|
|
||||||
def run_innosetup(self):
|
|
||||||
"""
|
|
||||||
Run InnoSetup to create an installer.
|
|
||||||
"""
|
|
||||||
self._print('Running Inno Setup...')
|
|
||||||
os.chdir(self.script_path)
|
|
||||||
innosetup = Popen((self.innosetup, os.path.join(self.script_path, 'OpenLP.iss'), '/q'))
|
|
||||||
code = innosetup.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running Inno Setup')
|
|
||||||
|
|
||||||
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(' Clearing old files')
|
|
||||||
# Remove previous contents of portableapp build directory.
|
|
||||||
if os.path.exists(self.portable_path):
|
|
||||||
rmtree(self.portable_path)
|
|
||||||
self._print(' Creating PortableApps build directory')
|
|
||||||
# Copy the contents of the OpenLPPortable directory to the portable
|
|
||||||
# build directory.
|
|
||||||
dir_util.copy_tree(os.path.join(self.script_path, 'OpenLPPortable'), self.portable_path)
|
|
||||||
self.check_portableapp_directory()
|
|
||||||
self.create_portableapps_appinfo_file()
|
|
||||||
# Copy distribution files to portableapp build directory.
|
|
||||||
self._print(' Copying distribution files')
|
|
||||||
portable_app_path = os.path.join(self.portable_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(' 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(' Building PortableApps Launcher')
|
|
||||||
portableapps = Popen((self.portablelauncher, self.portable_path), stdout=PIPE)
|
|
||||||
code = portableapps.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error creating PortableAppa Launcher')
|
|
||||||
# Build the portable installer.
|
|
||||||
self._print(' Building PortableApps Installer')
|
|
||||||
portableapps = Popen((self.portableinstaller, self.portable_path), stdout=PIPE)
|
|
||||||
code = portableapps.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error running PortableApps Installer')
|
|
||||||
portable_app = os.path.abspath(os.path.join(self.portable_path, '..',
|
|
||||||
'OpenLPPortable_%s.paf.exe' % self.version))
|
|
||||||
if os.path.exists(portable_app):
|
|
||||||
move(portable_app, os.path.abspath(os.path.join(self.dist_path, '..')))
|
|
||||||
self._print(' PortableApp build complete')
|
|
||||||
else:
|
|
||||||
raise Exception('PortableApp failed to build')
|
|
||||||
|
|
||||||
def build_pptviewlib(self):
|
|
||||||
"""
|
|
||||||
Build the PowerPoint Viewer DLL using Visual Studio.
|
|
||||||
"""
|
|
||||||
self._print('Building PPTVIEWLIB.DLL...')
|
|
||||||
if not os.path.exists(self.vcbuild):
|
|
||||||
self._print('... WARNING: vcbuild.exe was not found, skipping building pptviewlib.dll')
|
|
||||||
return
|
|
||||||
vcbuild = Popen((self.vcbuild, '/rebuild', os.path.join(self.pptviewlib_path, 'pptviewlib.vcproj'),
|
|
||||||
'Release|Win32'))
|
|
||||||
code = vcbuild.wait()
|
|
||||||
if code != 0:
|
|
||||||
raise Exception('Error building pptviewlib.dll')
|
|
||||||
copy(os.path.join(self.pptviewlib_path, 'Release', 'pptviewlib.dll'), self.pptviewlib_path)
|
|
||||||
|
|
||||||
def main(self):
|
|
||||||
"""
|
|
||||||
The main function to run the Windows builder.
|
|
||||||
"""
|
|
||||||
self._print_verbose('OpenLP main script: ......%s', self.openlp_script)
|
|
||||||
self._print_verbose('Script path: .............%s', os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
self._print_verbose('Branch path: .............%s', self.branch_path)
|
|
||||||
self._print_verbose('Source path: .............%s', self.source_path)
|
|
||||||
self._print_verbose('Dist path: ...............%s', self.dist_path)
|
|
||||||
self._print_verbose('Portable path: ...........%s', self.portable_path)
|
|
||||||
self._print_verbose('PyInstaller: .............%s', self.pyinstaller)
|
|
||||||
self._print_verbose('Documentation branch path:%s', self.docs_path)
|
|
||||||
self._print_verbose('Help file build path: ....%s', self.helpfile_path)
|
|
||||||
self._print_verbose('Inno Setup path: .........%s', self.innosetup)
|
|
||||||
self._print_verbose('PortableApp Launcher......%s', self.portablelauncher)
|
|
||||||
self._print_verbose('PortableApp Installer.....%s', self.portableinstaller)
|
|
||||||
self._print_verbose('Windows resources: .......%s', self.winres_path)
|
|
||||||
self._print_verbose('VCBuild path: ............%s', self.vcbuild)
|
|
||||||
self._print_verbose('PPTVIEWLIB path: .........%s', self.pptviewlib_path)
|
|
||||||
self._print_verbose('Mutool binary ............%s', self.mutool_bin)
|
|
||||||
self._print_verbose('')
|
|
||||||
if not self.args.skip_update:
|
|
||||||
self.update_code()
|
|
||||||
self.build_pptviewlib()
|
|
||||||
self.run_pyinstaller()
|
|
||||||
self.write_version_file()
|
|
||||||
self.copy_default_theme()
|
|
||||||
self.copy_plugins()
|
|
||||||
self.copy_media_player()
|
|
||||||
if os.path.exists(self.manual_path):
|
|
||||||
self.run_sphinx()
|
|
||||||
self.run_htmlhelp()
|
|
||||||
else:
|
|
||||||
self._print('')
|
|
||||||
self._print('WARNING: Documentation trunk not found. Windows')
|
|
||||||
self._print(' Help file will not be included in build')
|
|
||||||
self._print('')
|
|
||||||
self.copy_windows_files()
|
|
||||||
if not self.args.skip_translations:
|
|
||||||
self.update_translations()
|
|
||||||
self.compile_translations()
|
|
||||||
self.create_innosetup_file()
|
|
||||||
self.run_innosetup()
|
|
||||||
if self.args.portable:
|
|
||||||
self.run_portableapp_builder()
|
|
||||||
self._print('Done.')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
WindowsBuilder().main()
|
|
Loading…
Reference in New Issue
Block a user