← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/fix-macos-codesign into lp:openlp/packaging

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/fix-macos-codesign into lp:openlp/packaging.

Commit message:
macOS codesigning fails on Apps with periods in file names. Incorporated fixes from PyInstaller's wiki and also updated the version number to match our new versioning scheme.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/fix-macos-codesign/+merge/359972

macOS codesigning fails on Apps with periods in file names. Incorporated fixes from PyInstaller's wiki and also updated the version number to match our new versioning scheme.
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/fix-macos-codesign into lp:openlp/packaging.
=== modified file 'builders/builder.py'
--- builders/builder.py	2018-10-27 06:08:24 +0000
+++ builders/builder.py	2018-12-02 06:11:42 +0000
@@ -311,7 +311,7 @@
                     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)
+                self.version = '{tag}.dev{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))

=== modified file 'builders/macosx-builder.py'
--- builders/macosx-builder.py	2016-12-06 20:51:27 +0000
+++ builders/macosx-builder.py	2018-12-02 06:11:42 +0000
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
 
 ###############################################################################
 # OpenLP - Open Source Lyrics Projection                                      #
@@ -94,15 +94,15 @@
 """
 
 import os
-import plistlib
-import signal
-from shutil import copy, copytree
+from pathlib import Path
+from shutil import copy, copytree, move, rmtree
 
 from macholib.MachO import MachO
-from macholib.util import flipwritable, in_system_path
+from macholib.util import in_system_path
 
 from builder import Builder
 
+
 class MacOSXBuilder(Builder):
     """
     The :class:`MacosxBuilder` class encapsulates everything that is needed
@@ -119,6 +119,99 @@
                 dir_size += os.path.getsize(filename)
         return dir_size
 
+    def _create_symlink(self, folder):
+        """
+        Create the appropriate symlink in the MacOS folder pointing to the Resources folder.
+        """
+        sibling = Path(str(folder).replace('MacOS', ''))
+
+        # PyQt5/Qt/qml/QtQml/Models.2
+        root = str(sibling).partition('Contents')[2].lstrip('/')
+        # ../../../../
+        backward = '../' * len(root.split('/'))
+        # ../../../../Resources/PyQt5/Qt/qml/QtQml/Models.2
+        good_path = f'{backward}Resources/{root}'
+
+        folder.symlink_to(good_path)
+
+    def _fix_qt_dll(self, dll):
+        """
+        Fix the DLL lookup paths to use relative ones for Qt dependencies.
+        Inspiration: PyInstaller/depend/dylib.py:mac_set_relative_dylib_deps()
+        Currently one header is pointing to (we are in the Resources folder):
+            @loader_path/../../../../QtCore (it is referencing to the old MacOS folder)
+        It will be converted to:
+            @loader_path/../../../../../../MacOS/QtCore
+        """
+
+        def match_func(pth):
+            """
+            Callback function for MachO.rewriteLoadCommands() that is
+            called on every lookup path setted in the DLL headers.
+            By returning None for system libraries, it changes nothing.
+            Else we return a relative path pointing to the good file
+            in the MacOS folder.
+            """
+            basename = os.path.basename(pth)
+            if not basename.startswith('Qt'):
+                return None
+            return f'@loader_path{good_path}/{basename}'
+
+        # Resources/PyQt5/Qt/qml/QtQuick/Controls.2/Fusion
+        root = str(dll.parent).partition('Contents')[2][1:]
+        # /../../../../../../..
+        backward = '/..' * len(root.split('/'))
+        # /../../../../../../../MacOS
+        good_path = f'{backward}/MacOS'
+
+        # Rewrite Mach headers with corrected @loader_path
+        dll = MachO(dll)
+        dll.rewriteLoadCommands(match_func)
+        with open(dll.filename, 'rb+') as f:
+            for header in dll.headers:
+                f.seek(0)
+                dll.write(f)
+            f.seek(0, 2)
+            f.flush()
+
+    def _find_problematic_qt_folders(self, folder):
+        """
+        Recursively yields problematic folders (containing a dot in their name).
+        """
+        for path in folder.iterdir():
+            if not path.is_dir() or path.is_symlink():
+                # Skip simlinks as they are allowed (even with a dot)
+                continue
+            if '.' in path.name:
+                yield path
+            else:
+                yield from self._find_problematic_qt_folders(path)
+
+    def _move_contents_to_resources(self, folder):
+        """
+        Recursively move any non symlink file from a problematic folder to the sibling one in Resources.
+        """
+        for path in folder.iterdir():
+            if path.is_symlink():
+                continue
+            if path.is_dir():
+                yield from self._move_contents_to_resources(path)
+            else:
+                sibling = Path(str(path).replace('MacOS', 'Resources'))
+                move(path, sibling)
+                yield sibling
+
+    def _fix_qt_paths(self):
+        """
+        Fix the Qt paths
+        """
+        app_path = Path(self.dist_app_path) / 'Contents' / 'MacOS'
+        for folder in self._find_problematic_qt_folders(app_path):
+            for problematic_file in self._move_contents_to_resources(folder):
+                self._fix_qt_dll(problematic_file)
+            rmtree(folder)
+            self._create_symlink(folder)
+
     def _relink_mupdf(self, bin_name):
         """
         Relink mupdf to bundled libraries
@@ -181,7 +274,8 @@
         """
         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)))
+        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')
@@ -237,9 +331,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')
+                           '-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)
@@ -299,6 +394,7 @@
         """
         Build the actual DMG
         """
+        self._fix_qt_paths()
         self._code_sign()
         self._create_dmg()
 


Follow ups