← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/new-dmg-build into lp:openlp/packaging

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/new-dmg-build into lp:openlp/packaging.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/new-dmg-build/+merge/310581

Update the macOS build system to use dmgbuild instead of manually running hdiutil and AppleScript.
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/new-dmg-build into lp:openlp/packaging.
=== modified file '.bzrignore'
--- .bzrignore	2014-06-14 03:40:58 +0000
+++ .bzrignore	2016-11-10 20:35:43 +0000
@@ -34,3 +34,4 @@
 *.dll
 *.DS_Store
 config.ini
+*.dmg

=== removed file 'osx/DmgImageInstallBackground.png'
Binary files osx/DmgImageInstallBackground.png	2012-06-24 22:35:37 +0000 and osx/DmgImageInstallBackground.png	1970-01-01 00:00:00 +0000 differ
=== removed file 'osx/applescript-adjust-dmg-view.master'
--- osx/applescript-adjust-dmg-view.master	2016-05-03 20:04:45 +0000
+++ osx/applescript-adjust-dmg-view.master	1970-01-01 00:00:00 +0000
@@ -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

=== modified file 'osx/config.ini.default'
--- osx/config.ini.default	2016-05-03 20:04:45 +0000
+++ osx/config.ini.default	2016-11-10 20:35:43 +0000
@@ -2,9 +2,7 @@
 sphinx = sphinx-build-3.4
 pyinstaller = %(projects)s/../pyinstaller/pyinstaller.py
 lrelease = /opt/local/libexec/qt5/bin/lrelease
-diskutil = diskutil
-hdiutil = hdiutil
-osascript = osascript
+dmgbuild = dmgbuild
 mudrawbin = mudraw
 mutoolbin = mutool
 
@@ -12,10 +10,9 @@
 branch = %(projects)s/trunk
 documentation = %(projects)s/documentation
 app_icon = %(here)s/OpenLP.icns
-dmg_icon = %(here)s/openlp-dmg.icns
 bundle_info = %(here)s/Info.plist
 hooks = %(here)s/../pyinstaller-hooks
-dmg_background = %(here)s/DmgImageInstallBackground.png
+dmg_settings = %(here)s/dmg-settings.py
 
 [transifex]
 username =

=== added file 'osx/dmg-background-new-with-icons.png'
Binary files osx/dmg-background-new-with-icons.png	1970-01-01 00:00:00 +0000 and osx/dmg-background-new-with-icons.png	2016-11-10 20:35:43 +0000 differ
=== added file 'osx/dmg-background-new.png'
Binary files osx/dmg-background-new.png	1970-01-01 00:00:00 +0000 and osx/dmg-background-new.png	2016-11-10 20:35:43 +0000 differ
=== added file 'osx/dmg-background.png'
Binary files osx/dmg-background.png	1970-01-01 00:00:00 +0000 and osx/dmg-background.png	2016-11-10 20:35:43 +0000 differ
=== added file 'osx/dmg-settings-new.py'
--- osx/dmg-settings-new.py	1970-01-01 00:00:00 +0000
+++ osx/dmg-settings-new.py	2016-11-10 20:35:43 +0000
@@ -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

=== added file 'osx/dmg-settings.py'
--- osx/dmg-settings.py	1970-01-01 00:00:00 +0000
+++ osx/dmg-settings.py	2016-11-10 20:35:43 +0000
@@ -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

=== modified file 'osx/macosx-builder.py'
--- osx/macosx-builder.py	2016-05-03 20:04:45 +0000
+++ osx/macosx-builder.py	2016-11-10 20:35:43 +0000
@@ -27,9 +27,9 @@
 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.3/3.4
+Python 3.4
 
-PyQt4
+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.
@@ -111,6 +111,8 @@
     """
     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)
@@ -237,11 +239,12 @@
         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.diskutil = _which(self.config.get('executables', 'diskutil'))
-        self.hdiutil = self.config.get('executables', 'hdiutil')
-        self.osascript = _which(self.config.get('executables', 'osascript'))
+        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):
         """
@@ -264,9 +267,7 @@
         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.dmg_icon = os.path.abspath(self.config.get('paths', 'dmg_icon'))
         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.source_path = os.path.join(self.work_path, 'openlp')
         self.manual_path = os.path.join(self.docs_path, 'manual')
@@ -275,12 +276,13 @@
         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_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):
         """
@@ -453,11 +455,12 @@
         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(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()
         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()
+            copy(self.mutool_lib, os.path.join(self.dist_path, 'libjbig2dec.0.dylib'))
         else:
             self._print('... WARNING: mudraw and mutool not found')
 
@@ -564,10 +567,10 @@
                 if code != 0:
                     raise Exception('Error running lconvert on %s' % source_path)
         self._print('Copying qm files...')
-        source = self.qt_translat_path
+        source = self.qt_translations_path
         files = os.listdir(source)
         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)
                 copy(os.path.join(source, filename), os.path.join(self.dist_path, 'i18n', filename))
 
@@ -625,86 +628,26 @@
         # 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)
 
-        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.
-        if os.path.exists(dmg_file):
-            os.remove(dmg_file)
+        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('... 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.
-        old_mounts = self._get_mountpoints()
-        self._print('... mounting the dmg file: %s' % dmg_file)
-        self._run_command([self.hdiutil, 'attach', dmg_file], 'Could not mount dmg file, cannot continue.')
-        new_mounts = self._get_mountpoints()
-        # Get the mount point from difference between paths
-        # 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.')
+
+        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.
@@ -713,40 +656,25 @@
             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(dmg_file) + '\n')
+            f.write('OPENLP_DMGNAME=' + os.path.basename(self.dmg_file) + '\n')
             f.close()
 
         # Dmg done.
-        self._print('Finished creating dmg file, resulting file: %s' % compressed_dmg)
-
-        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.')
+        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', 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('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)

=== removed file 'osx/openlp-dmg.icns'
Binary files osx/openlp-dmg.icns	2016-05-03 20:04:45 +0000 and osx/openlp-dmg.icns	1970-01-01 00:00:00 +0000 differ
=== removed file 'osx/openlp-dmg.png'
Binary files osx/openlp-dmg.png	2016-05-03 20:04:45 +0000 and osx/openlp-dmg.png	1970-01-01 00:00:00 +0000 differ
=== added file 'osx/openlp-logo-new.icns'
Binary files osx/openlp-logo-new.icns	1970-01-01 00:00:00 +0000 and osx/openlp-logo-new.icns	2016-11-10 20:35:43 +0000 differ
=== removed file 'osx/openlp-logo-new.png.icns'
Binary files osx/openlp-logo-new.png.icns	2016-05-03 20:04:45 +0000 and osx/openlp-logo-new.png.icns	1970-01-01 00:00:00 +0000 differ
=== removed file 'osx/openlp-new-dmg.png'
Binary files osx/openlp-new-dmg.png	2016-05-03 20:04:45 +0000 and osx/openlp-new-dmg.png	1970-01-01 00:00:00 +0000 differ
=== removed file 'osx/openlp-new-dmg.png.icns'
Binary files osx/openlp-new-dmg.png.icns	2016-05-03 20:04:45 +0000 and osx/openlp-new-dmg.png.icns	1970-01-01 00:00:00 +0000 differ

Follow ups