\d))?((?Pa|b|rc)(?P\d))?')
def _which(program):
@@ -157,6 +159,16 @@ class Builder(object):
output, _ = self._run_command(['git', command] + args, err_msg)
return output
+ def parse_pep440_version(self, version: str) -> dict:
+ """Parse a PEP440-compatible version number into a dictionary"""
+ if m := PEP440.match(version):
+ groups = m.groupdict()
+ if not groups.get('fix'):
+ groups['fix'] = '0'
+ return groups
+ else:
+ return None
+
def get_platform(self):
"""
Return the platform we're building for
diff --git a/builders/macosx-builder.py b/builders/macosx-builder.py
index 5074853..cca49ff 100644
--- a/builders/macosx-builder.py
+++ b/builders/macosx-builder.py
@@ -92,6 +92,7 @@ like this:
import glob
import os
+import platform
from pathlib import Path
from shutil import copy, copytree, move, rmtree
@@ -106,6 +107,13 @@ class MacOSXBuilder(Builder):
The :class:`MacosxBuilder` class encapsulates everything that is needed
to build a Mac OS X .dmg file.
"""
+ def _pep440_to_mac_version(self, version: str) -> str:
+ """Convert a PEP440-compatible version to a Mac compatible version"""
+ if pep440 := self.parse_pep440_version(version):
+ return f'{pep440["major"]}.{pep440["minor"]}.{pep440["fix"]}'
+ else:
+ return '0.0.0'
+
def _get_directory_size(self, directory):
"""
Return directory size - size of everything in the dir.
@@ -261,14 +269,14 @@ class MacOSXBuilder(Builder):
except Exception:
pass
- def _install_pyro4(self):
+ def _install_pyro5(self):
"""
- Install Pyro4 into the vendor directory
+ Install Pyro5 into the vendor directory
"""
- self._print('Installing Pyro4 for LibreOffice')
+ self._print('Installing Pyro5 for LibreOffice')
target = os.path.join(self.dist_path, 'plugins', 'presentations', 'lib', 'vendor')
- argv = ['pip', 'install', 'Pyro4', '-t', target, '--disable-pip-version-check', '--no-compile']
- self._run_module('pip', argv, 'Error installing pyro4 with pip', run_name='__main__')
+ argv = ['pip', 'install', 'Pyro5', '-t', target, '--disable-pip-version-check', '--no-compile']
+ self._run_module('pip', argv, 'Error installing Pyro5 with pip', run_name='__main__')
egg_info_glob = glob.glob(os.path.join(target, '*.egg-info'))
egg_info_glob.extend(glob.glob(os.path.join(target, '*.dist-info')))
self._print_verbose('... glob: {}'.format(egg_info_glob))
@@ -279,13 +287,15 @@ class MacOSXBuilder(Builder):
"""
Copy the VLC files into the app bundle
"""
+ self._print('Copying VLC files')
vlc_path = '/Applications/VLC.app/Contents/MacOS/'
vlc_dest = os.path.join(self.dist_path, 'vlc')
if not os.path.exists(vlc_dest):
os.makedirs(vlc_dest)
for fname in ['libvlc.dylib', 'libvlccore.dylib']:
self._print_verbose('... {}'.format(fname))
- copy(os.path.join(vlc_path, 'lib', fname), os.path.join(vlc_dest, fname))
+ dest_file = os.readlink(os.path.join(vlc_path, 'lib', fname))
+ copy(os.path.join(vlc_path, 'lib', dest_file), os.path.join(vlc_dest, fname))
self._relink_binary(os.path.join('vlc', fname))
if os.path.exists(os.path.join(vlc_dest, 'plugins')):
rmtree(os.path.join(vlc_dest, 'plugins'))
@@ -308,7 +318,7 @@ class MacOSXBuilder(Builder):
with open(os.path.join(self.dist_app_path, 'Contents', os.path.basename(self.bundle_info_path)), 'w') as fw, \
open(self.bundle_info_path, 'r') as fr:
text = fr.read()
- text = text % {'openlp_version': self.version}
+ text = text % {'openlp_version': self._pep440_to_mac_version(self.version)}
fw.write(text)
def _copy_macosx_files(self):
@@ -335,8 +345,9 @@ class MacOSXBuilder(Builder):
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)
+ arch = platform.machine()
+ dmg_name = f'OpenLP-{self.version}-{arch}.dmg'
+ dmg_title = f'OpenLP {self.version}'
self.dmg_file = os.path.join(self.work_path, 'dist', dmg_name)
# Remove dmg if it exists.
@@ -393,7 +404,7 @@ class MacOSXBuilder(Builder):
# self._copy_vlc_files()
self._copy_bundle_files()
self._copy_macosx_files()
- self._install_pyro4()
+ self._install_pyro5()
def after_run_sphinx(self):
"""
diff --git a/builders/windows-builder.py b/builders/windows-builder.py
index f6782cd..d04745b 100644
--- a/builders/windows-builder.py
+++ b/builders/windows-builder.py
@@ -114,6 +114,40 @@ class WindowsBuilder(Builder):
The :class:`WindowsBuilder` class encapsulates everything that is needed
to build a Windows installer.
"""
+ # Make mypy happy
+ program_files: str
+ candle_exe: str
+ light_exe: str
+ htmlhelp_exe: str
+ mutool_exe: str
+ dist_path: str
+ icon_path: str
+ license_path: str
+ portable_source_path: str
+ portable_dest_path: str
+ portablelauncher_exe: str
+ portableinstaller_exe: str
+
+ def _pep440_to_windows_version(self, version: str) -> str:
+ """Convert a PEP440-compatible version string to a Windows version string"""
+ if pep440 := self.parse_pep440_version(version):
+ build_number = 5000
+ if pep440.get('pre'):
+ if pep440['pre'] == 'a':
+ build_number = 1000
+ elif pep440['pre'] == 'b':
+ build_number = 2000
+ elif pep440['pre'] == 'rc':
+ build_number = 3000
+ if pep440.get('rel'):
+ try:
+ rel = int(pep440['rel'])
+ build_number += rel
+ except ValueError:
+ pass
+ return f'{pep440["major"]}.{pep440["minor"]}.{pep440["fix"]}.{build_number}'
+ else:
+ return '0.0.0.0'
def _walk_dirs(self, dir_dict, path):
"""
@@ -214,12 +248,11 @@ class WindowsBuilder(Builder):
self._print_verbose('Reading base WiX file')
with open(os.path.join(config_dir, 'OpenLP-base.wxs'), 'rt') as base_file:
xml = base_file.read()
- # convert the version string to format x.x.x if needed
if '.dev' in self.version:
windows_version = self.version.replace('.dev', '.')
windows_version = windows_version.rsplit('+', 1)[0]
else:
- windows_version = self.version
+ windows_version = self._pep440_to_windows_version(self.version)
xml = xml % {
'dialog': os.path.join(config_dir, 'WizardMain.bmp'),
'banner': os.path.join(config_dir, 'WizardBanner.bmp'),
@@ -295,7 +328,7 @@ class WindowsBuilder(Builder):
version = version + '.0' * (2 - version.count('.'))
self.portable_version = version + '.' + revision.split('+')[0]
else:
- self.portable_version = self.version + '.0' * (3 - self.version.count('.'))
+ self.portable_version = self._pep440_to_windows_version(self.version)
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()
@@ -395,7 +428,7 @@ class WindowsBuilder(Builder):
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')
+ self.program_files = os.environ['PROGRAMFILES']
self.program_files_x86 = os.getenv('PROGRAMFILES(x86)')
self._print_verbose(' {:.<20}: {}'.format('site packages: ', self.site_packages))
self._print_verbose(' {:.<20}: {}'.format('program files: ', self.program_files))
diff --git a/osx/Info.plist b/osx/Info.plist
index 6fdc63c..6f4e96b 100755
--- a/osx/Info.plist
+++ b/osx/Info.plist
@@ -134,6 +134,8 @@
NSAppleScriptEnabled
+ NSAppleEventsUsageDescription
+ OpenLP needs to control System Events to be able control Keynote and PowerPoint
CFBundlePackageType
APPL
NSHighResolutionCapable
diff --git a/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.keynotecontroller.py b/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.keynotecontroller.py
new file mode 100644
index 0000000..27371f0
--- /dev/null
+++ b/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.keynotecontroller.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 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 #
+###############################################################################
+
+hiddenimports = ['applescript']
diff --git a/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.maclocontroller.py b/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.maclocontroller.py
index 3f58c8f..e43cbee 100644
--- a/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.maclocontroller.py
+++ b/pyinstaller-hooks/hook-openlp.plugins.presentations.lib.maclocontroller.py
@@ -20,4 +20,4 @@
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
-hiddenimports = ['Pyro4']
+hiddenimports = ['Pyro5']
diff --git a/tests/test_version.py b/tests/test_version.py
new file mode 100644
index 0000000..8e71847
--- /dev/null
+++ b/tests/test_version.py
@@ -0,0 +1,35 @@
+import re
+import pytest
+
+PEP440 = re.compile(r'(?P\d)\.(?P\d)(\.(?P\d))?((?Pa|b|rc)(?P\d))?')
+
+
+def _pep440_to_windows_version(version: str) -> str:
+ """Convert a PEP440-compatible version string to a Windows version string"""
+ if m := PEP440.match(version):
+ groups = m.groupdict()
+ if not groups.get('fix'):
+ groups['fix'] = '0'
+ build_number = 5000
+ if groups.get('pre'):
+ if groups['pre'] == 'a':
+ build_number = 1000
+ elif groups['pre'] == 'b':
+ build_number = 2000
+ elif groups['pre'] == 'rc':
+ build_number = 3000
+ if groups.get('rel'):
+ try:
+ rel = int(groups['rel'])
+ build_number += rel
+ except ValueError:
+ pass
+ return f'{groups["major"]}.{groups["minor"]}.{groups["fix"]}.{build_number}'
+ else:
+ return '0.0.0.0'
+
+
+@pytest.mark.parametrize('pep440_version, windows_version', [
+ ('2.9.1', '2.9.1.5000'), ('3.1rc1', '3.1.0.3001'), ('3.0.2b2', '3.0.2.2002'), ('1.9a1', '1.9.0.1001')])
+def test_pep440_to_windows_version(pep440_version, windows_version):
+ assert _pep440_to_windows_version(pep440_version) == windows_version