1
0
mirror of https://gitlab.com/openlp/packaging.git synced 2024-12-22 13:02:50 +00:00

Update the macOS build system to use dmgbuild instead of manually running hdiutil and AppleScript.

bzr-revno: 31
This commit is contained in:
raoul@snyman.info 2016-11-12 13:58:52 +02:00 committed by Raoul Snyman
commit ea2cf8459f
15 changed files with 87 additions and 139 deletions

View File

@ -34,3 +34,4 @@ __pycache__
*.dll *.dll
*.DS_Store *.DS_Store
config.ini config.ini
*.dmg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1,32 +0,0 @@
on run
-- wait for virus scanner
delay 2
tell application "Finder"
tell disk "%(dmg_name)s"
open
set current view of container window to icon view
set toolbar visible of container window to false
set statusbar visible of container window to false
set the bounds of container window to {400, 100, 1100, 500}
set theViewOptions to the icon view options of container window
set arrangement of theViewOptions to not arranged
set icon size of theViewOptions to 128
set background picture of theViewOptions to file ".background:installer-background.png"
set position of item "%(app_name)s" of container window to {160, 200}
set position of item "Applications" of container window to {550, 200}
set position of item ".background" of container window to {100, 500}
set position of item ".DS_Store" of container window to {200, 500}
set position of item ".fseventsd" of container window to {300, 500}
set position of item ".Trashes" of container window to {400, 500}
set position of item ".VolumeIcon.icns" of container window to {500, 500}
delay 5
close
open
update without registering applications
-- wait until the virus scan completes
delay 2
-- eject
end tell
end tell
end run

View File

@ -2,9 +2,7 @@
sphinx = sphinx-build-3.4 sphinx = sphinx-build-3.4
pyinstaller = %(projects)s/../pyinstaller/pyinstaller.py pyinstaller = %(projects)s/../pyinstaller/pyinstaller.py
lrelease = /opt/local/libexec/qt5/bin/lrelease lrelease = /opt/local/libexec/qt5/bin/lrelease
diskutil = diskutil dmgbuild = dmgbuild
hdiutil = hdiutil
osascript = osascript
mudrawbin = mudraw mudrawbin = mudraw
mutoolbin = mutool mutoolbin = mutool
@ -12,10 +10,9 @@ mutoolbin = mutool
branch = %(projects)s/trunk branch = %(projects)s/trunk
documentation = %(projects)s/documentation documentation = %(projects)s/documentation
app_icon = %(here)s/OpenLP.icns app_icon = %(here)s/OpenLP.icns
dmg_icon = %(here)s/openlp-dmg.icns
bundle_info = %(here)s/Info.plist bundle_info = %(here)s/Info.plist
hooks = %(here)s/../pyinstaller-hooks hooks = %(here)s/../pyinstaller-hooks
dmg_background = %(here)s/DmgImageInstallBackground.png dmg_settings = %(here)s/dmg-settings.py
[transifex] [transifex]
username = username =

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
osx/dmg-background-new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
osx/dmg-background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

27
osx/dmg-settings-new.py Normal file
View File

@ -0,0 +1,27 @@
import os
# This is the settings file for building the DMG. Run dmgbuild like so:
# $ dmgbuild -s dmg-settings.py -D size=<size>,app=<path/to/OpenLP.app> "OpenLP" OpenLP-{version}.dmg
HERE = os.getcwd()
format = 'UDBZ'
size = '600M'
files = [defines.get('app', '/Applications/OpenLP.app')]
symlinks = { 'Applications': '/Applications' }
badge_icon = os.path.join(HERE, 'openlp-logo-new.icns')
icon_locations = {
'OpenLP.app': (160, 200),
'Applications': (550, 200)
}
background = os.path.join(HERE, 'dmg-background-new.png')
window_rect = ((100, 100), (700, 457))
default_view = 'icon-view'
show_icon_preview = False
arrange_by = None
scroll_position = (0, 0)
grid_offset = (0, 0)
grid_spacing = 100
label_pos = 'bottom' # or 'right'
text_size = 16
icon_size = 128

27
osx/dmg-settings.py Normal file
View File

@ -0,0 +1,27 @@
import os
# This is the settings file for building the DMG. Run dmgbuild like so:
# $ dmgbuild -s dmg-settings.py -D size=<size>,app=<path/to/OpenLP.app> "OpenLP" OpenLP-{version}.dmg
HERE = os.getcwd()
format = 'UDZO'
size = defines.get('size', '600M')
files = [defines.get('app', '/Applications/OpenLP.app')]
symlinks = { 'Applications': '/Applications' }
badge_icon = defines.get('icon', 'OpenLP.icns')
icon_locations = {
'OpenLP.app': (160, 200),
'Applications': (550, 200)
}
background = os.path.join(HERE, 'dmg-background.png')
window_rect = ((100, 100), (700, 460))
default_view = 'icon-view'
show_icon_preview = False
arrange_by = None
scroll_position = (0, 0)
grid_offset = (0, 0)
grid_spacing = 100
label_pos = 'bottom' # or 'right'
text_size = 16
icon_size = 128

View File

@ -27,9 +27,9 @@ Mac OS X Build Script
This script is used to build the Mac OS X app bundle and pack it into dmg file. 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: For this script to work out of the box, it depends on a number of things:
Python 3.3/3.4 Python 3.4
PyQt4 PyQt5
You should already have this installed, OpenLP doesn't work without it. The 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 version the script expects is the packaged one available from River Bank
Computing. Computing.
@ -111,6 +111,8 @@ def _which(command):
""" """
Return absolute path to a command found on system PATH. Return absolute path to a command found on system PATH.
""" """
if command.startswith('/'):
return command
for path in os.environ["PATH"].split(os.pathsep): for path in os.environ["PATH"].split(os.pathsep):
if os.access(os.path.join(path, command), os.X_OK): if os.access(os.path.join(path, command), os.X_OK):
return "%s/%s" % (path, command) return "%s/%s" % (path, command)
@ -237,11 +239,12 @@ class MacosxBuilder(object):
self.sphinx = _which(self.config.get('executables', 'sphinx')) self.sphinx = _which(self.config.get('executables', 'sphinx'))
self.pyinstaller = os.path.abspath(self.config.get('executables', 'pyinstaller')) self.pyinstaller = os.path.abspath(self.config.get('executables', 'pyinstaller'))
self.lrelease = self.config.get('executables', 'lrelease') self.lrelease = self.config.get('executables', 'lrelease')
self.diskutil = _which(self.config.get('executables', 'diskutil')) self.dmgbuild = _which(self.config.get('executables', 'dmgbuild'))
self.hdiutil = self.config.get('executables', 'hdiutil')
self.osascript = _which(self.config.get('executables', 'osascript'))
self.mudraw_bin = _which(self.config.get('executables', 'mudrawbin')) self.mudraw_bin = _which(self.config.get('executables', 'mudrawbin'))
self.mutool_bin = _which(self.config.get('executables', 'mutoolbin')) 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): def setup_paths(self):
""" """
@ -264,9 +267,7 @@ class MacosxBuilder(object):
self.openlp_script = os.path.abspath(os.path.join(self.work_path, 'openlp.py')) 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.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.app_icon = os.path.abspath(self.config.get('paths', 'app_icon'))
self.dmg_icon = os.path.abspath(self.config.get('paths', 'dmg_icon'))
self.bundle_info = os.path.abspath(self.config.get('paths', 'bundle_info')) self.bundle_info = os.path.abspath(self.config.get('paths', 'bundle_info'))
self.dmg_background_img = os.path.abspath(self.config.get('paths', 'dmg_background'))
self.i18n_utils = os.path.join(self.work_path, 'scripts', 'translation_utils.py') self.i18n_utils = os.path.join(self.work_path, 'scripts', 'translation_utils.py')
self.source_path = os.path.join(self.work_path, 'openlp') self.source_path = os.path.join(self.work_path, 'openlp')
self.manual_path = os.path.join(self.docs_path, 'manual') self.manual_path = os.path.join(self.docs_path, 'manual')
@ -275,12 +276,13 @@ class MacosxBuilder(object):
self.build_path = os.path.join(self.work_path, 'build') 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_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.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. # Path to Qt translation files.
from PyQt5.QtCore import QCoreApplication from PyQt5.QtCore import QCoreApplication
qt_plug_dir = str(list(QCoreApplication.libraryPaths())[0]) qt_plug_dir = str(list(QCoreApplication.libraryPaths())[0])
self.qt_translat_path = os.path.join(os.path.dirname(qt_plug_dir), 'translations') self.qt_translations_path = os.path.join(os.path.dirname(qt_plug_dir), 'translations')
def update_code(self): def update_code(self):
""" """
@ -453,11 +455,12 @@ class MacosxBuilder(object):
copy(os.path.join(self.script_path, 'LICENSE.txt'), os.path.join(self.dist_path, 'LICENSE.txt')) copy(os.path.join(self.script_path, 'LICENSE.txt'), os.path.join(self.dist_path, 'LICENSE.txt'))
self._print_verbose('... mudraw') self._print_verbose('... mudraw')
if self.mudraw_bin and os.path.isfile(self.mudraw_bin): if self.mudraw_bin and os.path.isfile(self.mudraw_bin):
copy(os.path.join(self.mudraw_bin), os.path.join(self.dist_path, 'mudraw')) copy(self.mudraw_bin, os.path.join(self.dist_path, 'mudraw'))
self.relink_mudraw() self.relink_mudraw()
elif self.mutool_bin and os.path.isfile(self.mutool_bin): elif self.mutool_bin and os.path.isfile(self.mutool_bin):
copy(os.path.join(self.mutool_bin), os.path.join(self.dist_path, 'mutool')) copy(self.mutool_bin, os.path.join(self.dist_path, 'mutool'))
self.relink_mutool() self.relink_mutool()
copy(self.mutool_lib, os.path.join(self.dist_path, 'libjbig2dec.0.dylib'))
else: else:
self._print('... WARNING: mudraw and mutool not found') self._print('... WARNING: mudraw and mutool not found')
@ -564,10 +567,10 @@ class MacosxBuilder(object):
if code != 0: if code != 0:
raise Exception('Error running lconvert on %s' % source_path) raise Exception('Error running lconvert on %s' % source_path)
self._print('Copying qm files...') self._print('Copying qm files...')
source = self.qt_translat_path source = self.qt_translations_path
files = os.listdir(source) files = os.listdir(source)
for filename in files: for filename in files:
if filename.startswith('qt_') and filename.endswith('.qm') and len(filename) == 8: if filename.startswith('qt_') and filename.endswith('.qm'):
self._print_verbose('... %s', filename) self._print_verbose('... %s', filename)
copy(os.path.join(source, filename), os.path.join(self.dist_path, 'i18n', filename)) copy(os.path.join(source, filename), os.path.join(self.dist_path, 'i18n', filename))
@ -625,86 +628,26 @@ class MacosxBuilder(object):
# Release version does not contain revision in .dmg name. # Release version does not contain revision in .dmg name.
if self.args.devel: if self.args.devel:
dmg_name = 'OpenLP-' + str(self.version_string) + '.dmg' dmg_name = 'OpenLP-' + str(self.version_string) + '.dmg'
dmg_title = 'OpenLP {version}'.format(version=self.version_string)
else: else:
dmg_name = 'OpenLP-' + str(self.version_tag) + '.dmg' dmg_name = 'OpenLP-' + str(self.version_tag) + '.dmg'
dmg_title = 'OpenLP {version}'.format(version=self.version_tag)
dmg_file = os.path.join(self.work_path, 'build', dmg_name) self.dmg_file = os.path.join(self.work_path, 'dist', dmg_name)
# Remove dmg if it exists. # Remove dmg if it exists.
if os.path.exists(dmg_file): if os.path.exists(self.dmg_file):
os.remove(dmg_file) os.remove(self.dmg_file)
# Create empty dmg file. # Create empty dmg file.
size = self._get_directory_size(self.dist_app_path) # in bytes. size = self._get_directory_size(self.dist_app_path) # in bytes.
size = size / (1000 * 1000) # Convert to megabytes. size = size / (1000 * 1000) # Convert to megabytes.
size += 10 # Additional space in .dmg for other files. size += 10 # Additional space in .dmg for other files.
self._print('... dmg disk size: %s' % size)
self._run_command([self.hdiutil, 'create', dmg_file, '-ov', '-megabytes', str(size), '-fs', 'HFS+', '-volname',
'OpenLP'], 'Could not create dmg file.')
# Mount empty dmg file. self._print('... %s' % self.script_path)
old_mounts = self._get_mountpoints() os.chdir(self.script_path)
self._print('... mounting the dmg file: %s' % dmg_file) self._run_command([self.dmgbuild, '-s', self.dmg_settings, '-D', 'size={size}M'.format(size=size),
self._run_command([self.hdiutil, 'attach', dmg_file], 'Could not mount dmg file, cannot continue.') '-D', 'icon={icon_path}'.format(icon_path=self.app_icon),
new_mounts = self._get_mountpoints() '-D', 'app={dist_app_path}'.format(dist_app_path=self.dist_app_path), dmg_title, self.dmg_file],
# Get the mount point from difference between paths 'Unable to run dmgbuild')
# after mounting and before mounting the dmg file.
dmg_volume_path = list(set(new_mounts) - set(old_mounts))[0]
# Copy OpenLP.app and other files to .dmg
# TODO more reliable way to determine dmg_volume_path
self._print('... Copying the app to the dmg: ' + dmg_volume_path)
self._run_command(['cp', '-R', self.dist_app_path, dmg_volume_path],
'Could not copy app bundle, dmg creation failed.')
# Set icon for dmg file.
# http://endrift.com/blog/2010/06/14/dmg-files-volume-icons-cli/
self._print('... Setting the dmg icon.')
dmg_icon = os.path.join(dmg_volume_path, '.VolumeIcon.icns')
self._run_command(['cp', self.dmg_icon, dmg_icon], 'Could not copy the dmg icon file, dmg creation failed.')
# Set proper dmg icon attributes.
self._run_command(['SetFile', '-c', 'icnC', dmg_icon], 'Could not set dmg icon attributes.')
# Ensures dmg icon will be used while mounted.
self._run_command(['SetFile', '-a', 'C', dmg_volume_path], 'Could not set dmg icon attributes.')
# Create symlink in dmg pointing to the /Applications directory on OS X.
self._print('... Creating symlink to /Applications.')
os.symlink('/Applications', os.path.join(dmg_volume_path, 'Applications'))
# Set dmg background. Requires running Mac OS X gui.
# TODO: better formatting and code refactoring
if not self.args.devel:
self._print('... Setting the background image.')
os.mkdir(os.path.join(dmg_volume_path, '.background'))
self._run_command(['cp', self.dmg_background_img, os.path.join(dmg_volume_path,
'.background/installer-background.png')],
'Could not copy the background image, dmg creation failed.')
self.adjust_dmg_view(os.path.basename(dmg_volume_path))
# Unmount dmg file.
self._print('... unmounting the dmg.')
# Sometimes it could happen that OSX Finder is blocking umount.
# We need to find this process and kill it.
try:
output = subprocess.check_output(['fuser', dmg_volume_path]).strip()
if output:
blocking_proc_pid = int(output.split()[0])
os.kill(int(blocking_proc_pid), signal.SIGKILL)
except Exception as e:
print(str(e))
self._print('... failed to kill process using %s' % dmg_volume_path)
# Unmount dmg file.
self._run_command([self.hdiutil, 'detach', dmg_volume_path],
'Could not unmount the dmg file, dmg creation failed.')
# Compress dmg file.
self._print('... compressing the dmg file')
compressed_dmg = os.path.join(self.work_path, 'dist', os.path.basename(dmg_file)) # Put dmg to 'dist' dir.
# Remove dmg if it exists.
if os.path.exists(compressed_dmg):
os.remove(compressed_dmg)
self._run_command([self.hdiutil, 'convert', dmg_file, '-format', 'UDZO', '-imagekey', 'zlib-level=9', '-o',
compressed_dmg], 'Could not compress the dmg file, dmg creation failed.')
# Jenkins integration. # Jenkins integration.
# Continuous integration server needs to know the filename of dmg. # Continuous integration server needs to know the filename of dmg.
@ -713,40 +656,25 @@ class MacosxBuilder(object):
fpath = os.path.join(self.branch_path, 'openlp.properties') fpath = os.path.join(self.branch_path, 'openlp.properties')
self._print('... writing property file for jenkins: %s' % fpath) self._print('... writing property file for jenkins: %s' % fpath)
f = open(fpath, 'w') f = open(fpath, 'w')
f.write('OPENLP_DMGNAME=' + os.path.basename(dmg_file) + '\n') f.write('OPENLP_DMGNAME=' + os.path.basename(self.dmg_file) + '\n')
f.close() f.close()
# Dmg done. # Dmg done.
self._print('Finished creating dmg file, resulting file: %s' % compressed_dmg) self._print('Finished creating dmg file, resulting file: %s' % self.dmg_file)
self.dmg_file = compressed_dmg
def adjust_dmg_view(self, dmg_volume_name):
try:
master_script = os.path.join(self.script_path, 'applescript-adjust-dmg-view.master')
apple_script = os.path.join(self.script_path, 'adjust-dmg-view.applescript')
with open(master_script) as r, open(apple_script, 'w') as w:
apple_script = r.read() % {'dmg_name': dmg_volume_name, 'app_name': 'OpenLP.app'}
w.write(apple_script)
p = Popen([self.osascript, apple_script])
result = p.returncode
if (result != 0):
self._print('Adjusting dmg view failed (non-zero exit code).')
except (IOError, OSError):
self._print('Adjusting dmg view failed.')
def main(self): def main(self):
""" """
The main function to run the Mac OS X builder. The main function to run the Mac OS X builder.
""" """
self._print_verbose('OpenLP main script: ......%s', self.openlp_script) self._print_verbose('OpenLP main script: ......%s', self.openlp_script)
self._print_verbose('Script path: .............%s', os.path.split(os.path.abspath(__file__))[0]) self._print_verbose('Script path: .............%s', self.script_path)
self._print_verbose('Branch path: .............%s', self.branch_path) self._print_verbose('Branch path: .............%s', self.branch_path)
self._print_verbose('Source path: .............%s', self.source_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.app" path: .........%s', self.dist_app_path)
self._print_verbose('"dist" path: .............%s', self.dist_path) self._print_verbose('"dist" path: .............%s', self.dist_path)
self._print_verbose('"hooks" path: ............%s', self.hooks_path) self._print_verbose('"hooks" path: ............%s', self.hooks_path)
self._print_verbose('PyInstaller: .............%s', self.pyinstaller) self._print_verbose('PyInstaller: .............%s', self.pyinstaller)
self._print_verbose('dmgbuild: ................%s', self.dmgbuild)
self._print_verbose('Documentation branch path:%s', self.docs_path) self._print_verbose('Documentation branch path:%s', self.docs_path)
if self.mudraw_bin: if self.mudraw_bin:
self._print_verbose('mudraw binary ............%s', self.mudraw_bin) self._print_verbose('mudraw binary ............%s', self.mudraw_bin)

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.