Files
qelectrotech-source-mirror/misc/MacQetDeploy_arm64_cmake.sh
T
Laurent Trinques 4cc8d62c5f macOS: bundle the offscreen Qt platform plugin in the DMG
macdeployqt only deploys libqcocoa, so running QElectroTech with
QT_QPA_PLATFORM=offscreen aborts on the signed macOS bundle. This
prevents headless use (e.g. qet_mcp.py) without opening a Cocoa app.

Deploy platforms/libqoffscreen.dylib with macdeployqt -extra-plugins.
If it is not deployed, copy it from the Homebrew Qt and point its Qt
framework references to the bundle. The build stops if the plugin is
still missing. The existing signing loops already sign it with the
other plugins.
2026-10-05 16:10:36 +02:00

642 lines
24 KiB
Bash

#!/bin/sh
# Copyright 2026 The QElectroTech Team
# This file is part of QElectroTech.
# QElectroTech 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 2 of the License, or
# (at your option) any later version.
# Suppose que l'environnement a ete prepare via macos_homebrew_setup.sh
# (Homebrew, Qt6, cmake, ninja, kf6-kwidgetsaddons, kf6-kcoreaddons).
#
# Remplace l'ancien misc/MacQetDeploy_arm64.sh (qmake/Qt5) par un
# build CMake/Qt6/KF6. La chaine de signature/notarization/DMG est
# reprise a l'identique de l'ancien script (eprouvee, ne pas y toucher
# sans raison).
# configuration
APPNAME='qelectrotech'
BUNDLE=$APPNAME.app
IDENTITY="Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)"
QT_MAJOR="${QT_MAJOR:-6}"
BUILD_WITH_KF="${BUILD_WITH_KF:-ON}"
BUILD_DIR="build-macos-arm64"
# Temp paths
RW_DMG="/tmp/qet_rw.dmg"
MOUNT_POINT="/tmp/qet_dmg_mount"
STAGING="/tmp/qet_dmg_staging"
# Script location
current_dir=$(dirname "$0")
cd "${current_dir}/../"
current_dir=$(PWD)
### get system configuration ########################################
echo
echo "______________________________________________________________"
echo "This script prepares a Qt6/KF6 application bundle for deployment."
echo "This script :"
echo "\t - update the git depot"
echo "\t - configure and build via CMake,"
echo "\t - copy over required Qt frameworks,"
echo "\t - copy additional files: translations, titleblocks and elements,"
echo "\t - notarize the .app, then create a signed DMG."
echo
QT_PREFIX=$(brew --prefix qt 2>/dev/null || true)
if [ -z "$QT_PREFIX" ] || [ ! -d "$QT_PREFIX/lib/QtCore.framework" ] ; then
echo "ERROR: cannot find Qt6 via Homebrew. Run macos_homebrew_setup.sh first."
exit 1
fi
export CMAKE_PREFIX_PATH="$QT_PREFIX:$CMAKE_PREFIX_PATH"
### GIT ####################################################
echo
echo "______________________________________________________________"
echo "Run GIT:"
git submodule init
git submodule update
git pull --recurse-submodules
git pull
GITCOMMIT=$(git rev-parse --short HEAD)
A=$(git rev-list HEAD --count)
HEAD=$(($A+473))
VERSION=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
DMG_NAME="${APPNAME}-$VERSION-r$HEAD-arm64.dmg"
DMG_PATH="build-aux/mac-osx/$DMG_NAME"
if [ -e "$DMG_PATH" ] ; then
echo "There are not new updates, make disk image can"
echo "take a lot of time (5 min). Can you continu?"
echo "[y/n]"
read userinput
if [ "$userinput" == "n" ] ; then
echo
echo "Process is stopped."
echo
exit
fi
fi
### build with CMake #################################################
echo
echo "______________________________________________________________"
echo "Run CMake configure + build (Qt${QT_MAJOR}, BUILD_WITH_KF=${BUILD_WITH_KF}):"
if [ -d $BUNDLE ] ; then
echo "Removing old bundle..."
rm -rf $BUNDLE
fi
if [ -d "$BUILD_DIR" ] ; then
echo "Removing old build directory..."
rm -rf "$BUILD_DIR"
fi
cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \
-DQT_VERSION_MAJOR=$QT_MAJOR \
-DBUILD_WITH_KF=$BUILD_WITH_KF \
-DBUILD_KF=OFF \
-DQET_EXPORT_PROJECT_DB=ON \
-DPACKAGE_TESTS=OFF \
-DQET_ENABLE_SPACEMOUSE=ON \
-DQET_SPACEMOUSE_BACKEND=hid
if [ $? -ne 0 ]; then
echo "ERROR: cmake configure failed."
exit 1
fi
START_TIME=$SECONDS
coeur=$(sysctl hw.ncpu | awk '{print $2}')
cmake --build "$BUILD_DIR" -j$(($coeur + 1))
if [ $? -ne 0 ]; then
ELAPSED_TIME=$(($SECONDS - $START_TIME))
echo
echo "cmake build failed - $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
exit 1
fi
ELAPSED_TIME=$(($SECONDS - $START_TIME))
echo
echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec"
# Le .app sort a la racine de $BUILD_DIR : add_executable(... MACOSX_BUNDLE)
# sans RUNTIME_OUTPUT_DIRECTORY dans CMakeLists.txt.
echo "Copying built bundle into place..."
cp -R "$BUILD_DIR/qelectrotech.app" "./$BUNDLE"
if [ ! -d "$BUNDLE" ] ; then
echo "ERROR: expected bundle \"$BUNDLE\" not found after cmake build."
exit 1
fi
### copy over frameworks ############################################
echo
echo "______________________________________________________________"
echo "Copy Qt libraries and private frameworks:"
if [ ! -d $BUNDLE ] ; then
echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory"
exit 1
fi
macdeployqt $BUNDLE -extra-plugins=platforms/libqoffscreen.dylib
### offscreen platform plugin (headless use, e.g. qet_mcp.py) ########
# macdeployqt only deploys libqcocoa. Without libqoffscreen, running
# QT_QPA_PLATFORM=offscreen aborts. If -extra-plugins above did not
# deploy it, copy it from the Homebrew Qt and make it point to the
# frameworks of the bundle. It is signed with the other plugins below.
OFFSCREEN="$BUNDLE/Contents/PlugIns/platforms/libqoffscreen.dylib"
if [ ! -f "$OFFSCREEN" ] ; then
echo "libqoffscreen.dylib not deployed by macdeployqt, copying it:"
SRC_OFFSCREEN=""
for d in "$(brew --prefix qt 2>/dev/null)/share/qt/plugins" \
"$(brew --prefix qt 2>/dev/null)/plugins" \
/opt/homebrew/share/qt/plugins ; do
if [ -f "$d/platforms/libqoffscreen.dylib" ] ; then
SRC_OFFSCREEN="$d/platforms/libqoffscreen.dylib"
break
fi
done
if [ -z "$SRC_OFFSCREEN" ] ; then
echo "ERROR: libqoffscreen.dylib not found in the Qt plugins"
exit 1
fi
mkdir -p "$BUNDLE/Contents/PlugIns/platforms"
cp -L "$SRC_OFFSCREEN" "$OFFSCREEN"
chmod u+w "$OFFSCREEN"
# Qt frameworks: /opt/homebrew/.../QtGui.framework/Versions/A/QtGui
# -> inside the bundle
otool -L "$OFFSCREEN" | awk 'NR>1 { print $1 }' \
| grep -E '/Qt[A-Za-z0-9]+\.framework/' | while read ref; do
fwname=$(echo "$ref" | sed -E 's#.*/(Qt[A-Za-z0-9]+)\.framework/.*#\1#')
install_name_tool -change "$ref" \
"@executable_path/../Frameworks/$fwname.framework/Versions/A/$fwname" \
"$OFFSCREEN"
echo " Fixed ref: $fwname"
done
fi
if [ ! -f "$OFFSCREEN" ] ; then
echo "ERROR: libqoffscreen.dylib missing from the bundle"
exit 1
fi
### fix Homebrew dependencies macdeployqt could not handle ##########
# Recent Homebrew bottles (brotli, webp, sharpyuv...) reference their own
# dependencies as @rpath/libX.dylib. macdeployqt only resolves @rpath in
# Contents/lib and in Qt's lib dir (-libpath does not help): it prints
# "Cannot resolve rpath" and leaves some references untouched, either
# absolute /opt/homebrew paths or @rpath libs missing from the bundle.
# Fix both here: every /opt/homebrew reference is rewritten to
# @rpath/libX.dylib (install ids too), and every @rpath/libX.dylib is
# copied into Contents/Frameworks, where the executable's LC_RPATH
# (@executable_path/../Frameworks) lets dyld find it. Everything is signed
# below with the rest of Frameworks/.
echo
echo "______________________________________________________________"
echo "Fix Homebrew dependencies left by macdeployqt:"
FW="$BUNDLE/Contents/Frameworks"
chmod -R u+w "$BUNDLE/Contents/MacOS" "$FW" "$BUNDLE/Contents/PlugIns" 2>/dev/null
# Every Mach-O candidate of the bundle (otool silently ignores the others)
list_macho() {
find "$BUNDLE/Contents/MacOS" "$FW" "$BUNDLE/Contents/PlugIns" -type f \
\( -name '*.dylib' -o -perm -u+x \) 2>/dev/null
}
# Dependencies of one binary, without its own install id (dylibs only)
list_deps() {
_id=$(otool -D "$1" 2>/dev/null | sed -n 2p)
otool -L "$1" 2>/dev/null | awk 'NR>1 { print $1 }' | while read _dep; do
[ "$_dep" = "$_id" ] || echo "$_dep"
done
}
# The @rpath/libX.dylib names referenced anywhere in the bundle
list_rpath_libs() {
list_macho | while read bin; do
list_deps "$bin" | sed -n 's#^@rpath/\([^/]*\.dylib\)$#\1#p'
done | LC_ALL=C sort -u
}
if ! otool -l "$BUNDLE/Contents/MacOS/$APPNAME" | grep -q "@executable_path/../Frameworks" ; then
install_name_tool -add_rpath "@executable_path/../Frameworks" "$BUNDLE/Contents/MacOS/$APPNAME"
echo " Added LC_RPATH @executable_path/../Frameworks to $APPNAME"
fi
# 3 passes, since each copied library can bring its own dependencies:
# a. rewrite absolute /opt/homebrew references (install ids included),
# copying the referenced library into Frameworks/ if needed
# b. copy the @rpath/libX.dylib still missing from Frameworks/
for PASS in 1 2 3; do
list_macho | while read bin; do
_id=$(otool -D "$bin" 2>/dev/null | sed -n 2p)
case "$_id" in
/opt/homebrew/*)
install_name_tool -id "@rpath/$(basename "$_id")" "$bin" 2>/dev/null
echo " Fixed id (pass $PASS): $(basename "$bin")"
;;
esac
list_deps "$bin" | grep '^/opt/homebrew/' | while read ref; do
name=$(basename "$ref")
if [ ! -e "$FW/$name" ]; then
cp -L "$ref" "$FW/$name" && chmod u+w "$FW/$name"
echo " Copied (pass $PASS): $name"
fi
install_name_tool -change "$ref" "@rpath/$name" "$bin" 2>/dev/null
echo " Fixed ref (pass $PASS): $(basename "$bin") -> @rpath/$name"
done
done
list_rpath_libs | while read lib; do
if [ ! -e "$FW/$lib" ] && [ -e "/opt/homebrew/lib/$lib" ]; then
cp -L "/opt/homebrew/lib/$lib" "$FW/$lib" && chmod u+w "$FW/$lib"
echo " Copied (pass $PASS): $lib"
fi
done
done
# 3. Checks
UNRESOLVED=$(list_rpath_libs | while read lib; do [ -e "$FW/$lib" ] || echo "$lib"; done)
if [ -n "$UNRESOLVED" ]; then
echo "ERROR: @rpath libraries still missing from Frameworks/:" $UNRESOLVED
exit 1
fi
HOMEBREW_REFS=$(list_macho | while read bin; do
otool -L "$bin" 2>/dev/null | awk 'NR>1 { print $1 }' | grep '^/opt/homebrew/' \
| sed "s#^# $(basename "$bin") -> #"
done)
if [ -n "$HOMEBREW_REFS" ]; then
echo "ERROR: bundle still references Homebrew paths:"
echo "$HOMEBREW_REFS"
exit 1
fi
echo "All dependencies resolved inside the bundle."
### install Info.plist and app icon #################################
# NOTE: this must run AFTER macdeployqt, not before. macdeployqt
# rewrites/regenerates parts of Contents/Resources, and files copied
# there beforehand (e.g. the .icns icons) do not reliably survive it
# and end up missing from the final bundle, causing the app to show
# the generic placeholder icon instead of the real one. Info.plist
# itself lives directly in Contents/ and happens to survive either
# way, but keep it here too so this whole "final metadata" step stays
# in one place, after macdeployqt is done touching the bundle.
echo
echo "______________________________________________________________"
echo "Install Info.plist and app icon:"
cp -R ${current_dir}/misc/Info.plist $BUNDLE/Contents/
cp -R ${current_dir}/ico/mac_icon/*.icns $BUNDLE/Contents/Resources/
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "$BUNDLE/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :LSMinimumSystemVersion 14.0.0" "$BUNDLE/Contents/Info.plist"
### add missing files ###############################################
echo
echo "______________________________________________________________"
echo "Copy missing files:"
QET_ELMT_DIR="${current_dir}/elements/"
QET_TBT_DIR="${current_dir}/titleblocks/"
QET_LANG_DIR="${current_dir}/lang/"
QET_EXAMPLES_DIR="${current_dir}/examples/"
QET_FONTS_DIR="${current_dir}/fonts/"
QET_LICENSES_DIR="${current_dir}/licenses/"
if [ -d "${QET_ELMT_DIR}" ]; then
cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements
fi
if [ -d "${QET_TBT_DIR}" ]; then
cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks
fi
# Traductions : depuis la PR #751, les .qm ne sont plus versionnes ; lrelease
# les genere dans $BUILD_DIR/lang/. Jeu attendu = les .ts listes dans TS_FILES
# (cmake/qet_compilation_vars.cmake) : un .qm manquant arrete le script, un
# .ts present dans lang/ mais absent de TS_FILES donne seulement un WARNING.
# Fichiers temporaires plutot que <(...) : /bin/sh de macOS (bash 3.2 en mode
# POSIX) n'a pas la substitution de processus.
QM_SRC="${current_dir}/${BUILD_DIR}/lang"
QM_TMP=$(mktemp -d /tmp/qet_qm.XXXXXX)
grep -o 'lang/qet_[A-Za-z_]*\.ts' "${current_dir}/cmake/qet_compilation_vars.cmake" \
| sed -e 's#^lang/##' -e 's#\.ts$##' | LC_ALL=C sort -u > "$QM_TMP/listed"
if [ ! -s "$QM_TMP/listed" ]; then
echo "ERROR: cannot read TS_FILES from cmake/qet_compilation_vars.cmake"
rm -rf "$QM_TMP"
exit 1
fi
find "${QET_LANG_DIR}" -maxdepth 1 -name 'qet_*.ts' -exec basename {} .ts \; | LC_ALL=C sort > "$QM_TMP/present"
UNLISTED=$(LC_ALL=C comm -13 "$QM_TMP/listed" "$QM_TMP/present")
if [ -n "$UNLISTED" ]; then
echo "WARNING: .ts files not in TS_FILES, no .qm built:" $UNLISTED
fi
mkdir -p $BUNDLE/Contents/Resources/lang
find "${QM_SRC}" -maxdepth 1 -name 'qet_*.qm' -exec cp {} $BUNDLE/Contents/Resources/lang/ \; 2>/dev/null
find $BUNDLE/Contents/Resources/lang -maxdepth 1 -name 'qet_*.qm' -exec basename {} .qm \; | LC_ALL=C sort > "$QM_TMP/built"
MISSING=$(LC_ALL=C comm -23 "$QM_TMP/listed" "$QM_TMP/built")
echo "$(wc -l < "$QM_TMP/built" | tr -d ' ') .qm files copied to Contents/Resources/lang (expected: $(wc -l < "$QM_TMP/listed" | tr -d ' '))"
# Traductions de Qt lui-meme (boutons OK/Annuler, dialogues standard...) :
# elles viennent de qtbase_XX.qm, pas des .ts de QET, et macdeployqt ne les
# deploie pas. QETApp::setLanguage() charge "qt_XX" depuis le chemin de
# traductions de Qt (absent du bundle), puis depuis le dossier lang/ de QET :
# on y depose donc chaque qtbase_XX.qm sous le nom qt_XX.qm. qtbase_XX.qm est
# autonome, contrairement aux qt_XX.qm de Qt qui dependent de tous les modules.
# Premier dossier contenant des qtbase_*.qm : celui annonce par qtpaths, puis
# la formule Homebrew separee qttranslations, puis les autres emplacements
# Homebrew possibles. Homebrew fait de ces dossiers des liens symboliques
# (-> Cellar/qttranslations/...) : find doit donc les suivre (-L), sinon il
# ne voit que le lien lui-meme et ne trouve aucun fichier dedans.
QT_TR_DIR=""
for d in "$("$QT_PREFIX/bin/qtpaths" --query QT_INSTALL_TRANSLATIONS 2>/dev/null)" \
"$(brew --prefix qttranslations 2>/dev/null)/share/qt/translations" \
"$QT_PREFIX/share/qt/translations" \
/opt/homebrew/share/qt/translations \
/opt/homebrew/opt/*/share/qt/translations ; do
if ls "$d"/qtbase_*.qm >/dev/null 2>&1 ; then
QT_TR_DIR="$d"
break
fi
done
LANG_DST="$BUNDLE/Contents/Resources/lang"
find -L "$QT_TR_DIR" -maxdepth 1 -name 'qtbase_*.qm' 2>/dev/null | while read f; do
l=$(basename "$f" .qm | sed 's/^qtbase_//')
cp "$f" "$LANG_DST/qt_$l.qm"
done
# Langues QET sans equivalent Qt exact (pt -> pt_PT, zh -> zh_CN...) :
# QTranslator ne sait que raccourcir le code (fr_FR -> fr), pas l'allonger.
# On prefere la variante "principale" (pt_PT), sinon la premiere trouvee.
sed 's/^qet_//' "$QM_TMP/listed" | while read l; do
if [ ! -e "$LANG_DST/qt_$l.qm" ]; then
main="$QT_TR_DIR/qtbase_${l}_$(echo "$l" | tr 'a-z' 'A-Z').qm"
if [ -e "$main" ]; then
alt="$main"
else
alt=$(find -L "$QT_TR_DIR" -maxdepth 1 -name "qtbase_${l}_*.qm" 2>/dev/null | LC_ALL=C sort | head -1)
fi
[ -n "$alt" ] && cp "$alt" "$LANG_DST/qt_$l.qm"
fi
done
QT_QM_COUNT=$(find "$LANG_DST" -maxdepth 1 -name 'qt_*.qm' | wc -l | tr -d ' ')
echo "${QT_QM_COUNT} Qt translation files (qt_*.qm) copied from ${QT_TR_DIR:-<not found>}"
if [ "${QT_QM_COUNT}" -eq 0 ]; then
echo "ERROR: no qtbase_*.qm found (Qt translations not installed?)."
echo " Check with: find /opt/homebrew -name 'qtbase_fr.qm'"
rm -rf "$QM_TMP"
exit 1
fi
rm -rf "$QM_TMP"
if [ -n "$MISSING" ]; then
echo "ERROR: missing translations:" $MISSING
exit 1
fi
if [ -d "${QET_EXAMPLES_DIR}" ]; then
mkdir $BUNDLE/Contents/Resources/examples
cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples
fi
if [ -d "${QET_FONTS_DIR}" ]; then
mkdir $BUNDLE/Contents/Resources/fonts
cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts
fi
if [ -d "${QET_LICENSES_DIR}" ]; then
cp -R -L ${QET_LICENSES_DIR} $BUNDLE/Contents/Resources/licenses
fi
### Sign the bundle #################################################
# Sign in correct order: all dylibs first (including flat libs copied
# by macdeployqt from Homebrew), then frameworks, plugins, bundle last.
echo
echo "______________________________________________________________"
echo "Code signing (dylibs -> frameworks -> plugins -> bundle):"
echo "-- Signing dylibs in Frameworks/..."
find "$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
echo "-- Signing .framework bundles..."
find "$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
done
echo "-- Signing plugins..."
find "$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
echo "-- Signing dylibs in MacOS/..."
find "$BUNDLE/Contents/MacOS" -name "*.dylib" | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
echo "-- Signing main executable..."
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
"$BUNDLE/Contents/MacOS/$APPNAME"
echo "-- Signing bundle..."
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
--entitlements "${current_dir}/misc/qelectrotech.entitlements" "$BUNDLE"
echo
echo "Verifying bundle signature..."
codesign --verify --deep --strict --verbose=2 "$BUNDLE"
if [ $? -ne 0 ]; then
echo "ERROR: bundle signature verification failed, aborting."
exit 1
fi
spctl -a -vv "$BUNDLE"
echo "Bundle signature OK."
### Notarize the .app (via temporary ZIP) ###########################
echo
echo "______________________________________________________________"
echo "Create temporary ZIP for notarization:"
NOTARIZE_ZIP="/tmp/${APPNAME}-$VERSION-r$HEAD-arm64-notarize.zip"
/usr/bin/ditto -c -k --keepParent "$BUNDLE" "$NOTARIZE_ZIP"
echo -e "\033[1;31mWould you like to notarize the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
echo
echo "______________________________________________________________"
echo "Notarizing .app:"
xcrun notarytool submit "$NOTARIZE_ZIP" --keychain-profile "org.qelectrotech" --wait
if [ $? -ne 0 ]; then
echo "ERROR: notarization failed. Check the log with:"
echo " xcrun notarytool log <submission-id> --keychain-profile org.qelectrotech"
rm -f "$NOTARIZE_ZIP"
exit 1
fi
else
echo -e "\033[1;33mExit.\033[m"
fi
rm -f "$NOTARIZE_ZIP"
### Staple the .app #################################################
echo -e "\033[1;31mWould you like to staple the .app \"${APPNAME}-${VERSION}-r${HEAD}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
xcrun stapler staple -v "$BUNDLE"
if [ $? -ne 0 ]; then
echo "ERROR: stapling .app failed."
exit 1
fi
xcrun stapler validate -v "$BUNDLE"
spctl -a -vv "$BUNDLE"
echo ".app stapled OK."
else
echo -e "\033[1;33mExit.\033[m"
fi
### Create staging folder with Applications symlink #################
echo
echo "______________________________________________________________"
echo "Preparing DMG staging folder:"
rm -rf "$STAGING"
mkdir -p "$STAGING"
cp -R "$BUNDLE" "$STAGING/"
ln -s /Applications "$STAGING/Applications"
### Create writable DMG (UDRW) ######################################
echo
echo "______________________________________________________________"
echo "Create writable DMG (UDRW) and re-sign .app inside:"
rm -f "$RW_DMG"
hdiutil create \
-volname "QElectroTech $VERSION" \
-srcfolder "$STAGING" \
-ov \
-format UDRW \
-fs HFS+ \
"$RW_DMG"
if [ $? -ne 0 ]; then
echo "ERROR: hdiutil failed to create writable DMG."
rm -rf "$STAGING"
exit 1
fi
rm -rf "$MOUNT_POINT"
mkdir -p "$MOUNT_POINT"
hdiutil attach "$RW_DMG" -mountpoint "$MOUNT_POINT" -nobrowse -noverify
if [ $? -ne 0 ]; then
echo "ERROR: failed to mount writable DMG."
rm -f "$RW_DMG"
rm -rf "$STAGING"
exit 1
fi
echo "-- Re-signing dylibs inside DMG..."
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
find "$MOUNT_POINT/$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw"
done
find "$MOUNT_POINT/$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do
codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib"
done
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
"$MOUNT_POINT/$BUNDLE/Contents/MacOS/$APPNAME"
codesign --force --sign "$IDENTITY" --timestamp --options=runtime \
--entitlements "${current_dir}/misc/qelectrotech.entitlements" \
"$MOUNT_POINT/$BUNDLE"
echo "Verifying bundle signature inside DMG..."
codesign --verify --deep --strict --verbose=2 "$MOUNT_POINT/$BUNDLE"
if [ $? -ne 0 ]; then
echo "ERROR: bundle signature invalid inside DMG, aborting."
hdiutil detach "$MOUNT_POINT"
rm -f "$RW_DMG"
rm -rf "$STAGING" "$MOUNT_POINT"
exit 1
fi
echo "Bundle signature inside DMG OK."
hdiutil detach "$MOUNT_POINT"
### Convert UDRW to final compressed UDZO ###########################
echo
echo "______________________________________________________________"
echo "Convert to final compressed DMG (UDZO):"
mkdir -p "build-aux/mac-osx"
rm -f "$DMG_PATH"
hdiutil convert "$RW_DMG" \
-format UDZO \
-o "$DMG_PATH"
if [ $? -ne 0 ]; then
echo "ERROR: hdiutil convert failed."
rm -f "$RW_DMG"
rm -rf "$STAGING" "$MOUNT_POINT"
exit 1
fi
rm -f "$RW_DMG"
rm -rf "$STAGING" "$MOUNT_POINT"
### Sign the final DMG ##############################################
echo "Signing final DMG..."
codesign --sign "$IDENTITY" --timestamp "$DMG_PATH"
### Notarize and staple the final DMG ###############################
echo -e "\033[1;31mWould you like to notarize the DMG \"${DMG_NAME}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
echo
echo "______________________________________________________________"
echo "Notarizing DMG:"
xcrun notarytool submit "$DMG_PATH" --keychain-profile "org.qelectrotech" --wait
if [ $? -ne 0 ]; then
echo "ERROR: DMG notarization failed. Check the log with:"
echo " xcrun notarytool log <submission-id> --keychain-profile org.qelectrotech"
exit 1
fi
echo "Stapling DMG..."
xcrun stapler staple "$DMG_PATH"
if [ $? -ne 0 ]; then
echo "ERROR: stapling DMG failed."
exit 1
fi
echo "DMG notarized and stapled OK."
spctl -a -vv "$DMG_PATH"
else
echo -e "\033[1;33mExit.\033[m"
fi
### Clean up bundle #################################################
rm -rf "$BUNDLE"
echo
echo "______________________________________________________________"
echo "The process is done."
echo "DMG is in the folder 'build-aux/mac-osx'."
### Upload via rsync ################################################
echo -e "\033[1;31mWould you like to upload MacOS package \"${DMG_NAME}\", n/Y?\033[m"
read a
if [[ $a == "Y" || $a == "y" ]]; then
cp -Rf "$DMG_PATH" /Users/laurent/MAC_OS_X/
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
--progress --exclude='.DS_Store' \
/Users/laurent/MAC_OS_X/ \
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
if [ $? != 0 ]; then
echo "RSYNC ERROR: problem syncing ${DMG_NAME}, retrying..."
rsync -e ssh -av --delete-after --no-owner --no-g --chmod=g+w \
--progress --exclude='.DS_Store' \
/Users/laurent/MAC_OS_X/ \
server:download.qelectrotech.org/qet/builds/MAC_OS_X/arm64/
fi
else
echo -e "\033[1;33mExit.\033[m"
fi