← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/pyinstaller-change into lp:openlp/packaging

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/pyinstaller-change into lp:openlp/packaging.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/pyinstaller-change/+merge/313061

Refactor the Windowsand macOS builders to reuse common code. Fix the issue with dud builds when using PyInstaller 3.2.
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/pyinstaller-change into lp:openlp/packaging.
=== added directory 'builders'
=== added file 'builders/builder.py'
--- builders/builder.py	1970-01-01 00:00:00 +0000
+++ builders/builder.py	2016-12-12 19:11:48 +0000
@@ -0,0 +1,475 @@
+# -*- 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(program):
+    """
+    Return absolute path to a command found on system PATH.
+    """
+    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(os.path.abspath(program)):
+        return os.path.abspath(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):
+    """
+    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, exit_code=0):
+        """
+        Run command in subprocess and print error message in case of Exception.
+
+        Return text from stdout.
+        """
+        proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
+        output, error = proc.communicate()
+        code = proc.wait()
+        if code != exit_code:
+            self._print(output)
+            self._print(error)
+            raise Exception(err_msg)
+        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)
+        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': os.path.dirname(self.config_path)}
+
+    def get_sphinx_build(self):
+        """
+        Get the type of build we should be running for Sphinx. Defaults to html.
+        """
+        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.
+        """
+        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 read_config(self):
+        """
+        Read the configuration from the configuration file.
+        """
+        self.config = ConfigParser(defaults=self.get_config_defaults())
+        self.config.read(self.config_path)
+
+    def setup_system_paths(self):
+        """
+        Set up some system paths.
+        """
+        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 executable in self.config.options('executables'):
+            path = self.config.get('executables', executable)
+            if not path.strip():
+                path = None
+            else:
+                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.
+        """
+        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)
+        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')
+        # 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))
+        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))
+        self._print_verbose('Overrides:')
+        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):
+        """
+        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...')
+        copy(os.path.join(self.work_path, 'openlp.py'), self.openlp_script)
+        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 not self.args.release:
+            cmd.append('-d')
+        self._print_verbose('... {}'.format(' '.join(cmd)))
+        output, error = self._run_command(cmd, 'Error running PyInstaller')
+        self._print_verbose(output)
+        self._print_verbose(error)
+
+    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].split()
+            output = self._bzr('log', self.branch_path, ['--line', '-r', '-1'], 'Error running bzr log')
+            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:
+            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, _, 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, _, 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, _, 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...')
+        if not os.path.exists(os.path.join(self.dist_path, 'i18n')):
+            os.makedirs(os.path.join(self.dist_path, 'i18n'))
+        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...')
+        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))
+
+    def run_sphinx(self):
+        """
+        Run Sphinx to build the manual
+        """
+        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)
+        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()
+        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_extra_files()
+        if not self.args.skip_translations:
+            if self.args.update_translations:
+                self.update_translations()
+            self.compile_translations()
+        self.build_package()
+
+        self._print('Done.')
+        raise SystemExit()
+
+

=== added file 'builders/macosx-builder.py'
--- builders/macosx-builder.py	1970-01-01 00:00:00 +0000
+++ builders/macosx-builder.py	2016-12-12 19:11:48 +0000
@@ -0,0 +1,307 @@
+# -*- 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 _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 _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
+
+        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 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
+        """
+        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.
+        """
+        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()
+
+
+if __name__ == '__main__':
+    MacOSXBuilder().main()

=== added file 'builders/windows-builder.py'
--- builders/windows-builder.py	1970-01-01 00:00:00 +0000
+++ builders/windows-builder.py	2016-12-12 19:11:48 +0000
@@ -0,0 +1,345 @@
+# -*- 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
+from distutils import dir_util
+from shutil import copy, move, rmtree
+
+from builder import Builder
+
+
+class WindowsBuilder(Builder):
+    """
+    The :class:`WindowsBuilder` class encapsulates everything that is needed
+    to build a Windows installer.
+    """
+    def _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...')
+        config_dir = os.path.dirname(self.config_path)
+        with open(os.path.join(config_dir, 'OpenLP.iss.default'), 'r') as input_file, \
+                open(os.path.join(config_dir, 'OpenLP.iss'), 'w') as output_file:
+            content = input_file.read()
+            content = content.replace('%(branch)s', self.branch_path)
+            content = content.replace('%(display_version)s', self.version.replace('-bzr', '.'))
+            output_file.write(content)
+
+    def _run_innosetup(self):
+        """
+        Run InnoSetup to create an installer.
+        """
+        self._print('Running Inno Setup...')
+        config_dir = os.path.dirname(self.config_path)
+        os.chdir(config_dir)
+        self._run_command([self.innosetup_exe, os.path.join(config_dir, 'OpenLP.iss'), '/q'],
+                          'Error running InnoSetup')
+
+    def _create_portableapp_structure(self):
+        """
+        Checks the PortableApp directory structure amd creates
+        missing subdirs
+        """
+        self._print('... Checking PortableApps directory structure...')
+        launcher_path = os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'Launcher')
+        if not os.path.exists(launcher_path):
+            os.makedirs(launcher_path)
+        settings_path = os.path.join(self.portable_dest_path, 'Data', 'Settings')
+        if not os.path.exists(settings_path):
+            os.makedirs(settings_path)
+
+    def _create_portableapps_appinfo_file(self):
+        """
+        Create a Portabbleapps appinfo.ini file.
+        """
+        self._print_verbose('... Creating PortableApps appinfo file ...')
+        config_dir = os.path.dirname(self.config_path)
+        if '-bzr' in self.version:
+            version, revision = self.version.split('-bzr')
+            version = version + '.0' * (2 - version.count('.'))
+            self.portable_version = version + '.' + revision
+        else:
+            self.portable_version = self.version + '.0' * (3 - self.version.count('.'))
+        with open(os.path.join(config_dir, 'appinfo.ini.default'), 'r') as input_file, \
+                open(os.path.join(self.portable_dest_path, 'App', 'Appinfo', 'appinfo.ini'), 'w') as output_file:
+            content = input_file.read()
+            content = content.replace('%(display_version)s', self.portable_version)
+            content = content.replace('%(package_version)s', self.portable_version)
+            output_file.write(content)
+
+    def _run_portableapp_builder(self):
+        """
+        Creates a portable installer.
+        1  Copies the distribution to the portable apps directory
+        2  Builds the PortableApps Launcher
+        3  Builds the PortableApps Install
+        """
+        self._print('Running PortableApps Builder...')
+        self._print_verbose('... Clearing old files')
+        # Remove previous contents of portableapp build directory.
+        if os.path.exists(self.portable_dest_path):
+            rmtree(self.portable_dest_path)
+        self._print_verbose('... Creating PortableApps build directory')
+        # Copy the contents of the OpenLPPortable directory to the portable
+        # build directory.
+        dir_util.copy_tree(self.portable_source_path, self.portable_dest_path)
+        self._create_portableapp_structure()
+        self._create_portableapps_appinfo_file()
+        # Copy distribution files to portableapp build directory.
+        self._print_verbose('... Copying distribution files')
+        portable_app_path = os.path.join(self.portable_dest_path, 'App', 'OpenLP')
+        dir_util.copy_tree(self.dist_path, portable_app_path)
+        # Copy help files to portableapp build directory.
+        if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')):
+            self._print_verbose('... Copying help files')
+            dir_util.copy_tree(self.helpfile_path, os.path.join(portable_app_path, 'help'))
+        else:
+            self._print('... WARNING: Windows help file not found')
+        # Build the launcher.
+        self._print_verbose('... Building PortableApps Launcher')
+        self._run_command([self.portablelauncher_exe, self.portable_dest_path],
+                          'Error creating PortableApps Launcher')
+        # Build the portable installer.
+        self._print_verbose('... Building PortableApps Installer')
+        self._run_command([self.portableinstaller_exe, self.portable_dest_path],
+                          'Error running PortableApps Installer')
+        portable_exe_name = 'OpenLPPortable_%s.paf.exe' % self.portable_version
+        portable_exe_path = os.path.abspath(os.path.join(self.portable_dest_path, '..', portable_exe_name))
+        self._print_verbose('... Portable Build: {}'.format(portable_exe_path))
+        if os.path.exists(portable_exe_path):
+            move(portable_exe_path, os.path.join(self.dist_path, '..', portable_exe_name))
+            self._print('PortableApp build complete')
+        else:
+            raise Exception('PortableApp failed to build')
+
+    def get_platform(self):
+        """
+        Return the platform we're building for
+        """
+        return 'Windows'
+
+    def get_config_defaults(self):
+        """
+        Build some default values for the config file
+        """
+        config_defaults = super().get_config_defaults()
+        config_defaults.update({
+            'pyroot': self.python_root,
+            'progfiles': self.program_files,
+            'sitepackages': self.site_packages,
+            'projects': os.path.abspath(os.path.join(self.script_path, '..', '..'))
+        })
+        return config_defaults
+
+    def get_sphinx_build(self):
+        """
+        Tell Sphinx we want to build HTML help
+        """
+        return "htmlhelp"
+
+    def get_qt_translations_path(self):
+        """
+        Return the path to Qt translation files on macOS
+        """
+        return os.path.join(self.site_packages, 'PyQt5', 'translations')
+
+    def add_extra_args(self, parser):
+        """
+        Add extra arguments to the command line argument parser
+        """
+        parser.add_argument('--portable', action='store_true', default=False,
+                            help='Build a PortableApps.com build of OpenLP too')
+
+    def setup_system_paths(self):
+        """
+        Set up some system paths.
+        """
+        super().setup_system_paths()
+        self.python_root = os.path.dirname(self.python)
+        self.site_packages = os.path.join(self.python_root, 'Lib', 'site-packages')
+        self.program_files = os.getenv('PROGRAMFILES')
+
+    def setup_paths(self):
+        """
+        Set up a variety of paths that we use throughout the build process.
+        """
+        super().setup_paths()
+        self.dist_path = os.path.join(self.work_path, 'dist', 'OpenLP')
+        self.helpfile_path = os.path.join(self.manual_build_path, 'htmlhelp')
+        self.winres_path = os.path.join(self.branch_path, 'resources', 'windows')
+        self.pptviewlib_path = os.path.join(self.source_path, 'plugins', 'presentations', 'lib', 'pptviewlib')
+
+    def copy_extra_files(self):
+        """
+        Copy all the Windows-specific files.
+        """
+        self._print('Copying extra files for Windows...')
+        self._print_verbose('... OpenLP.ico')
+        copy(self.icon_path, os.path.join(self.dist_path, 'OpenLP.ico'))
+        self._print_verbose('... LICENSE.txt')
+        copy(self.license_path, os.path.join(self.dist_path, 'LICENSE.txt'))
+        self._print_verbose('... psvince.dll')
+        copy(self.psvince_exe, os.path.join(self.dist_path, 'psvince.dll'))
+        if os.path.isfile(os.path.join(self.helpfile_path, 'OpenLP.chm')):
+            self._print_verbose('... OpenLP.chm')
+            copy(os.path.join(self.helpfile_path, 'OpenLP.chm'), os.path.join(self.dist_path, 'OpenLP.chm'))
+        else:
+            self._print('... WARNING: Windows help file not found')
+        self._print_verbose('... mutool.exe')
+        if self.mutool_exe and os.path.isfile(self.mutool_exe):
+            copy(os.path.join(self.mutool_exe), os.path.join(self.dist_path, 'mutool.exe'))
+        else:
+            self._print('... WARNING: mutool.exe not found')
+        self._print_verbose('... MediaInfo.exe')
+        if self.mediainfo_exe and os.path.isfile(self.mediainfo_exe):
+            copy(os.path.join(self.mediainfo_exe), os.path.join(self.dist_path, 'MediaInfo.exe'))
+        else:
+            self._print('... WARNING: MediaInfo.exe not found')
+
+    def after_run_sphinx(self):
+        """
+        Run HTML Help Workshop to convert the Sphinx output into a manual.
+        """
+        self._print('Running HTML Help Workshop...')
+        os.chdir(os.path.join(self.manual_build_path, 'htmlhelp'))
+        self._run_command([self.htmlhelp_exe, 'OpenLP.chm'], 'Error running HTML Help Workshop', exit_code=1)
+
+    def build_package(self):
+        """
+        Build the installer
+        """
+        self._build_pptviewlib()
+        self._create_innosetup_file()
+        self._run_innosetup()
+        if self.args.portable:
+            self._run_portableapp_builder()
+
+
+if __name__ == '__main__':
+    WindowsBuilder().main()

=== removed file 'osx/macosx-builder.py'
--- osx/macosx-builder.py	2016-11-10 20:33:23 +0000
+++ osx/macosx-builder.py	1970-01-01 00:00:00 +0000
@@ -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()

=== modified file 'windows/config-appveyor.ini'
--- windows/config-appveyor.ini	2016-11-29 13:38:50 +0000
+++ windows/config-appveyor.ini	2016-12-12 19:11:48 +0000
@@ -1,22 +1,24 @@
 [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
 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
+license = %(here)s\LICENSE.txt
+portable_source = %(here)s\OpenLPPortable
+portable_dest = %(projects)s\OpenLPPortable
 
 [transifex]
 username =

=== modified file 'windows/config.ini.default'
--- windows/config.ini.default	2016-11-29 13:38:50 +0000
+++ windows/config.ini.default	2016-12-12 19:11:48 +0000
@@ -8,15 +8,17 @@
 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
+license = %(here)s\LICENSE.txt
+portable_source = %(here)s\OpenLPPortable
+portable_dest = %(projects)s\OpenLPPortable
 
 [transifex]
 username =

=== removed file 'windows/windows-builder.py'
--- windows/windows-builder.py	2016-11-29 13:38:50 +0000
+++ windows/windows-builder.py	1970-01-01 00:00:00 +0000
@@ -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()


Follow ups