From dbd59b9a3cc8bb4c25cbda58c684b4716093a1b6 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 3 Dec 2016 13:23:23 +0200 Subject: [PATCH 01/27] Refactor the builders so that we can inherit as much common code as possible --- builders/builder.py | 443 ++++++++++++++++++++++ builders/macosx-builder.py | 288 +++++++++++++++ builders/windows-builder.py | 386 +++++++++++++++++++ osx/macosx-builder.py | 717 ------------------------------------ windows/windows-builder.py | 653 -------------------------------- 5 files changed, 1117 insertions(+), 1370 deletions(-) create mode 100644 builders/builder.py create mode 100644 builders/macosx-builder.py create mode 100755 builders/windows-builder.py delete mode 100644 osx/macosx-builder.py delete mode 100755 windows/windows-builder.py diff --git a/builders/builder.py b/builders/builder.py new file mode 100644 index 0000000..c2338da --- /dev/null +++ b/builders/builder.py @@ -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() + + diff --git a/builders/macosx-builder.py b/builders/macosx-builder.py new file mode 100644 index 0000000..92d9d21 --- /dev/null +++ b/builders/macosx-builder.py @@ -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() diff --git a/builders/windows-builder.py b/builders/windows-builder.py new file mode 100755 index 0000000..8b9ad27 --- /dev/null +++ b/builders/windows-builder.py @@ -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() diff --git a/osx/macosx-builder.py b/osx/macosx-builder.py deleted file mode 100644 index 57ef00d..0000000 --- a/osx/macosx-builder.py +++ /dev/null @@ -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() diff --git a/windows/windows-builder.py b/windows/windows-builder.py deleted file mode 100755 index ba6b994..0000000 --- a/windows/windows-builder.py +++ /dev/null @@ -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() From 05dde36a9e90c2b6d8612a3cce63b34ef7808f55 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 3 Dec 2016 17:22:08 +0200 Subject: [PATCH 02/27] Fix up the rest of the macOS builder --- builders/builder.py | 50 ++++++++++++++++++++++--------------- builders/macosx-builder.py | 26 +++++++++++-------- builders/windows-builder.py | 0 3 files changed, 46 insertions(+), 30 deletions(-) mode change 100755 => 100644 builders/windows-builder.py diff --git a/builders/builder.py b/builders/builder.py index c2338da..dc151fb 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -31,22 +31,30 @@ 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.' + '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): +def _which(program): """ 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) + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath and is_exe(program): + return program + else: + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None class Builder(object): @@ -168,7 +176,11 @@ class Builder(object): """ self._print_verbose('Executables:') for option in self.config.options('executables'): - value = _which(self.config.get('executables', option)) + value = self.config.get('executables', option) + if not value.strip(): + value = None + else: + value = _which(value) setattr(self, '{option}_exe'.format(option=option), value) self._print_verbose(' {option:.<30}: {value}'.format(option=option + ' ', value=value)) @@ -233,6 +245,7 @@ class Builder(object): Run PyInstaller on the branch to build an executable. """ self._print('Running PyInstaller...') + copy(os.path.join(self.work_path, 'openlp.py'), self.openlp_script) os.chdir(self.work_path) cmd = [self.python, self.pyinstaller_exe, @@ -249,7 +262,7 @@ class Builder(object): cmd.append('--log-level=ERROR') else: cmd.append('--log-level=DEBUG') - if self.args.devel: + if not self.args.release: cmd.append('-d') self._run_command(cmd, 'Error running PyInstaller') @@ -381,10 +394,10 @@ class Builder(object): """ Run Sphinx to build the manual """ - self._print('Deleting previous help manual build... %s', self.manual_build_path) + self._print('Running Sphinx...') + self._print_verbose(' 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)] @@ -420,8 +433,6 @@ class Builder(object): 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: @@ -429,13 +440,12 @@ class Builder(object): self._print('WARNING: Documentation trunk not found') self._print(' Help file will not be included in build') self._print('') - self.copy_macosx_files() + self.copy_extra_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.build_package() self._print('Done.') raise SystemExit() diff --git a/builders/macosx-builder.py b/builders/macosx-builder.py index 92d9d21..a50b9d9 100644 --- a/builders/macosx-builder.py +++ b/builders/macosx-builder.py @@ -119,14 +119,20 @@ class MacosxBuilder(Builder): dir_size += os.path.getsize(filename) return dir_size + def get_sphinx_build(self): + """ + The type of build Sphinx should be doing + """ + return 'applehelp' + 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')) + if hasattr(self, 'mutool_exe'): + self.mutool_lib = os.path.abspath( + os.path.join(os.path.dirname(self.mutool_exe), '..', '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') @@ -157,15 +163,15 @@ class MacosxBuilder(Builder): """ 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')) + copy(self.license_path, 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')) + if hasattr(self, 'mudraw_exe') and self.mudraw_exe and os.path.isfile(self.mudraw_exe): + copy(self.mudraw_exe, 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')) + elif hasattr(self, 'mutool_exe') and self.mutool_exe and os.path.isfile(self.mutool_exe): + copy(self.mutool_exe, os.path.join(self.dist_path, 'mutool')) self.relink_mutool() - copy(self.mutoollib, os.path.join(self.dist_path, 'libjbig2dec.0.dylib')) + copy(self.mutool_lib, os.path.join(self.dist_path, 'libjbig2dec.0.dylib')) else: self._print('... WARNING: mudraw and mutool not found') @@ -274,7 +280,7 @@ class MacosxBuilder(Builder): size += 10 self._print('... %s' % self.script_path) - os.chdir(self.script_path) + os.chdir(os.path.dirname(self.dmg_settings_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], diff --git a/builders/windows-builder.py b/builders/windows-builder.py old mode 100755 new mode 100644 From f4f7fc9cad359ef80f322544ce5ffd81e6e33630 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 5 Dec 2016 20:23:50 +0200 Subject: [PATCH 03/27] A few fixes to the macOS builder, and fix up the Windows builder --- builders/macosx-builder.py | 224 ++++++++++++++-------------- builders/windows-builder.py | 287 +++++++++++++++--------------------- windows/config.ini.default | 6 +- 3 files changed, 234 insertions(+), 283 deletions(-) diff --git a/builders/macosx-builder.py b/builders/macosx-builder.py index a50b9d9..11331bb 100644 --- a/builders/macosx-builder.py +++ b/builders/macosx-builder.py @@ -103,7 +103,7 @@ from macholib.util import flipwritable, in_system_path from builder import Builder -class MacosxBuilder(Builder): +class MacOSXBuilder(Builder): """ The :class:`MacosxBuilder` class encapsulates everything that is needed to build a Mac OS X .dmg file. @@ -119,75 +119,7 @@ class MacosxBuilder(Builder): dir_size += os.path.getsize(filename) return dir_size - def get_sphinx_build(self): - """ - The type of build Sphinx should be doing - """ - return 'applehelp' - - def setup_paths(self): - """ - Extra setup to run - """ - super().setup_paths() - if hasattr(self, 'mutool_exe'): - self.mutool_lib = os.path.abspath( - os.path.join(os.path.dirname(self.mutool_exe), '..', '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(self.license_path, os.path.join(self.dist_path, 'LICENSE.txt')) - self._print_verbose('... mudraw') - if hasattr(self, 'mudraw_exe') and self.mudraw_exe and os.path.isfile(self.mudraw_exe): - copy(self.mudraw_exe, os.path.join(self.dist_path, 'mudraw')) - self.relink_mudraw() - elif hasattr(self, 'mutool_exe') and self.mutool_exe and os.path.isfile(self.mutool_exe): - copy(self.mutool_exe, 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): + def _relink_mupdf(self, bin_name): """ Relink mupdf to bundled libraries """ @@ -233,6 +165,116 @@ class MacosxBuilder(Builder): except Exception: pass + 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 _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(self.license_path, os.path.join(self.dist_path, 'LICENSE.txt')) + self._print_verbose('... mudraw') + if hasattr(self, 'mudraw_exe') and self.mudraw_exe and os.path.isfile(self.mudraw_exe): + copy(self.mudraw_exe, os.path.join(self.dist_path, 'mudraw')) + self._relink_mudraw() + elif hasattr(self, 'mutool_exe') and self.mutool_exe and os.path.isfile(self.mutool_exe): + copy(self.mutool_exe, 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 _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_verbose('... %s' % self.script_path) + os.chdir(os.path.dirname(self.dmg_settings_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) + + def get_platform(self): + """ + Return the plaform we're building for + """ + return 'Mac OS X' + + def get_sphinx_build(self): + """ + The type of build Sphinx should be doing + """ + return 'applehelp' + + def setup_paths(self): + """ + Extra setup to run + """ + super().setup_paths() + if hasattr(self, 'mutool_exe'): + self.mutool_lib = os.path.abspath( + os.path.join(os.path.dirname(self.mutool_exe), '..', '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 after_run_sphinx(self): """ Run Sphinx to build an HTML Help project. @@ -250,45 +292,9 @@ class MacosxBuilder(Builder): """ 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(os.path.dirname(self.dmg_settings_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) + self._code_sign() + self._create_dmg() if __name__ == '__main__': - MacosxBuilder().main() + MacOSXBuilder().main() diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 8b9ad27..61e9da1 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -19,7 +19,6 @@ # 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 -------------------- @@ -39,7 +38,7 @@ PyEnchant installed. You can find this on the PyEnchant site. Inno Setup 5 - Inno Setup should be installed into "C:\%PROGRAMFILES%\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 @@ -58,7 +57,7 @@ Bazaar 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". + structure like this: "openlp\\branch-name". Visual C++ 2008 Express Edition This is to build pptviewlib.dll, the library for controlling the @@ -73,14 +72,14 @@ psvince.dll 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) + 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 + 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 @@ -116,12 +115,8 @@ Portable App Builds """ 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 shutil import copy, move, rmtree from builder import Builder @@ -131,6 +126,107 @@ class WindowsBuilder(Builder): The :class:`WindowsBuilder` class encapsulates everything that is needed to build a Windows installer. """ + 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_exe, '/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. + self._create_portableapp_directory() + self._create_portableapps_appinfo_file() + dir_util.copy_tree(os.path.join(self.script_path, 'OpenLPPortable'), self.portable_path) + # 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') + self_run_command([self.portablelauncher_exe, self.portable_path], 'Error creating PortableAppa Launcher') + # Build the portable installer. + self._print(' Building PortableApps Installer') + self._run_command([self.portableinstaller_exe, 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 get_platform(self): """ Return the platform we're building for @@ -195,20 +291,20 @@ class WindowsBuilder(Builder): 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')) + copy(self.psvince_exe, os.path.join(self.dist_path, 'psvince.dll')) if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')): self._print_verbose('... OpenLP.chm') copy(os.path.join(self.helpfile_path, 'OpenLP.chm'), os.path.join(self.dist_path, 'OpenLP.chm')) else: self._print('... WARNING: Windows help file not found') self._print_verbose('... mutool.exe') - if self.mutoolbin and os.path.isfile(self.mutoolbin): - copy(os.path.join(self.mutoolbin), os.path.join(self.dist_path, 'mutool.exe')) + if self.mutool_exe and os.path.isfile(self.mutool_exe): + copy(os.path.join(self.mutool_exe), os.path.join(self.dist_path, 'mutool.exe')) else: self._print('... WARNING: mutool.exe not found') self._print_verbose('... MediaInfo.exe') - if self.mediainfo_bin and os.path.isfile(self.mediainfo_bin): - copy(os.path.join(self.mediainfo_bin), os.path.join(self.dist_path, 'MediaInfo.exe')) + if self.mediainfo_exe and os.path.isfile(self.mediainfo_exe): + copy(os.path.join(self.mediainfo_exe), os.path.join(self.dist_path, 'MediaInfo.exe')) else: self._print('... WARNING: MediaInfo.exe not found') @@ -218,168 +314,17 @@ class WindowsBuilder(Builder): """ 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') + self._run_command([self.htmlhelp_exe, 'OpenLP.chm'], 'Error running HTML Help Workshop') def build_package(self): """ Build the installer """ - self.build_pptviewlib() - self.create_innosetup_file() - self.run_innosetup() + 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.') + self._run_portableapp_builder() if __name__ == '__main__': diff --git a/windows/config.ini.default b/windows/config.ini.default index 04c786b..925baf9 100644 --- a/windows/config.ini.default +++ b/windows/config.ini.default @@ -8,13 +8,13 @@ psvince = %(here)s\psvince.dll lrelease = %(sitepackages)s\PyQt5\bin\lrelease.exe portablelauncher = %(progfiles)s\PortableApps.comLauncher\PortableApps.comLauncherGenerator.exe portableinstaller = %(progfiles)s\PortableApps.comInstaller\PortableApps.comInstaller.exe -mutoolbin = %(here)s\..\mupdf-1.9a-windows\mutool.exe -mediainfobin = %(here)s\..\MediaInfo\MediaInfo.exe +mutool = %(here)s\..\mupdf-1.9a-windows\mutool.exe +mediainfo = %(here)s\..\MediaInfo\MediaInfo.exe [paths] branch = %(projects)s\trunk documentation = %(projects)s\documentation -win32icon = %(here)s\OpenLP.ico +icon = %(here)s\OpenLP.ico hooks = %(here)s\..\pyinstaller-hooks portable = %(projects)s\OpenLPPortable From 4cdcc9b7b114fc5d577a1e9af66536f398fabeff Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 5 Dec 2016 23:18:21 +0200 Subject: [PATCH 04/27] Translation locations are different on different platforms --- builders/builder.py | 30 ++++++++++++++---------------- builders/macosx-builder.py | 8 ++++++++ builders/windows-builder.py | 6 ++++++ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index dc151fb..52b3b6d 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -125,6 +125,18 @@ class Builder(object): """ return 'html' + def get_qt_translations_path(self): + """ + Return the path to Qt's translation files + """ + return '' + + def add_extra_args(self, parser): + """ + Add extra arguments to the argument parser + """ + pass + def setup_args(self): """ Set up an argument parser and parse the command line arguments. @@ -150,12 +162,6 @@ class Builder(object): 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. @@ -209,12 +215,6 @@ class Builder(object): 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 @@ -382,10 +382,8 @@ class Builder(object): 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: + self._print('Copying Qt translation files...') + for filename in os.listdir(self.get_qt_translations_path()): 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)) diff --git a/builders/macosx-builder.py b/builders/macosx-builder.py index 11331bb..e266995 100644 --- a/builders/macosx-builder.py +++ b/builders/macosx-builder.py @@ -257,6 +257,14 @@ class MacOSXBuilder(Builder): """ return 'applehelp' + def get_qt_translations_path(self): + """ + Return the path to Qt translation files on macOS + """ + from PyQt5.QtCore import QCoreApplication + qt_library_path = QCoreApplication.libraryPaths()[0] + return os.path.join(os.path.dirname(qt_library_path), 'translations') + def setup_paths(self): """ Extra setup to run diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 61e9da1..8153f17 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -252,6 +252,12 @@ class WindowsBuilder(Builder): """ return "htmlhelp" + def get_qt_translations_path(self): + """ + Return the path to Qt translation files on macOS + """ + return os.path.join(self.site_packages, 'PyQt5', 'translations') + def add_extra_args(self, parser): """ Add extra arguments to the command line argument parser From 751e771bb17dbee11fd547f77ef804611edd8ff3 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 5 Dec 2016 23:22:29 +0200 Subject: [PATCH 05/27] Update AppVeyor config --- windows/config-appveyor.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/config-appveyor.ini b/windows/config-appveyor.ini index 73f548b..d341c77 100644 --- a/windows/config-appveyor.ini +++ b/windows/config-appveyor.ini @@ -8,13 +8,13 @@ psvince = %(here)s\psvince.dll lrelease = %(sitepackages)s\PyQt5\bin\lrelease.exe portablelauncher = %(here)s\..\..\PortableApps.comLauncher\PortableApps.comLauncherGenerator.exe portableinstaller = %(here)s\..\..\PortableApps.comInstaller\PortableApps.comInstaller.exe -mutoolbin = %(here)s\..\..\mupdf-1.9a-windows\mutool.exe -mediainfobin = %(here)s\..\..\MediaInfo\MediaInfo.exe +mutool = %(here)s\..\..\mupdf-1.9a-windows\mutool.exe +mediainfo = %(here)s\..\..\MediaInfo\MediaInfo.exe [paths] branch = %(projects)s\trunk documentation = %(projects)s\documentation -win32icon = %(here)s\OpenLP.ico +icon = %(here)s\OpenLP.ico hooks = %(here)s\..\pyinstaller-hooks portable = %(projects)s\OpenLPPortable From 5ce4f143faa62d42ea19e3012c9613d752c993c9 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 5 Dec 2016 23:36:20 +0200 Subject: [PATCH 06/27] %(here)s should be the directory of the config file --- builders/builder.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index 52b3b6d..c19766e 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -117,7 +117,7 @@ class Builder(object): """ Build some default values for the config file """ - return {'here': self.script_path} + return {'here': os.path.dirname(self.config_path)} def get_sphinx_build(self): """ @@ -167,14 +167,15 @@ class Builder(object): Read the configuration from the configuration file. """ self.config = ConfigParser(defaults=self.get_config_defaults()) - self.config.read(os.path.abspath(self.args.config)) + self.config.read(self.config_path) 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.script_path = os.path.dirname(os.path.abspath(__file__)) + self.config_path = os.path.abspath(self.args.config) def setup_executables(self): """ From 6e6f6ddf2d245058ba3b5d8d1576c19208a8b530 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 5 Dec 2016 23:39:17 +0200 Subject: [PATCH 07/27] Absolute paths --- builders/builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index c19766e..bb76103 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -46,8 +46,8 @@ def _which(program): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) - if fpath and is_exe(program): - return program + if fpath and is_exe(os.path.abspath(program)): + return os.path.abspath(program) else: for path in os.environ['PATH'].split(os.pathsep): path = path.strip('"') From 71f000a5c8a29a6ae4d811f3beba0408df02a86c Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 00:57:59 +0200 Subject: [PATCH 08/27] Make stdout a string instead of bytes, update AppVeyor config --- builders/builder.py | 26 +++++++++++++------------- windows/config-appveyor.ini | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index bb76103..b0a0c54 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -90,7 +90,7 @@ class Builder(object): Return text from stdout. """ - proc = Popen(cmd, stdout=PIPE, stderr=PIPE) + proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) output, error = proc.communicate() code = proc.wait() if code != 0: @@ -280,9 +280,9 @@ class Builder(object): tag = '0.0.0' revision = '0' else: - tag, revision = lines[-1].decode('utf-8').split() + tag, revision = lines[-1].split() output = self._bzr('log', self.branch_path, ['--line', '-r', '-1'], 'Error running bzr log') - revision = output.decode('utf-8').split(':')[0] + revision = output.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: @@ -295,7 +295,7 @@ class Builder(object): 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 root, _, files in os.walk(source): for filename in files: if filename.endswith('.json'): dest_path = os.path.join(dest, root[len(source) + 1:]) @@ -312,7 +312,7 @@ class Builder(object): 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 root, _, files in os.walk(source): for filename in files: if not filename.endswith('.pyc'): dest_path = os.path.join(dest, root[len(source) + 1:]) @@ -328,7 +328,7 @@ class Builder(object): 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 root, _, files in os.walk(source): for filename in files: if not filename.endswith('.pyc'): dest_path = os.path.join(dest, root[len(source) + 1:]) @@ -373,18 +373,18 @@ class Builder(object): 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')) + for filename in os.listdir(self.i18n_path): + if filename.endswith('.ts'): + self._print_verbose('... %s', filename) + source_path = os.path.join(self.i18n_path, filename) + dest_path = os.path.join(self.dist_path, 'i18n', filename.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 Qt translation files...') - for filename in os.listdir(self.get_qt_translations_path()): + source = self.get_qt_translations_path() + for filename in os.listdir(source): 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)) diff --git a/windows/config-appveyor.ini b/windows/config-appveyor.ini index d341c77..a6f4baa 100644 --- a/windows/config-appveyor.ini +++ b/windows/config-appveyor.ini @@ -1,7 +1,7 @@ [executables] innosetup = %(progfiles)s\Inno Setup 5\ISCC.exe sphinx = %(pyroot)s\Scripts\sphinx-build.exe -pyinstaller = %(here)s\..\..\pyinstaller-develop\pyinstaller.py +pyinstaller = %(here)s\..\..\PyInstaller-3.2\pyinstaller.py vcbuild = %(progfiles)s\Microsoft Visual Studio 9.0\VC\vcpackages\vcbuild.exe htmlhelp = %(progfiles)s\HTML Help Workshop\hhc.exe psvince = %(here)s\psvince.dll From 0a2a90794e5610fb622c33fac1b4e214639bce67 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 21:03:42 +0200 Subject: [PATCH 09/27] Make provision for Microsoft's exceptional wisdom in making an exit code of 1 be a success --- builders/builder.py | 4 ++-- builders/windows-builder.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index b0a0c54..6c13e40 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -84,7 +84,7 @@ class Builder(object): if self.args.verbose: self._print(text, *args) - def _run_command(self, cmd, err_msg): + def _run_command(self, cmd, err_msg, exit_code=0): """ Run command in subprocess and print error message in case of Exception. @@ -93,7 +93,7 @@ class Builder(object): proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True) output, error = proc.communicate() code = proc.wait() - if code != 0: + if code != exit_code: self._print(output) self._print(error) raise Exception(err_msg) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 8153f17..9c85636 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -320,7 +320,7 @@ class WindowsBuilder(Builder): """ self._print('Running HTML Help Workshop...') os.chdir(os.path.join(self.manual_build_path, 'htmlhelp')) - self._run_command([self.htmlhelp_exe, 'OpenLP.chm'], 'Error running HTML Help Workshop') + self._run_command([self.htmlhelp_exe, 'OpenLP.chm'], 'Error running HTML Help Workshop', exit_code=1) def build_package(self): """ From 2f7e689c99045fbe4fbd44b5c2b4baabb6df98ab Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 21:43:40 +0200 Subject: [PATCH 10/27] Add some more debug logging to figure out what's going on with the paths --- builders/builder.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index 6c13e40..df38cac 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -176,38 +176,51 @@ class Builder(object): self.python = sys.executable self.script_path = os.path.dirname(os.path.abspath(__file__)) self.config_path = os.path.abspath(self.args.config) + self._print_verbose('System paths:') + self._print_verbose(' {:.<20}: {}'.format('python: ', self.python)) + self._print_verbose(' {:.<20}: {}'.format('script: ', self.script_path)) + self._print_verbose(' {:.<20}: {}'.format('config: ', self.config_path)) 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 = self.config.get('executables', option) - if not value.strip(): - value = None + for executable in self.config.options('executables'): + path = self.config.get('executables', executable) + if not path.strip(): + path = None else: - value = _which(value) - setattr(self, '{option}_exe'.format(option=option), value) - self._print_verbose(' {option:.<30}: {value}'.format(option=option + ' ', value=value)) + path = _which(path) + setattr(self, '{exe}_exe'.format(exe=executable), path) + self._print_verbose(' {exe:.<20} {path}'.format(exe=executable + ': ', path=path)) 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))) + self._print_verbose('Paths:') + for name in self.config.options('paths'): + path = os.path.abspath(self.config.get('paths', name)) + setattr(self, '{name}_path'.format(name=name), path) + self._print_verbose(' {name:.<20} {path}'.format(name=name + ': ', path=path)) # Make any command line options override the config file if self.args.branch: self.branch_path = os.path.abspath(self.args.branch) + self._print_verbose(' {:.<20} {}'.format('branch **: ', self.branch_path)) if self.args.documentation: self.documentation_path = os.path.abspath(self.args.documentation) + self._print_verbose(' {:.<20} {}'.format('documentation **: ', self.branch_path)) if self.args.release: self.version = self.args.release self.work_path = os.path.abspath(os.path.join(self.branch_path, '..', 'OpenLP-' + self.version)) + self._print_verbose(' {:.<20} {}'.format('release: ', self.branch_path)) + self._print_verbose(' {:.<20} {}'.format('work path: ', self.work_path)) else: self.version = None self.work_path = self.branch_path + self._print_verbose(' {:.<20} {}'.format('version: ', 'bzr')) + self._print_verbose(' {:.<20} {}'.format('work path: ', self.work_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') @@ -215,6 +228,13 @@ class Builder(object): 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') + self._print_verbose(' {:.<20} {}'.format('openlp script: ', self.openlp_script)) + self._print_verbose(' {:.<20} {}'.format('source: ', self.source_path)) + self._print_verbose(' {:.<20} {}'.format('manual path: ', self.manual_path)) + self._print_verbose(' {:.<20} {}'.format('manual build path: ', self.manual_build_path)) + self._print_verbose(' {:.<20} {}'.format('i18n utils: ', self.i18n_utils)) + self._print_verbose(' {:.<20} {}'.format('i18n path: ', self.i18n_path)) + self._print_verbose(' {:.<20} {}'.format('build path: ', self.build_path)) def setup_extra(self): """ @@ -259,6 +279,7 @@ class Builder(object): '-i', self.icon_path, '-n', 'OpenLP', self.openlp_script] + self._print_verbose(' {}'.format(cmd)) if not self.args.verbose: cmd.append('--log-level=ERROR') else: From 77c9b883a975b7d18e34fb0f721004d71305c521 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 21:58:41 +0200 Subject: [PATCH 11/27] Add some more debug logging to figure out what's going on with the paths --- builders/builder.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index df38cac..56f9864 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -180,6 +180,7 @@ class Builder(object): self._print_verbose(' {:.<20}: {}'.format('python: ', self.python)) self._print_verbose(' {:.<20}: {}'.format('script: ', self.script_path)) self._print_verbose(' {:.<20}: {}'.format('config: ', self.config_path)) + self._print_verbose(' {:.<20}: {}'.format('config path: ', os.path.dirname(self.config_path))) def setup_executables(self): """ @@ -279,14 +280,15 @@ class Builder(object): '-i', self.icon_path, '-n', 'OpenLP', self.openlp_script] - self._print_verbose(' {}'.format(cmd)) if not self.args.verbose: cmd.append('--log-level=ERROR') else: cmd.append('--log-level=DEBUG') if not self.args.release: cmd.append('-d') - self._run_command(cmd, 'Error running PyInstaller') + self._print_verbose('... {}'.format(' '.join(cmd))) + output = self._run_command(cmd, 'Error running PyInstaller') + self._print_verbose(output) def write_version_file(self): """ From 7d98b5ab2a11ec229e25cf8f81b06cf9cd776781 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 22:11:10 +0200 Subject: [PATCH 12/27] Move some logging around and fix up a path to the icon file --- builders/builder.py | 30 +++++++++++++++--------------- builders/windows-builder.py | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index 56f9864..b9d1f52 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -180,7 +180,6 @@ class Builder(object): self._print_verbose(' {:.<20}: {}'.format('python: ', self.python)) self._print_verbose(' {:.<20}: {}'.format('script: ', self.script_path)) self._print_verbose(' {:.<20}: {}'.format('config: ', self.config_path)) - self._print_verbose(' {:.<20}: {}'.format('config path: ', os.path.dirname(self.config_path))) def setup_executables(self): """ @@ -205,7 +204,22 @@ class Builder(object): path = os.path.abspath(self.config.get('paths', name)) setattr(self, '{name}_path'.format(name=name), path) self._print_verbose(' {name:.<20} {path}'.format(name=name + ': ', path=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') + self._print_verbose(' {:.<20} {}'.format('openlp script: ', self.openlp_script)) + self._print_verbose(' {:.<20} {}'.format('source: ', self.source_path)) + self._print_verbose(' {:.<20} {}'.format('manual path: ', self.manual_path)) + self._print_verbose(' {:.<20} {}'.format('manual build path: ', self.manual_build_path)) + self._print_verbose(' {:.<20} {}'.format('i18n utils: ', self.i18n_utils)) + self._print_verbose(' {:.<20} {}'.format('i18n path: ', self.i18n_path)) + self._print_verbose(' {:.<20} {}'.format('build path: ', self.build_path)) # Make any command line options override the config file + self._print_verbose('Overrides:') if self.args.branch: self.branch_path = os.path.abspath(self.args.branch) self._print_verbose(' {:.<20} {}'.format('branch **: ', self.branch_path)) @@ -222,20 +236,6 @@ class Builder(object): self.work_path = self.branch_path self._print_verbose(' {:.<20} {}'.format('version: ', 'bzr')) self._print_verbose(' {:.<20} {}'.format('work path: ', self.work_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') - self._print_verbose(' {:.<20} {}'.format('openlp script: ', self.openlp_script)) - self._print_verbose(' {:.<20} {}'.format('source: ', self.source_path)) - self._print_verbose(' {:.<20} {}'.format('manual path: ', self.manual_path)) - self._print_verbose(' {:.<20} {}'.format('manual build path: ', self.manual_build_path)) - self._print_verbose(' {:.<20} {}'.format('i18n utils: ', self.i18n_utils)) - self._print_verbose(' {:.<20} {}'.format('i18n path: ', self.i18n_path)) - self._print_verbose(' {:.<20} {}'.format('build path: ', self.build_path)) def setup_extra(self): """ diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 9c85636..4b21ef7 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -293,7 +293,7 @@ class WindowsBuilder(Builder): """ 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')) + copy(self.icon_path, 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') From 9425cd40f0f0e830f81d7cd786d69aa8cce25f51 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 22:27:46 +0200 Subject: [PATCH 13/27] Oops, can't reference a value before it exists --- builders/builder.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index b9d1f52..beefebc 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -204,6 +204,17 @@ class Builder(object): path = os.path.abspath(self.config.get('paths', name)) setattr(self, '{name}_path'.format(name=name), path) self._print_verbose(' {name:.<20} {path}'.format(name=name + ': ', path=path)) + # 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') @@ -211,6 +222,7 @@ class Builder(object): 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') + # Print out all the values self._print_verbose(' {:.<20} {}'.format('openlp script: ', self.openlp_script)) self._print_verbose(' {:.<20} {}'.format('source: ', self.source_path)) self._print_verbose(' {:.<20} {}'.format('manual path: ', self.manual_path)) @@ -218,24 +230,11 @@ class Builder(object): self._print_verbose(' {:.<20} {}'.format('i18n utils: ', self.i18n_utils)) self._print_verbose(' {:.<20} {}'.format('i18n path: ', self.i18n_path)) self._print_verbose(' {:.<20} {}'.format('build path: ', self.build_path)) - # Make any command line options override the config file self._print_verbose('Overrides:') - if self.args.branch: - self.branch_path = os.path.abspath(self.args.branch) - self._print_verbose(' {:.<20} {}'.format('branch **: ', self.branch_path)) - if self.args.documentation: - self.documentation_path = os.path.abspath(self.args.documentation) - self._print_verbose(' {:.<20} {}'.format('documentation **: ', self.branch_path)) - if self.args.release: - self.version = self.args.release - self.work_path = os.path.abspath(os.path.join(self.branch_path, '..', 'OpenLP-' + self.version)) - self._print_verbose(' {:.<20} {}'.format('release: ', self.branch_path)) - self._print_verbose(' {:.<20} {}'.format('work path: ', self.work_path)) - else: - self.version = None - self.work_path = self.branch_path - self._print_verbose(' {:.<20} {}'.format('version: ', 'bzr')) - self._print_verbose(' {:.<20} {}'.format('work path: ', self.work_path)) + self._print_verbose(' {:.<20} {}'.format('branch **: ', self.branch_path)) + self._print_verbose(' {:.<20} {}'.format('documentation **: ', self.branch_path)) + self._print_verbose(' {:.<20} {}'.format('version: ', self.version)) + self._print_verbose(' {:.<20} {}'.format('work path: ', self.work_path)) def setup_extra(self): """ From 6eb26bf1837d49bfe67f47a9bf1adf5d18edd214 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 22:51:27 +0200 Subject: [PATCH 14/27] Remove refences to script_path and change them to config_dir/config_path --- builders/builder.py | 7 ++++--- builders/macosx-builder.py | 1 - builders/windows-builder.py | 18 +++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index beefebc..575249b 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -97,14 +97,14 @@ class Builder(object): self._print(output) self._print(error) raise Exception(err_msg) - return output + return output, error 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) + output, _ = self._run_command(['bzr', command] + args, err_msg) return output def get_platform(self): @@ -286,8 +286,9 @@ class Builder(object): if not self.args.release: cmd.append('-d') self._print_verbose('... {}'.format(' '.join(cmd))) - output = self._run_command(cmd, 'Error running PyInstaller') + output, error = self._run_command(cmd, 'Error running PyInstaller') self._print_verbose(output) + self._print_verbose(error) def write_version_file(self): """ diff --git a/builders/macosx-builder.py b/builders/macosx-builder.py index e266995..8213434 100644 --- a/builders/macosx-builder.py +++ b/builders/macosx-builder.py @@ -235,7 +235,6 @@ class MacOSXBuilder(Builder): size = size / (1000 * 1000) size += 10 - self._print_verbose('... %s' % self.script_path) os.chdir(os.path.dirname(self.dmg_settings_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), diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 4b21ef7..e94fda3 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -143,8 +143,9 @@ class WindowsBuilder(Builder): 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: + config_dir = os.path.dirname(self.config_path) + with open(os.path.join(config_dir, 'OpenLP.iss.default'), 'r') as input_file, \ + open(os.path.join(config_dir, 'OpenLP.iss'), 'w') as output_file: content = input_file.read() content = content.replace('%(branch)s', self.branch_path) content = content.replace('%(display_version)s', self.version) @@ -155,8 +156,9 @@ class WindowsBuilder(Builder): 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'], + config_dir = os.path.dirname(self.config_path) + os.chdir(config_dir) + self._run_command([self.innosetup_exe, os.path.join(config_dir, 'OpenLP.iss'), '/q'], 'Error running InnoSetup') def _create_portableapp_directory(self): @@ -177,8 +179,9 @@ class WindowsBuilder(Builder): Create a Portabbleapps appinfo.ini file. """ self._print(' Creating PortableApps appinfo file ...') + config_dir = os.path.dirname(self.config_path) 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, \ + with open(os.path.join(config_dir, '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) @@ -202,7 +205,7 @@ class WindowsBuilder(Builder): # build directory. self._create_portableapp_directory() self._create_portableapps_appinfo_file() - dir_util.copy_tree(os.path.join(self.script_path, 'OpenLPPortable'), self.portable_path) + dir_util.copy_tree(os.path.join(os.path.dirname(self.config_path), 'OpenLPPortable'), self.portable_path) # Copy distribution files to portableapp build directory. self._print(' Copying distribution files') portable_app_path = os.path.join(self.portable_path, 'App', 'OpenLP') @@ -295,7 +298,8 @@ class WindowsBuilder(Builder): self._print_verbose('... OpenLP.ico') copy(self.icon_path, 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')) + copy(os.path.join(os.path.dirname(self.config_path), 'LICENSE.txt'), + os.path.join(self.dist_path, 'LICENSE.txt')) self._print_verbose('... psvince.dll') copy(self.psvince_exe, os.path.join(self.dist_path, 'psvince.dll')) if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')): From ea2c3b7be1536a981f63d8f23b8af238283dbe82 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Tue, 6 Dec 2016 23:02:14 +0200 Subject: [PATCH 15/27] Put some more stuff in the config file - makes referencing it later easier --- builders/windows-builder.py | 30 +++++++++++++++--------------- windows/config-appveyor.ini | 4 +++- windows/config.ini.default | 4 +++- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index e94fda3..8a563f2 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -167,10 +167,10 @@ class WindowsBuilder(Builder): missing subdirs """ self._print(' Checking PortableApps directory structure...') - launcher_path = os.path.join(self.portable_path, 'App', 'Appinfo', 'Launcher') + launcher_path = os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'Launcher') if not os.path.exists(launcher_path): os.makedirs(launcher_path) - settings_path = os.path.join(self.portable_path, 'Data', 'Settings') + settings_path = os.path.join(self.portable_dest_path, 'Data', 'Settings') if not os.path.exists(settings_path): os.makedirs(settings_path) @@ -182,7 +182,7 @@ class WindowsBuilder(Builder): config_dir = os.path.dirname(self.config_path) portable_version = self.version.replace('-', '.') + '.0' * (3 - self.version.count('.')) with open(os.path.join(config_dir, 'appinfo.ini.default'), 'r') as input_file, \ - open(os.path.join(self.portable_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file: + open(os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file: content = input_file.read() content = content.replace('%(display_version)s', self.version) content = content.replace('%(package_version)s', portable_version) @@ -198,17 +198,17 @@ class WindowsBuilder(Builder): 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) + if os.path.exists(self.portable_dest_path): + rmtree(self.portable_dest_path) self._print(' Creating PortableApps build directory') # Copy the contents of the OpenLPPortable directory to the portable # build directory. self._create_portableapp_directory() self._create_portableapps_appinfo_file() - dir_util.copy_tree(os.path.join(os.path.dirname(self.config_path), 'OpenLPPortable'), self.portable_path) + dir_util.copy_tree(self.portable_source_path, self.portable_dest_path) # Copy distribution files to portableapp build directory. self._print(' Copying distribution files') - portable_app_path = os.path.join(self.portable_path, 'App', 'OpenLP') + portable_app_path = os.path.join(self.portable_dest_path, 'App', 'OpenLP') dir_util.copy_tree(self.dist_path, portable_app_path) # Copy help files to portableapp build directory. if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')): @@ -218,11 +218,13 @@ class WindowsBuilder(Builder): self._print('... WARNING: Windows help file not found') # Build the launcher. self._print(' Building PortableApps Launcher') - self_run_command([self.portablelauncher_exe, self.portable_path], 'Error creating PortableAppa Launcher') + self_run_command([self.portablelauncher_exe, self.portable_dest_path], + 'Error creating PortableAppa Launcher') # Build the portable installer. self._print(' Building PortableApps Installer') - self._run_command([self.portableinstaller_exe, self.portable_path], 'Error running PortableApps Installer') - portable_app = os.path.abspath(os.path.join(self.portable_path, '..', + self._run_command([self.portableinstaller_exe, self.portable_dest_path], + 'Error running PortableApps Installer') + portable_app = os.path.abspath(os.path.join(self.portable_dest_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, '..'))) @@ -281,14 +283,13 @@ class WindowsBuilder(Builder): """ 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) + self.portable_dest_path = os.path.abspath(self.args.portable) def copy_extra_files(self): """ @@ -298,8 +299,7 @@ class WindowsBuilder(Builder): self._print_verbose('... OpenLP.ico') copy(self.icon_path, os.path.join(self.dist_path, 'OpenLP.ico')) self._print_verbose('... LICENSE.txt') - copy(os.path.join(os.path.dirname(self.config_path), 'LICENSE.txt'), - os.path.join(self.dist_path, 'LICENSE.txt')) + copy(self.license_path, os.path.join(self.dist_path, 'LICENSE.txt')) self._print_verbose('... psvince.dll') copy(self.psvince_exe, os.path.join(self.dist_path, 'psvince.dll')) if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')): @@ -333,7 +333,7 @@ class WindowsBuilder(Builder): self._build_pptviewlib() self._create_innosetup_file() self._run_innosetup() - if self.portable_path and os.path.exists(self.portable_path): + if self.portable_dest_path and os.path.exists(self.portable_dest_path): self._run_portableapp_builder() diff --git a/windows/config-appveyor.ini b/windows/config-appveyor.ini index a6f4baa..e98aba7 100644 --- a/windows/config-appveyor.ini +++ b/windows/config-appveyor.ini @@ -16,7 +16,9 @@ branch = %(projects)s\trunk documentation = %(projects)s\documentation icon = %(here)s\OpenLP.ico hooks = %(here)s\..\pyinstaller-hooks -portable = %(projects)s\OpenLPPortable +license = %(here)s\LICENSE.txt +portable_source = %(here)s\OpenLPPortable +portable_dest = %(projects)s\OpenLPPortable [transifex] username = diff --git a/windows/config.ini.default b/windows/config.ini.default index 925baf9..d335ff4 100644 --- a/windows/config.ini.default +++ b/windows/config.ini.default @@ -16,7 +16,9 @@ branch = %(projects)s\trunk documentation = %(projects)s\documentation icon = %(here)s\OpenLP.ico hooks = %(here)s\..\pyinstaller-hooks -portable = %(projects)s\OpenLPPortable +license = %(here)s\LICENSE.txt +portable_source = %(here)s\OpenLPPortable +portable_dest = %(projects)s\OpenLPPortable [transifex] username = From aa3c2d2f1381ce31b47d4334c75bb19e9061cf8e Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 7 Dec 2016 15:18:50 +0200 Subject: [PATCH 16/27] Fix up version numbers for InnoSetup and PortableApps --- builders/windows-builder.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 8a563f2..7fac2fa 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -148,7 +148,7 @@ class WindowsBuilder(Builder): open(os.path.join(config_dir, 'OpenLP.iss'), 'w') as output_file: content = input_file.read() content = content.replace('%(branch)s', self.branch_path) - content = content.replace('%(display_version)s', self.version) + content = content.replace('%(display_version)s', self.version.replace('-bzr', '.')) output_file.write(content) def _run_innosetup(self): @@ -161,7 +161,7 @@ class WindowsBuilder(Builder): self._run_command([self.innosetup_exe, os.path.join(config_dir, 'OpenLP.iss'), '/q'], 'Error running InnoSetup') - def _create_portableapp_directory(self): + def _create_portableapp_structure(self): """ Checks the PortableApp directory structure amd creates missing subdirs @@ -180,12 +180,13 @@ class WindowsBuilder(Builder): """ self._print(' Creating PortableApps appinfo file ...') config_dir = os.path.dirname(self.config_path) - portable_version = self.version.replace('-', '.') + '.0' * (3 - self.version.count('.')) + self.portable_version = self.version.replace('-bzr', '.') + self.portable_version = self.portable_version + '.0' * (3 - self.portable_version.count('.')) with open(os.path.join(config_dir, 'appinfo.ini.default'), 'r') as input_file, \ open(os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file: content = input_file.read() content = content.replace('%(display_version)s', self.version) - content = content.replace('%(package_version)s', portable_version) + content = content.replace('%(package_version)s', self.portable_version) output_file.write(content) def _run_portableapp_builder(self): @@ -203,9 +204,9 @@ class WindowsBuilder(Builder): self._print(' Creating PortableApps build directory') # Copy the contents of the OpenLPPortable directory to the portable # build directory. - self._create_portableapp_directory() - self._create_portableapps_appinfo_file() dir_util.copy_tree(self.portable_source_path, self.portable_dest_path) + self._create_portableapp_structure() + self._create_portableapps_appinfo_file() # Copy distribution files to portableapp build directory. self._print(' Copying distribution files') portable_app_path = os.path.join(self.portable_dest_path, 'App', 'OpenLP') @@ -225,7 +226,7 @@ class WindowsBuilder(Builder): self._run_command([self.portableinstaller_exe, self.portable_dest_path], 'Error running PortableApps Installer') portable_app = os.path.abspath(os.path.join(self.portable_dest_path, '..', - 'OpenLPPortable_%s.paf.exe' % self.version.replace('-', '.'))) + 'OpenLPPortable_%s.paf.exe' % self.portable_version)) if os.path.exists(portable_app): move(portable_app, os.path.abspath(os.path.join(self.dist_path, '..'))) self._print(' PortableApp build complete') From dc3f564082a299ff78a1c5931df69e8eaa3ee8e8 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 7 Dec 2016 19:08:40 +0200 Subject: [PATCH 17/27] Make portable argument a flag --- builders/windows-builder.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 7fac2fa..064749d 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -268,7 +268,7 @@ class WindowsBuilder(Builder): """ Add extra arguments to the command line argument parser """ - parser.add_argument('--portable', metavar='PATH', default=None, + parser.add_argument('--portable', metavar='PATH', action='store_true', default=False, help='Specify the path to build the portable installation.') def setup_system_paths(self): @@ -289,8 +289,6 @@ class WindowsBuilder(Builder): 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_dest_path = os.path.abspath(self.args.portable) def copy_extra_files(self): """ @@ -334,7 +332,7 @@ class WindowsBuilder(Builder): self._build_pptviewlib() self._create_innosetup_file() self._run_innosetup() - if self.portable_dest_path and os.path.exists(self.portable_dest_path): + if self.args.portable and os.path.exists(self.portable_dest_path): self._run_portableapp_builder() From 1d27ba766db02c18941010fae1f29bf568b4c01a Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 7 Dec 2016 19:09:07 +0200 Subject: [PATCH 18/27] Make portable argument a flag --- builders/windows-builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 064749d..41c207c 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -332,7 +332,7 @@ class WindowsBuilder(Builder): self._build_pptviewlib() self._create_innosetup_file() self._run_innosetup() - if self.args.portable and os.path.exists(self.portable_dest_path): + if self.args.portable: self._run_portableapp_builder() From 47ad69b9ae503471919c103b96e324599660d8ef Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 7 Dec 2016 23:49:54 +0200 Subject: [PATCH 19/27] metavar is not needed with a flag --- builders/windows-builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 41c207c..f2edeab 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -268,7 +268,7 @@ class WindowsBuilder(Builder): """ Add extra arguments to the command line argument parser """ - parser.add_argument('--portable', metavar='PATH', action='store_true', default=False, + parser.add_argument('--portable', action='store_true', default=False, help='Specify the path to build the portable installation.') def setup_system_paths(self): From 5c2b25c2333e2e15086dce9ea28400864d029e08 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 9 Dec 2016 01:38:22 +0200 Subject: [PATCH 20/27] Fix a typo --- builders/windows-builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index f2edeab..bdbc3df 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -219,7 +219,7 @@ class WindowsBuilder(Builder): self._print('... WARNING: Windows help file not found') # Build the launcher. self._print(' Building PortableApps Launcher') - self_run_command([self.portablelauncher_exe, self.portable_dest_path], + self._run_command([self.portablelauncher_exe, self.portable_dest_path], 'Error creating PortableAppa Launcher') # Build the portable installer. self._print(' Building PortableApps Installer') From 2a52c24fa3dd76339d42ea4c55f257b0c664f67d Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 9 Dec 2016 01:39:07 +0200 Subject: [PATCH 21/27] Fix indentation --- builders/windows-builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index bdbc3df..c79a7f1 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -220,7 +220,7 @@ class WindowsBuilder(Builder): # Build the launcher. self._print(' Building PortableApps Launcher') self._run_command([self.portablelauncher_exe, self.portable_dest_path], - 'Error creating PortableAppa Launcher') + 'Error creating PortableAppa Launcher') # Build the portable installer. self._print(' Building PortableApps Installer') self._run_command([self.portableinstaller_exe, self.portable_dest_path], From 4ad35f87c55742b2a3e6614844e8111926ec3143 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 9 Dec 2016 11:52:33 +0200 Subject: [PATCH 22/27] Fix up some messages, make some messages only show in verbose mode, and add some more logging for debugging --- builders/builder.py | 2 +- builders/windows-builder.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/builders/builder.py b/builders/builder.py index 575249b..b1ed5c7 100644 --- a/builders/builder.py +++ b/builders/builder.py @@ -417,7 +417,7 @@ class Builder(object): Run Sphinx to build the manual """ self._print('Running Sphinx...') - self._print_verbose(' Deleting previous help manual build... %s', self.manual_build_path) + self._print_verbose('... Deleting previous help manual build... %s', self.manual_build_path) if os.path.exists(self.manual_build_path): rmtree(self.manual_build_path) os.chdir(self.manual_path) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index c79a7f1..b0af119 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -166,7 +166,7 @@ class WindowsBuilder(Builder): Checks the PortableApp directory structure amd creates missing subdirs """ - self._print(' Checking PortableApps directory structure...') + self._print('... Checking PortableApps directory structure...') launcher_path = os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'Launcher') if not os.path.exists(launcher_path): os.makedirs(launcher_path) @@ -178,7 +178,7 @@ class WindowsBuilder(Builder): """ Create a Portabbleapps appinfo.ini file. """ - self._print(' Creating PortableApps appinfo file ...') + self._print_verbose('... Creating PortableApps appinfo file ...') config_dir = os.path.dirname(self.config_path) self.portable_version = self.version.replace('-bzr', '.') self.portable_version = self.portable_version + '.0' * (3 - self.portable_version.count('.')) @@ -197,39 +197,40 @@ class WindowsBuilder(Builder): 3 Builds the PortableApps Install """ self._print('Running PortableApps Builder...') - self._print(' Clearing old files') + self._print_verbose('... Clearing old files') # Remove previous contents of portableapp build directory. if os.path.exists(self.portable_dest_path): rmtree(self.portable_dest_path) - self._print(' Creating PortableApps build directory') + self._print_verbose('... Creating PortableApps build directory') # Copy the contents of the OpenLPPortable directory to the portable # build directory. dir_util.copy_tree(self.portable_source_path, self.portable_dest_path) self._create_portableapp_structure() self._create_portableapps_appinfo_file() # Copy distribution files to portableapp build directory. - self._print(' Copying distribution files') + self._print_verbose('... Copying distribution files') portable_app_path = os.path.join(self.portable_dest_path, 'App', 'OpenLP') dir_util.copy_tree(self.dist_path, portable_app_path) # Copy help files to portableapp build directory. if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')): - self._print(' Copying help files') + self._print_verbose('... Copying help files') dir_util.copy_tree(self.helpfile_path, os.path.join(portable_app_path, 'help')) else: self._print('... WARNING: Windows help file not found') # Build the launcher. - self._print(' Building PortableApps Launcher') + self._print_verbose('... Building PortableApps Launcher') self._run_command([self.portablelauncher_exe, self.portable_dest_path], - 'Error creating PortableAppa Launcher') + 'Error creating PortableApps Launcher') # Build the portable installer. - self._print(' Building PortableApps Installer') + self._print_verbose('... Building PortableApps Installer') self._run_command([self.portableinstaller_exe, self.portable_dest_path], 'Error running PortableApps Installer') portable_app = os.path.abspath(os.path.join(self.portable_dest_path, '..', 'OpenLPPortable_%s.paf.exe' % self.portable_version)) + self._print_verbose('... Portable Build: {}'.format(portable_app)) if os.path.exists(portable_app): move(portable_app, os.path.abspath(os.path.join(self.dist_path, '..'))) - self._print(' PortableApp build complete') + self._print('PortableApp build complete') else: raise Exception('PortableApp failed to build') From a2c416b09b69975d335f064fd565ee86382fb33b Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sat, 10 Dec 2016 23:12:38 +0200 Subject: [PATCH 23/27] Try to get the portable filename right --- builders/windows-builder.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index b0af119..5cf8a4d 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -185,7 +185,7 @@ class WindowsBuilder(Builder): with open(os.path.join(config_dir, 'appinfo.ini.default'), 'r') as input_file, \ open(os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file: content = input_file.read() - content = content.replace('%(display_version)s', self.version) + content = content.replace('%(display_version)s', self.portable_version) content = content.replace('%(package_version)s', self.portable_version) output_file.write(content) @@ -225,11 +225,10 @@ class WindowsBuilder(Builder): self._print_verbose('... Building PortableApps Installer') self._run_command([self.portableinstaller_exe, self.portable_dest_path], 'Error running PortableApps Installer') - portable_app = os.path.abspath(os.path.join(self.portable_dest_path, '..', - 'OpenLPPortable_%s.paf.exe' % self.portable_version)) + portable_app = os.path.join(self.portable_dest_path, 'OpenLPPortable_%s.paf.exe' % self.portable_version) self._print_verbose('... Portable Build: {}'.format(portable_app)) if os.path.exists(portable_app): - move(portable_app, os.path.abspath(os.path.join(self.dist_path, '..'))) + move(portable_app, os.path.join(self.dist_path, 'OpenLPPortable_%s.paf.exe' % self.portable_version)) self._print('PortableApp build complete') else: raise Exception('PortableApp failed to build') From 19b5a489cff07202baa0f087cbbea4a225bb7e89 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 11 Dec 2016 14:20:35 +0200 Subject: [PATCH 24/27] (hopefully) Fix the portable build once and for all --- builders/windows-builder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 5cf8a4d..010c091 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -225,10 +225,11 @@ class WindowsBuilder(Builder): self._print_verbose('... Building PortableApps Installer') self._run_command([self.portableinstaller_exe, self.portable_dest_path], 'Error running PortableApps Installer') - portable_app = os.path.join(self.portable_dest_path, 'OpenLPPortable_%s.paf.exe' % self.portable_version) - self._print_verbose('... Portable Build: {}'.format(portable_app)) - if os.path.exists(portable_app): - move(portable_app, os.path.join(self.dist_path, 'OpenLPPortable_%s.paf.exe' % self.portable_version)) + portable_exe_name = 'OpenLPPortable_%s.paf.exe' % self.portable_version + portable_exe_path = os.path.join(self.portable_dest_path, '..', portable_exe_name) + self._print_verbose('... Portable Build: {}'.format(portable_exe_path)) + if os.path.exists(portable_exe_path): + move(portable_exe_path, os.path.join(self.dist_path, portable_exe_name)) self._print('PortableApp build complete') else: raise Exception('PortableApp failed to build') @@ -269,7 +270,7 @@ class WindowsBuilder(Builder): Add extra arguments to the command line argument parser """ parser.add_argument('--portable', action='store_true', default=False, - help='Specify the path to build the portable installation.') + help='Build a PortableApps.com build of OpenLP too') def setup_system_paths(self): """ From 6fe60a1c94b48c09255577251f0c72ea347e80e5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 12 Dec 2016 12:00:31 +0200 Subject: [PATCH 25/27] Path needs to be absolute --- builders/windows-builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 010c091..3deca37 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -226,7 +226,7 @@ class WindowsBuilder(Builder): self._run_command([self.portableinstaller_exe, self.portable_dest_path], 'Error running PortableApps Installer') portable_exe_name = 'OpenLPPortable_%s.paf.exe' % self.portable_version - portable_exe_path = os.path.join(self.portable_dest_path, '..', portable_exe_name) + portable_exe_path = os.path.abspath(os.path.join(self.portable_dest_path, '..', portable_exe_name)) self._print_verbose('... Portable Build: {}'.format(portable_exe_path)) if os.path.exists(portable_exe_path): move(portable_exe_path, os.path.join(self.dist_path, portable_exe_name)) From 648afbef34d2f6218d22416d297a3288ee961ace Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 12 Dec 2016 18:43:19 +0200 Subject: [PATCH 26/27] dist_path is weird --- builders/windows-builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 010c091..9c6febc 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -229,7 +229,7 @@ class WindowsBuilder(Builder): portable_exe_path = os.path.join(self.portable_dest_path, '..', portable_exe_name) self._print_verbose('... Portable Build: {}'.format(portable_exe_path)) if os.path.exists(portable_exe_path): - move(portable_exe_path, os.path.join(self.dist_path, portable_exe_name)) + move(portable_exe_path, os.path.join(self.dist_path, '..', portable_exe_name)) self._print('PortableApp build complete') else: raise Exception('PortableApp failed to build') From c5654e42560b77aa6e432401911c363cf13136bb Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Mon, 12 Dec 2016 21:02:33 +0200 Subject: [PATCH 27/27] Always put the revision number at the end of the portable build number --- builders/windows-builder.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/builders/windows-builder.py b/builders/windows-builder.py index 1fa6cd0..a0086f3 100644 --- a/builders/windows-builder.py +++ b/builders/windows-builder.py @@ -180,8 +180,12 @@ class WindowsBuilder(Builder): """ self._print_verbose('... Creating PortableApps appinfo file ...') config_dir = os.path.dirname(self.config_path) - self.portable_version = self.version.replace('-bzr', '.') - self.portable_version = self.portable_version + '.0' * (3 - self.portable_version.count('.')) + if '-bzr' in self.version: + version, revision = self.version.split('-bzr') + version = version + '.0' * (2 - version.count('.')) + self.portable_version = version + '.' + revision + else: + self.portable_version = self.version + '.0' * (3 - self.version.count('.')) with open(os.path.join(config_dir, 'appinfo.ini.default'), 'r') as input_file, \ open(os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file: content = input_file.read()