#!/bin/sh # Copyright 2023 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. # QElectroTech 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 QElectroTech. If not, see . # Need homebrew and coreutils installed see . #Force MacOSX12.3.sdk #see: https://www.downtowndougbrown.com/2023/08/how-to-create-a-qt-5-arm-intel-universal-binary-for-mac/ export DEVELOPER_DIR=/Applications/Xcode_14.01.app/Contents/Developer # configuration APPNAME='qelectrotech' BUNDLE=$APPNAME.app APPBIN="$BUNDLE/Contents/MacOS/$APPNAME" IDENTITY="Developer ID Application: Laurent TRINQUES (Y73WZ6WZ5X)" # Script location current_dir=$(dirname "$0") # Go back to repo root cd "${current_dir}/../" # Current directory current_dir=$(PWD) ### get system configuration ######################################## echo echo "______________________________________________________________" echo "This script prepares a Qt application bundle for deployment." echo "Please see the \"Deploying an Application on Qt/Mac\"" echo "page in the Qt documentation for more information." echo echo "This script :" echo "\t - update the git depot" echo "\t - build the application bundle," 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 echo "Enjoy ;-)" echo # as long as we can find qmake, we don't need QTDIR FWPATH=`qmake -query QT_INSTALL_LIBS` if [ ! -d $FWPATH/QtGui.framework ] ; then echo "ERROR: cannot find the Qt frameworks. Make sure Qt is installed" echo "and qmake is in your environment path." exit fi ### GIT #################################################### echo echo "______________________________________________________________" echo "Run GIT:" git submodule init git submodule update git pull --recurse-submodules git pull # Get revision number and version 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" # Check if already built 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 ### make install #################################################### echo echo "______________________________________________________________" echo "Run make install:" # Remove old bundle if [ -d $BUNDLE ] ; then echo "Removing old bundle..." rm -rf $BUNDLE fi if [ -e Makefile ] ; then echo "Removing old Makefile..." rm .qmake.stash make clean fi # Generate Makefile echo "Generating new makefile..." qmake -spec macx-clang # Compile if [ -e Makefile.Release ] ; then START_TIME=$SECONDS testSuccessBuild () { if [ $? -ne 0 ]; then cleanVerionTag ELAPSED_TIME=$(($SECONDS - $START_TIME)) echo echo "make failed - $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec" exit 1 fi } coeur=$(sysctl hw.ncpu | awk '{print $2}') if [ $? -ne 0 ]; then make -f Makefile.Release testSuccessBuild else make -j$(($coeur + 1)) -f Makefile.Release testSuccessBuild fi ELAPSED_TIME=$(($SECONDS - $START_TIME)) echo echo "The time of compilation is $(($ELAPSED_TIME/60)) min $(($ELAPSED_TIME%60)) sec" else echo "ERROR: Makefile not found. This script requires the macx-clang makespec" exit fi cp -R ${current_dir}/misc/Info.plist qelectrotech.app/Contents/ cp -R ${current_dir}/ico/mac_icon/*.icns qelectrotech.app/Contents/Resources/ /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $VERSION r$HEAD" "qelectrotech.app/Contents/Info.plist" ### copy over frameworks ############################################ echo echo "______________________________________________________________" echo "Copy Qt libraries and private frameworks:" echo "Processing Mac deployment tool..." if [ ! -d $BUNDLE ] ; then echo "ERROR: cannot find application bundle \"$BUNDLE\" in current directory" exit fi macdeployqt $BUNDLE ### 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/" LANG_DIR="${current_dir}/lang1/" if [ -d "${QET_ELMT_DIR}" ]; then echo "Copying elements in the bundle..." cp -R ${QET_ELMT_DIR} $BUNDLE/Contents/Resources/elements fi if [ -d "${QET_TBT_DIR}" ]; then echo "Copying titleblocks in the bundle..." cp -R ${QET_TBT_DIR} $BUNDLE/Contents/Resources/titleblocks fi if [ -d "${QET_LANG_DIR}" ]; then echo "Copying translations in the bundle..." mkdir $BUNDLE/Contents/Resources/lang cp ${current_dir}/lang/*.qm $BUNDLE/Contents/Resources/lang fi if [ -d "${LANG_DIR}" ]; then echo "Copying extra translations in the bundle..." cp ${current_dir}/lang1/*.qm $BUNDLE/Contents/Resources/lang fi if [ -d "${QET_EXAMPLES_DIR}" ]; then echo "Copying examples in the bundle..." mkdir $BUNDLE/Contents/Resources/examples cp ${current_dir}/examples/*.qet $BUNDLE/Contents/Resources/examples fi if [ -d "${QET_FONTS_DIR}" ]; then echo "Copying fonts in the bundle..." mkdir $BUNDLE/Contents/Resources/fonts cp ${current_dir}/fonts/*.ttf $BUNDLE/Contents/Resources/fonts fi if [ -d "${QET_LICENSES_DIR}" ]; then echo "Copying licenses in the bundle..." mkdir $BUNDLE/Contents/Resources/licenses 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. # --deep is deprecated and misses flat dylibs in Frameworks/. echo echo "______________________________________________________________" echo "Code signing (dylibs → frameworks → plugins → bundle):" # 1. Sign all flat .dylib files in Frameworks/ echo "-- Signing dylibs in Frameworks/..." find "$BUNDLE/Contents/Frameworks" -name "*.dylib" | while read lib; do echo " $(basename $lib)" codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib" done # 2. Sign .framework bundles echo "-- Signing .framework bundles..." find "$BUNDLE/Contents/Frameworks" -maxdepth 1 -name "*.framework" | while read fw; do echo " $(basename $fw)" codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$fw" done # 3. Sign plugins echo "-- Signing plugins..." find "$BUNDLE/Contents/PlugIns" \( -name "*.dylib" -o -name "*.so" \) | while read lib; do echo " $(basename $lib)" codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib" done # 4. Sign any dylibs in MacOS/ echo "-- Signing dylibs in MacOS/..." find "$BUNDLE/Contents/MacOS" -name "*.dylib" | while read lib; do echo " $(basename $lib)" codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$lib" done # 5. Sign the main executable explicitly echo "-- Signing main executable..." codesign --force --sign "$IDENTITY" --timestamp --options=runtime \ "$BUNDLE/Contents/MacOS/$APPNAME" # 6. Sign the bundle itself last echo "-- Signing bundle..." codesign --force --sign "$IDENTITY" --timestamp --options=runtime "$BUNDLE" # 7. Verify 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) ########################### # Strategy: # 1. Submit the .app as a ZIP to Apple notarytool → get ticket # 2. Staple the ticket onto the .app # 3. Create the DMG from the stapled .app # 4. Sign the DMG # 5. Staple the DMG directly (no re-submission needed: Apple # recognises the ticket already registered for the .app bundle) # # This avoids submitting the DMG to notarytool, which would fail # because hdiutil copies the .app and can invalidate its Sealed # Resources signature during DMG creation. 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 --keychain-profile org.qelectrotech" rm -f "$NOTARIZE_ZIP" exit 1 fi else echo -e "\033[1;33mExit.\033[m" fi echo "Cleaning up temporary notarization ZIP..." 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 echo "Verifying staple on .app..." xcrun stapler validate -v "$BUNDLE" spctl -a -vv "$BUNDLE" echo ".app stapled OK." else echo -e "\033[1;33mExit.\033[m" fi ### Create DMG from the stapled .app ################################ # The .app is already notarized and stapled at this point. # We create the DMG directly from it — no need to re-submit to # notarytool. Stapling the DMG directly retrieves the already- # registered ticket from Apple's CDN. echo echo "______________________________________________________________" echo "Create DMG from stapled .app:" mkdir -p "build-aux/mac-osx" hdiutil create \ -volname "QElectroTech $VERSION" \ -srcfolder "$BUNDLE" \ -ov \ -format UDZO \ -fs HFS+ \ "$DMG_PATH" if [ $? -ne 0 ]; then echo "ERROR: hdiutil failed to create DMG." exit 1 fi # Sign the DMG echo "Signing DMG..." codesign \ --sign "$IDENTITY" \ --timestamp \ "$DMG_PATH" # Staple the DMG — retrieves the ticket already registered for the # .app bundle, no new notarytool submission required echo "Stapling DMG..." xcrun stapler staple "$DMG_PATH" if [ $? -ne 0 ]; then echo "WARNING: stapling DMG failed. The DMG is still usable but" echo "will require an internet connection for Gatekeeper validation." fi echo "DMG ready: $DMG_PATH" ### Clean up bundle ################################################# echo "Cleaning up bundle..." rm -rf "$BUNDLE" ### The end ######################################################### 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