]> Nutra Git (v2) - gamesguru/feather.git/commitdiff
macOS: deterministic bundling
authortobtoht <tob@featherwallet.org>
Wed, 15 Mar 2023 13:31:45 +0000 (14:31 +0100)
committertobtoht <tob@featherwallet.org>
Wed, 15 Mar 2023 14:11:56 +0000 (15:11 +0100)
CMakeLists.txt
contrib/guix/libexec/build.sh
contrib/macdeploy/detached-sig-create.sh [deleted file]
contrib/macdeploy/macdeployqtplus [deleted file]

index 40a68f783ed00641c64cb5a67eee9e352d15e55e..bb022f5a6c12ea55b54ac02bb5bd34d74dd6fcf2 100644 (file)
@@ -274,18 +274,17 @@ add_subdirectory(src)
 
 configure_file("${CMAKE_SOURCE_DIR}/contrib/installers/windows/setup.nsi.in" "${CMAKE_SOURCE_DIR}/contrib/installers/windows/setup.nsi" @ONLY)
 
-if(APPLE AND CMAKE_CROSSCOMPILING)
-    set(macos_app "Feather.app")
-    configure_file(contrib/macdeploy/Info.plist.in ${macos_app}/Contents/Info.plist @ONLY)
-    configure_file(src/assets/images/appicons/appicon.icns ${macos_app}/Contents/Resources/appicon.icns COPYONLY)
-
-    add_custom_target(deploy
-            COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${CMAKE_BINARY_DIR}/release" --strip
-            COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_BINARY_DIR}/${macos_app}/Contents/MacOS"
-            COMMAND "${CMAKE_COMMAND}" -E rename "${CMAKE_BINARY_DIR}/bin/$<TARGET_FILE_NAME:feather>" "${CMAKE_BINARY_DIR}/${macos_app}/Contents/MacOS/feather"
-            COMMAND PYTHONPATH=${PYTHONPATH} INSTALL_NAME_TOOL=${CMAKE_INSTALL_NAME_TOOL} OTOOL=${OTOOL} STRIP=${CMAKE_STRIP} ${CMAKE_SOURCE_DIR}/contrib/macdeploy/macdeployqtplus ${macos_app} ${PACKAGE_NAME} -zip
-            VERBATIM
-            )
+if(APPLE)
+    configure_file(${CMAKE_SOURCE_DIR}/contrib/macdeploy/Info.plist.in ${CMAKE_SOURCE_DIR}/contrib/macdeploy/Info.plist @ONLY)
+
+    set_target_properties(feather PROPERTIES
+            RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
+            MACOSX_BUNDLE TRUE
+            MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/contrib/macdeploy/Info.plist"
+            LINK_FLAGS_RELEASE -s
+    )
+
+    file(COPY "${CMAKE_SOURCE_DIR}/src/assets/images/appicons/appicon.icns" DESTINATION "${CMAKE_SOURCE_DIR}/installed/feather.app/Contents/Resources/" )
 endif()
 
 message("\n")
index 0dade7f12b063358c4b8fc13e6b65558cd530bf7..faa6d348832e4f4f2fce9585867d0e3b060ea5d8 100755 (executable)
@@ -349,14 +349,6 @@ mkdir -p "$DISTSRC"
             ;;
     esac
 
-    # Make macOS DMG
-    case "$HOST" in
-        *darwin*)
-            make -C build deploy ${V:+V=1}
-            mv build/feather.zip "${OUTDIR}/${DISTNAME}.zip"
-            ;;
-    esac
-
     (
         cd installed
 
@@ -364,6 +356,9 @@ mkdir -p "$DISTSRC"
             *linux*)
                 mv feather "${DISTNAME}"
                 ;;
+            *darwin*)
+                mv "feather.app" "Feather.app"
+                ;;
         esac
 
         # Finally, deterministically produce {non-,}debug binary tarballs ready
@@ -408,6 +403,14 @@ mkdir -p "$DISTSRC"
                     | zip -X@ "${OUTDIR}/${DISTNAME}-linux${LINUX_ARCH}-appimage${ANONDIST}.zip" \
                     || ( rm -f "${OUTDIR}/${DISTNAME}-linux${LINUX_ARCH}-appimage${ANONDIST}.zip" && exit 1 )
                 ;;
+            *darwin*)
+                find . -print0 \
+                    | xargs -0r touch --no-dereference --date="@${SOURCE_DATE_EPOCH}"
+                find . \
+                    | sort \
+                    | zip -X@ "${OUTDIR}/${DISTNAME}-mac.zip" \
+                    || ( rm -f "${OUTDIR}/${DISTNAME}-mac.zip" && exit 1 )
+                ;;
         esac
 
     )
diff --git a/contrib/macdeploy/detached-sig-create.sh b/contrib/macdeploy/detached-sig-create.sh
deleted file mode 100755 (executable)
index f393331..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2014-2021 The Bitcoin Core developers
-# Distributed under the MIT software license, see the accompanying
-# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-
-export LC_ALL=C
-set -e
-
-ROOTDIR=dist
-BUNDLE="${ROOTDIR}/Bitcoin-Qt.app"
-BINARY="${BUNDLE}/Contents/MacOS/Bitcoin-Qt"
-SIGNAPPLE=signapple
-TEMPDIR=sign.temp
-ARCH=$(${SIGNAPPLE} info ${BINARY} | head -n 1 | cut -d " " -f 1)
-OUT="signature-osx-${ARCH}.tar.gz"
-OUTROOT=osx/dist
-
-if [ -z "$1" ]; then
-  echo "usage: $0 <signapple args>"
-  echo "example: $0 <path to key>"
-  exit 1
-fi
-
-rm -rf ${TEMPDIR}
-mkdir -p ${TEMPDIR}
-
-${SIGNAPPLE} sign -f --detach "${TEMPDIR}/${OUTROOT}"  "$@" "${BUNDLE}"
-
-tar -C "${TEMPDIR}" -czf "${OUT}" .
-rm -rf "${TEMPDIR}"
-echo "Created ${OUT}"
diff --git a/contrib/macdeploy/macdeployqtplus b/contrib/macdeploy/macdeployqtplus
deleted file mode 100755 (executable)
index 9988d47..0000000
+++ /dev/null
@@ -1,512 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2011  Patrick "p2k" Schneider <me@p2k-network.org>
-#
-# 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, either version 3 of the License, or
-# (at your option) any later version.
-#
-# 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, see <http://www.gnu.org/licenses/>.
-#
-
-import sys, re, os, platform, shutil, stat, subprocess, os.path
-from argparse import ArgumentParser
-from pathlib import Path
-from subprocess import PIPE, run
-from typing import List, Optional
-
-# This is ported from the original macdeployqt with modifications
-
-class FrameworkInfo(object):
-    def __init__(self):
-        self.frameworkDirectory = ""
-        self.frameworkName = ""
-        self.frameworkPath = ""
-        self.binaryDirectory = ""
-        self.binaryName = ""
-        self.binaryPath = ""
-        self.version = ""
-        self.installName = ""
-        self.deployedInstallName = ""
-        self.sourceFilePath = ""
-        self.destinationDirectory = ""
-        self.sourceResourcesDirectory = ""
-        self.sourceVersionContentsDirectory = ""
-        self.sourceContentsDirectory = ""
-        self.destinationResourcesDirectory = ""
-        self.destinationVersionContentsDirectory = ""
-    
-    def __eq__(self, other):
-        if self.__class__ == other.__class__:
-            return self.__dict__ == other.__dict__
-        else:
-            return False
-    
-    def __str__(self):
-        return f""" Framework name: {self.frameworkName}
- Framework directory: {self.frameworkDirectory}
- Framework path: {self.frameworkPath}
- Binary name: {self.binaryName}
- Binary directory: {self.binaryDirectory}
- Binary path: {self.binaryPath}
- Version: {self.version}
- Install name: {self.installName}
- Deployed install name: {self.deployedInstallName}
- Source file Path: {self.sourceFilePath}
- Deployed Directory (relative to bundle): {self.destinationDirectory}
-"""
-    
-    def isDylib(self):
-        return self.frameworkName.endswith(".dylib")
-    
-    def isQtFramework(self):
-        if self.isDylib():
-            return self.frameworkName.startswith("libQt")
-        else:
-            return self.frameworkName.startswith("Qt")
-    
-    reOLine = re.compile(r'^(.+) \(compatibility version [0-9.]+, current version [0-9.]+\)$')
-    bundleFrameworkDirectory = "Contents/Frameworks"
-    bundleBinaryDirectory = "Contents/MacOS"
-    
-    @classmethod
-    def fromOtoolLibraryLine(cls, line: str) -> Optional['FrameworkInfo']:
-        # Note: line must be trimmed
-        if line == "":
-            return None
-        
-        # Don't deploy system libraries
-        if line.startswith("/System/Library/") or line.startswith("@executable_path") or line.startswith("/usr/lib/"):
-            return None
-        
-        m = cls.reOLine.match(line)
-        if m is None:
-            raise RuntimeError(f"otool line could not be parsed: {line}")
-        
-        path = m.group(1)
-        
-        info = cls()
-        info.sourceFilePath = path
-        info.installName = path
-        
-        if path.endswith(".dylib"):
-            dirname, filename = os.path.split(path)
-            info.frameworkName = filename
-            info.frameworkDirectory = dirname
-            info.frameworkPath = path
-            
-            info.binaryDirectory = dirname
-            info.binaryName = filename
-            info.binaryPath = path
-            info.version = "-"
-            
-            info.installName = path
-            info.deployedInstallName = f"@executable_path/../Frameworks/{info.binaryName}"
-            info.sourceFilePath = path
-            info.destinationDirectory = cls.bundleFrameworkDirectory
-        else:
-            parts = path.split("/")
-            i = 0
-            # Search for the .framework directory
-            for part in parts:
-                if part.endswith(".framework"):
-                    break
-                i += 1
-            if i == len(parts):
-                raise RuntimeError(f"Could not find .framework or .dylib in otool line: {line}")
-            
-            info.frameworkName = parts[i]
-            info.frameworkDirectory = "/".join(parts[:i])
-            info.frameworkPath = os.path.join(info.frameworkDirectory, info.frameworkName)
-            
-            info.binaryName = parts[i+3]
-            info.binaryDirectory = "/".join(parts[i+1:i+3])
-            info.binaryPath = os.path.join(info.binaryDirectory, info.binaryName)
-            info.version = parts[i+2]
-            
-            info.deployedInstallName = f"@executable_path/../Frameworks/{os.path.join(info.frameworkName, info.binaryPath)}"
-            info.destinationDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, info.binaryDirectory)
-            
-            info.sourceResourcesDirectory = os.path.join(info.frameworkPath, "Resources")
-            info.sourceContentsDirectory = os.path.join(info.frameworkPath, "Contents")
-            info.sourceVersionContentsDirectory = os.path.join(info.frameworkPath, "Versions", info.version, "Contents")
-            info.destinationResourcesDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Resources")
-            info.destinationVersionContentsDirectory = os.path.join(cls.bundleFrameworkDirectory, info.frameworkName, "Versions", info.version, "Contents")
-        
-        return info
-
-class ApplicationBundleInfo(object):
-    def __init__(self, path: str):
-        self.path = path
-        self.binaryPath = os.path.join(path, "Contents", "MacOS", "feather")
-        if not os.path.exists(self.binaryPath):
-            raise RuntimeError(f"Could not find bundle binary for {path}")
-        self.resourcesPath = os.path.join(path, "Contents", "Resources")
-        self.pluginPath = os.path.join(path, "Contents", "PlugIns")
-
-class DeploymentInfo(object):
-    def __init__(self):
-        self.qtPath = None
-        self.pluginPath = None
-        self.deployedFrameworks = []
-    
-    def detectQtPath(self, frameworkDirectory: str):
-        parentDir = os.path.dirname(frameworkDirectory)
-        if os.path.exists(os.path.join(parentDir, "translations")):
-            # Classic layout, e.g. "/usr/local/Trolltech/Qt-4.x.x"
-            self.qtPath = parentDir
-        else:
-            self.qtPath = os.getenv("QTDIR", None)
-
-        if self.qtPath is not None:
-            pluginPath = os.path.join(self.qtPath, "plugins")
-            if os.path.exists(pluginPath):
-                self.pluginPath = pluginPath
-    
-    def usesFramework(self, name: str) -> bool:
-        for framework in self.deployedFrameworks:
-            if framework.endswith(".framework"):
-                if framework.startswith(f"{name}."):
-                    return True
-            elif framework.endswith(".dylib"):
-                if framework.startswith(f"lib{name}."):
-                    return True
-        return False
-
-def getFrameworks(binaryPath: str, verbose: int) -> List[FrameworkInfo]:
-    if verbose:
-        print(f"Inspecting with otool: {binaryPath}")
-    otoolbin=os.getenv("OTOOL", "otool")
-    otool = run([otoolbin, "-L", binaryPath], stdout=PIPE, stderr=PIPE, universal_newlines=True)
-    if otool.returncode != 0:
-        sys.stderr.write(otool.stderr)
-        sys.stderr.flush()
-        raise RuntimeError(f"otool failed with return code {otool.returncode}")
-
-    otoolLines = otool.stdout.split("\n")
-    otoolLines.pop(0) # First line is the inspected binary
-    if ".framework" in binaryPath or binaryPath.endswith(".dylib"):
-        otoolLines.pop(0) # Frameworks and dylibs list themselves as a dependency.
-    
-    libraries = []
-    for line in otoolLines:
-        line = line.replace("@loader_path", os.path.dirname(binaryPath))
-        info = FrameworkInfo.fromOtoolLibraryLine(line.strip())
-        if info is not None:
-            if verbose:
-                print("Found framework:")
-                print(info)
-            libraries.append(info)
-    
-    return libraries
-
-def runInstallNameTool(action: str, *args):
-    installnametoolbin=os.getenv("INSTALL_NAME_TOOL", "install_name_tool")
-    run([installnametoolbin, "-"+action] + list(args), check=True)
-
-def changeInstallName(oldName: str, newName: str, binaryPath: str, verbose: int):
-    if verbose:
-        print("Using install_name_tool:")
-        print(" in", binaryPath)
-        print(" change reference", oldName)
-        print(" to", newName)
-    runInstallNameTool("change", oldName, newName, binaryPath)
-
-def changeIdentification(id: str, binaryPath: str, verbose: int):
-    if verbose:
-        print("Using install_name_tool:")
-        print(" change identification in", binaryPath)
-        print(" to", id)
-    runInstallNameTool("id", id, binaryPath)
-
-def runStrip(binaryPath: str, verbose: int):
-    stripbin=os.getenv("STRIP", "strip")
-    if verbose:
-        print("Using strip:")
-        print(" stripped", binaryPath)
-    run([stripbin, "-x", binaryPath], check=True)
-
-def copyFramework(framework: FrameworkInfo, path: str, verbose: int) -> Optional[str]:
-    if framework.sourceFilePath.startswith("Qt"):
-        #standard place for Nokia Qt installer's frameworks
-        fromPath = f"/Library/Frameworks/{framework.sourceFilePath}"
-    else:
-        fromPath = framework.sourceFilePath
-    toDir = os.path.join(path, framework.destinationDirectory)
-    toPath = os.path.join(toDir, framework.binaryName)
-
-    if framework.isDylib():
-        if not os.path.exists(fromPath):
-            raise RuntimeError(f"No file at {fromPath}")
-
-        if os.path.exists(toPath):
-            return None # Already there
-
-        if not os.path.exists(toDir):
-            os.makedirs(toDir)
-
-        shutil.copy2(fromPath, toPath)
-        if verbose:
-            print("Copied:", fromPath)
-            print(" to:", toPath)
-    else:
-        to_dir = os.path.join(path, "Contents", "Frameworks", framework.frameworkName)
-        if os.path.exists(to_dir):
-            return None # Already there
-
-        from_dir = framework.frameworkPath
-        if not os.path.exists(from_dir):
-            raise RuntimeError(f"No directory at {from_dir}")
-
-        shutil.copytree(from_dir, to_dir, symlinks=True)
-        if verbose:
-            print("Copied:", from_dir)
-            print(" to:", to_dir)
-
-        headers_link = os.path.join(to_dir, "Headers")
-        if os.path.exists(headers_link):
-            os.unlink(headers_link)
-
-        headers_dir = os.path.join(to_dir, framework.binaryDirectory, "Headers")
-        if os.path.exists(headers_dir):
-            shutil.rmtree(headers_dir)
-
-    permissions = os.stat(toPath)
-    if not permissions.st_mode & stat.S_IWRITE:
-      os.chmod(toPath, permissions.st_mode | stat.S_IWRITE)
-
-    return toPath
-
-def deployFrameworks(frameworks: List[FrameworkInfo], bundlePath: str, binaryPath: str, strip: bool, verbose: int, deploymentInfo: Optional[DeploymentInfo] = None) -> DeploymentInfo:
-    if deploymentInfo is None:
-        deploymentInfo = DeploymentInfo()
-    
-    while len(frameworks) > 0:
-        framework = frameworks.pop(0)
-        deploymentInfo.deployedFrameworks.append(framework.frameworkName)
-        
-        print("Processing", framework.frameworkName, "...")
-        
-        # Get the Qt path from one of the Qt frameworks
-        if deploymentInfo.qtPath is None and framework.isQtFramework():
-            deploymentInfo.detectQtPath(framework.frameworkDirectory)
-        
-        if framework.installName.startswith("@executable_path") or framework.installName.startswith(bundlePath):
-            print(framework.frameworkName, "already deployed, skipping.")
-            continue
-        
-        # install_name_tool the new id into the binary
-        changeInstallName(framework.installName, framework.deployedInstallName, binaryPath, verbose)
-        
-        # Copy framework to app bundle.
-        deployedBinaryPath = copyFramework(framework, bundlePath, verbose)
-        # Skip the rest if already was deployed.
-        if deployedBinaryPath is None:
-            continue
-        
-        if strip:
-            runStrip(deployedBinaryPath, verbose)
-        
-        # install_name_tool it a new id.
-        changeIdentification(framework.deployedInstallName, deployedBinaryPath, verbose)
-        # Check for framework dependencies
-        dependencies = getFrameworks(deployedBinaryPath, verbose)
-        
-        for dependency in dependencies:
-            changeInstallName(dependency.installName, dependency.deployedInstallName, deployedBinaryPath, verbose)
-            
-            # Deploy framework if necessary.
-            if dependency.frameworkName not in deploymentInfo.deployedFrameworks and dependency not in frameworks:
-                frameworks.append(dependency)
-    
-    return deploymentInfo
-
-def deployFrameworksForAppBundle(applicationBundle: ApplicationBundleInfo, strip: bool, verbose: int) -> DeploymentInfo:
-    frameworks = getFrameworks(applicationBundle.binaryPath, verbose)
-    if len(frameworks) == 0:
-        print(f"Warning: Could not find any external frameworks to deploy in {applicationBundle.path}.")
-        return DeploymentInfo()
-    else:
-        return deployFrameworks(frameworks, applicationBundle.path, applicationBundle.binaryPath, strip, verbose)
-
-def deployPlugins(appBundleInfo: ApplicationBundleInfo, deploymentInfo: DeploymentInfo, strip: bool, verbose: int):
-    plugins = []
-    if deploymentInfo.pluginPath is None:
-        return
-    for dirpath, dirnames, filenames in os.walk(deploymentInfo.pluginPath):
-        pluginDirectory = os.path.relpath(dirpath, deploymentInfo.pluginPath)
-
-        if pluginDirectory not in ['styles', 'platforms']:
-            continue
-
-        for pluginName in filenames:
-            pluginPath = os.path.join(pluginDirectory, pluginName)
-
-            if pluginName.split('.')[0] not in ['libqminimal', 'libqcocoa', 'libqmacstyle']:
-                continue
-
-            plugins.append((pluginDirectory, pluginName))
-    
-    for pluginDirectory, pluginName in plugins:
-        print("Processing plugin", os.path.join(pluginDirectory, pluginName), "...")
-        
-        sourcePath = os.path.join(deploymentInfo.pluginPath, pluginDirectory, pluginName)
-        destinationDirectory = os.path.join(appBundleInfo.pluginPath, pluginDirectory)
-        if not os.path.exists(destinationDirectory):
-            os.makedirs(destinationDirectory)
-        
-        destinationPath = os.path.join(destinationDirectory, pluginName)
-        shutil.copy2(sourcePath, destinationPath)
-        if verbose:
-            print("Copied:", sourcePath)
-            print(" to:", destinationPath)
-        
-        if strip:
-            runStrip(destinationPath, verbose)
-        
-        dependencies = getFrameworks(destinationPath, verbose)
-        
-        for dependency in dependencies:
-            changeInstallName(dependency.installName, dependency.deployedInstallName, destinationPath, verbose)
-            
-            # Deploy framework if necessary.
-            if dependency.frameworkName not in deploymentInfo.deployedFrameworks:
-                deployFrameworks([dependency], appBundleInfo.path, destinationPath, strip, verbose, deploymentInfo)
-
-ap = ArgumentParser(description="""Improved version of macdeployqt.
-
-Outputs a ready-to-deploy app in a folder "dist" and optionally wraps it in a .zip file.
-Note, that the "dist" folder will be deleted before deploying on each run.
-
-Optionally, Qt translation files (.qm) can be added to the bundle.""")
-
-ap.add_argument("app_bundle", nargs=1, metavar="app-bundle", help="application bundle to be deployed")
-ap.add_argument("appname", nargs=1, metavar="appname", help="name of the app being deployed")
-ap.add_argument("-verbose", nargs="?", const=True, help="Output additional debugging information")
-ap.add_argument("-no-plugins", dest="plugins", action="store_false", default=True, help="skip plugin deployment")
-ap.add_argument("-no-strip", dest="strip", action="store_false", default=True, help="don't run 'strip' on the binaries")
-ap.add_argument("-translations-dir", nargs=1, metavar="path", default=None, help="Path to Qt's translations. Base translations will automatically be added to the bundle's resources.")
-ap.add_argument("-zip", nargs="?", const="", metavar="zip", help="create a .zip containing the app bundle")
-
-config = ap.parse_args()
-
-verbose = config.verbose
-
-# ------------------------------------------------
-
-app_bundle = config.app_bundle[0]
-appname = config.appname[0]
-
-if not os.path.exists(app_bundle):
-    sys.stderr.write(f"Error: Could not find app bundle \"{app_bundle}\"\n")
-    sys.exit(1)
-
-# ------------------------------------------------
-
-if os.path.exists("dist"):
-    print("+ Removing existing dist folder +")
-    shutil.rmtree("dist")
-
-if os.path.exists(appname + ".zip"):
-    print("+ Removing existing .zip +")
-    os.unlink(appname + ".zip")
-
-# ------------------------------------------------
-
-target = os.path.join("dist", "Feather.app")
-
-print("+ Copying source bundle +")
-if verbose:
-    print(app_bundle, "->", target)
-
-os.mkdir("dist")
-shutil.copytree(app_bundle, target, symlinks=True)
-
-applicationBundle = ApplicationBundleInfo(target)
-
-# ------------------------------------------------
-
-print("+ Deploying frameworks +")
-
-try:
-    deploymentInfo = deployFrameworksForAppBundle(applicationBundle, config.strip, verbose)
-    if deploymentInfo.qtPath is None:
-        deploymentInfo.qtPath = os.getenv("QTDIR", None)
-        if deploymentInfo.qtPath is None:
-            sys.stderr.write("Warning: Could not detect Qt's path, skipping plugin deployment!\n")
-            config.plugins = False
-except RuntimeError as e:
-    sys.stderr.write(f"Error: {str(e)}\n")
-    sys.exit(1)
-
-# ------------------------------------------------
-
-if config.plugins:
-    print("+ Deploying plugins +")
-    
-    try:
-        deployPlugins(applicationBundle, deploymentInfo, config.strip, verbose)
-    except RuntimeError as e:
-        sys.stderr.write(f"Error: {str(e)}\n")
-        sys.exit(1)
-
-# ------------------------------------------------
-
-if config.translations_dir:
-    if not Path(config.translations_dir[0]).exists():
-        sys.stderr.write(f"Error: Could not find translation dir \"{config.translations_dir[0]}\"\n")
-        sys.exit(1)
-
-    print("+ Adding Qt translations +")
-
-    translations = Path(config.translations_dir[0])
-
-    regex = re.compile('qt_[a-z]*(.qm|_[A-Z]*.qm)')
-
-    lang_files = [x for x in translations.iterdir() if regex.match(x.name)]
-
-    for file in lang_files:
-        if verbose:
-            print(file.as_posix(), "->", os.path.join(applicationBundle.resourcesPath, file.name))
-        shutil.copy2(file.as_posix(), os.path.join(applicationBundle.resourcesPath, file.name))
-
-# ------------------------------------------------
-
-print("+ Installing qt.conf +")
-
-qt_conf="""[Paths]
-Translations=Resources
-Plugins=PlugIns
-"""
-
-with open(os.path.join(applicationBundle.resourcesPath, "qt.conf"), "wb") as f:
-    f.write(qt_conf.encode())
-
-# ------------------------------------------------
-
-if platform.system() == "Darwin":
-    subprocess.check_call(f"codesign --deep --force --sign - {target}", shell=True)
-
-# ------------------------------------------------
-
-print("+ Generating symlink for /Applications +")
-
-os.symlink("/Applications", os.path.join('dist', "Applications"))
-
-# ------------------------------------------------
-
-if config.zip is not None:
-    shutil.make_archive('{}'.format(appname), format='zip', root_dir='dist', base_dir='Feather.app')
-
-# ------------------------------------------------
-
-print("+ Done +")
-
-sys.exit(0)