From 944681ed7b9938d5ccf9be4c4aea16df6cacb551 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 17:35:06 +0200 Subject: [PATCH 01/12] Add macOS CI build and refresh the macOS qmake configuration The macx block had not been touched since the Qt5 era and no longer matched the file it lives in: - pkg-config names were opencv (OpenCV 3) and Qt5Qwt6, but DFTFringe.pro is now the Qt6 project file - zlib was missing from LIBS, which cnpy.cpp needs since npz support landed - QMAKE_MKDIR and QMAKE_PKG_CONFIG hard coded conflicting Homebrew prefixes, one Intel and one Apple silicon - QMAKE_FULL_VERSION was set to the literal string APP_VERSION - INCLUDEPATH had an -I prefix and pointed at the plugins directory The new build-macos workflow builds one disk image per architecture on macos-15 and macos-15-intel. A universal binary would require rebuilding every Homebrew dependency for both architectures, so it is left out. The bundle is ad-hoc signed only; notarisation needs an Apple Developer ID. Refs githubdoe/DFTFringe#24, githubdoe/DFTFringe#118 Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-macos.yml | 131 ++++++++++++++++++++++++++++++ DFTFringe.pro | 38 +++------ 2 files changed, 144 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/build-macos.yml diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 00000000..7a123de1 --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,131 @@ +name: build-macos +on: + push: + branches: + - master + - macos-build + pull_request: + workflow_dispatch: + workflow_call: + +jobs: + build-macos: + strategy: + fail-fast: false + matrix: + include: + - os: macos-15 + arch: arm64 + - os: macos-15-intel + arch: x86_64 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + # opencv pulls a large dependency tree, so this is the slowest step by far. + - name: Install dependencies + run: brew install qt qwt opencv armadillo + + # Homebrew lives in /usr/local on Intel and /opt/homebrew on Apple silicon, + # and Qt6 is split across several kegs, so everything is resolved at runtime + # rather than hard coded. + - name: Resolve Homebrew layout + run: | + QT_PREFIX="$(brew --prefix qt)" + if [ ! -x "$QT_PREFIX/bin/qmake" ]; then QT_PREFIX="$(brew --prefix qtbase)"; fi + echo "QT_PREFIX=$QT_PREFIX" >> "$GITHUB_ENV" + echo "PKG_CONFIG_PATH=$(brew --prefix qwt)/lib/pkgconfig:$(brew --prefix opencv)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" >> "$GITHUB_ENV" + + # Prints everything needed to diagnose a failed configure step without + # having to guess at the Homebrew layout from the outside. + - name: Probe toolchain + run: | + echo "--- prefixes" + for f in qt qtbase qwt opencv armadillo; do + echo "$f -> $(brew --prefix $f 2>/dev/null || echo 'NOT INSTALLED')" + done + echo "--- qmake" + "$QT_PREFIX/bin/qmake" -v + "$QT_PREFIX/bin/qmake" -query + echo "--- qt modules visible to qmake" + ls "$(brew --prefix)/share/qt/mkspecs/modules" 2>/dev/null | grep -iE "charts|datavis|opengl|printsupport" || echo "(module .pri files not found)" + echo "--- pkg-config packages" + pkg-config --list-all | grep -iE "qwt|opencv|armadillo" || echo "(no matches)" + echo "--- pkg-config resolution" + for p in armadillo opencv4 Qt6Qwt6; do + echo "$p: $(pkg-config --modversion $p 2>&1)" + done + echo "--- qwt keg layout" + find "$(brew --prefix qwt)" -maxdepth 3 \( -name "*.pc" -o -name "*.framework" -o -name "libqwt*" \) 2>/dev/null || true + + # Mirrors the naming used by build-windows.yml so artifacts line up. + - name: Resolve version strings + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + WORKFLOW_VERSION="${{ github.event.pull_request.head.sha }}_${{ github.event.pull_request.base.sha }}" + elif [ "${{ startsWith(github.ref, 'refs/tags/v') }}" = "true" ]; then + WORKFLOW_VERSION="${{ github.ref_name }}" + else + WORKFLOW_VERSION="${{ github.sha }}" + fi + # CFBundleShortVersionString only accepts a dotted numeric string, so a + # commit sha cannot go there. Tags give a real version, anything else 0.0.0. + BUNDLE_VERSION="$(printf '%s' "$WORKFLOW_VERSION" | sed -nE 's/^v?([0-9]+\.[0-9]+\.[0-9]+).*$/\1/p')" + if [ -z "$BUNDLE_VERSION" ]; then BUNDLE_VERSION="0.0.0"; fi + echo "WORKFLOW_VERSION=$WORKFLOW_VERSION" >> "$GITHUB_ENV" + echo "BUNDLE_VERSION=$BUNDLE_VERSION" >> "$GITHUB_ENV" + echo "version=$WORKFLOW_VERSION bundle=$BUNDLE_VERSION" + + - name: Find and Replace MY_AUTOMATED_VERSION_STRING + run: sed -i '' "s/MY_AUTOMATED_VERSION_STRING/${WORKFLOW_VERSION}/" DFTFringe.pro + + - name: Configure + run: | + "$QT_PREFIX/bin/qmake" DFTFringe.pro CONFIG+=release + + - uses: ammaraskar/gcc-problem-matcher@master + - run: echo "::add-matcher::.github/matcher/uic_matcher.json" + - name: Build + run: make -j$(sysctl -n hw.ncpu) + - run: echo "::remove-matcher owner=uic-problem-matcher::" + + # macdeployqt copies the Qt frameworks and the Homebrew dylibs the binary + # references into the bundle and rewrites their install names. + - name: Bundle dependencies + run: | + "$QT_PREFIX/bin/macdeployqt" build/release/DFTFringe.app -verbose=1 + # colormapviewerdlg looks for ColorMaps next to the executable. + cp -R ColorMaps build/release/DFTFringe.app/Contents/MacOS/ + /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $BUNDLE_VERSION" \ + build/release/DFTFringe.app/Contents/Info.plist + /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUNDLE_VERSION" \ + build/release/DFTFringe.app/Contents/Info.plist + + # Ad-hoc signature only. Without an Apple Developer ID the app cannot be + # notarised, so users have to clear the quarantine flag themselves. See + # "How to build DFTFringe on MacOS" in README.md. + - name: Ad-hoc sign + run: codesign --force --deep --sign - build/release/DFTFringe.app + + - name: Verify the bundle is self contained + run: | + otool -L build/release/DFTFringe.app/Contents/MacOS/DFTFringe + echo "--- references outside the bundle and the system:" + otool -L build/release/DFTFringe.app/Contents/MacOS/DFTFringe \ + | tail -n +2 | awk '{print $1}' \ + | grep -vE "^(@rpath|@executable_path|/usr/lib|/System)" || echo "(none)" + + - name: Create disk image + run: | + STAGE="$(mktemp -d)/DFTFringe" + mkdir -p "$STAGE" + cp -R build/release/DFTFringe.app "$STAGE/" + ln -s /Applications "$STAGE/Applications" + hdiutil create -volname "DFTFringe" -srcfolder "$STAGE" -ov -format UDZO \ + "DFTFringe-${WORKFLOW_VERSION}-${{ matrix.arch }}.dmg" + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: DFTFringe-macos-${{ matrix.arch }}-build-artifact + path: DFTFringe-${{ env.WORKFLOW_VERSION }}-${{ matrix.arch }}.dmg diff --git a/DFTFringe.pro b/DFTFringe.pro index e94e9cab..f5498814 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -96,11 +96,11 @@ macx { CONFIG += app_bundle CONFIG += sdk_no_version_check CONFIG += link_pkgconfig - CONFIG += silent - QMAKE_FULL_VERSION=APP_VERSION - QMAKE_MACOSX_DEPLOYMENT_TARGET = 11.0 - QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64 + # Build for the host architecture only. Homebrew ships single architecture + # libraries, so a universal binary would need every dependency rebuilt and + # lipo'd. The CI produces one disk image per architecture instead. + QMAKE_APPLE_DEVICE_ARCHS = $$QMAKE_HOST.arch CONFIG( debug, debug|release ) { DESTDIR = build/debug } CONFIG( release, debug|release ) { DESTDIR = build/release } @@ -109,32 +109,20 @@ macx { OBJECTS_DIR = $$DESTDIR/.obj #these change between build and release. RCC_DIR = $$DESTDIR/.qrc UI_DIR = $$DESTDIR/.ui - QMAKE_MKDIR = /usr/local/bin/mkdir # This tells QMAKE which mkdir command to use. - QMAKE_PKG_CONFIG = /opt/homebrew/bin/pkg-config # This tells QMAKE which pkg-config executable to use. - PKG_CONFIG_PATH = $$[QT_INSTALL_LIBS]/pkgconfig - INCLUDEPATH += -I$$[QT_INSTALL_PLUGINS] - LIBS += -L$$[QT_INSTALL_PLUGINS] - PKGCONFIG += armadillo opencv Qt5Qwt6 + + # Dependencies are resolved through pkg-config so that no Homebrew prefix is + # hard coded here: /usr/local on Intel and /opt/homebrew on Apple silicon. + # PKG_CONFIG_PATH must list the qwt, opencv and armadillo kegs. + # See "How to build DFTFringe on MacOS" in README.md. + PKGCONFIG += armadillo opencv4 Qt6Qwt6 + + LIBS += -lz # zip compression library needed for cnpy.cpp message(........QT_VERSION: $$[QT_VERSION]) message(.QT_INSTALL_PREFIX: $$[QT_INSTALL_PREFIX]) - message(QT_INSTALL_HEADERS: $$[QT_INSTALL_HEADERS]) - message(...QT_INSTALL_LIBS: $$[QT_INSTALL_LIBS]) - message(QT_INSTALL_PLUGINS: $$[QT_INSTALL_PLUGINS]) - message(...................) + message(..............ARCHS: $$QMAKE_APPLE_DEVICE_ARCHS) message(...........DESTDIR: $$DESTDIR) - message(...........MOC_DIR: $$MOC_DIR) - message(.......OBJECTS_DIR: $$OBJECTS_DIR) - message(...........RCC_DIR: $$RCC_DIR) - message(............UI_DIR: $$UI_DIR) - message(...................) - message(.......QMAKE_MKDIR: $$QMAKE_MKDIR) - message(..QMAKE_PKG_CONFIG: $$QMAKE_PKG_CONFIG) - message(...PKG_CONFIG_PATH: $$PKG_CONFIG_PATH) - message(.......INCLUDEPATH: $$INCLUDEPATH) - message(..............LIBS: $$LIBS) message(.........PKGCONFIG: $$PKGCONFIG) - message(............CONFIG: $$CONFIG) } # Below are the includes for source files and other resources, sorted alphabetically. ################################## From 5197d8a25800bc461bfc211faf57781d134714b7 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 17:40:21 +0200 Subject: [PATCH 02/12] Use opencv@4 on macOS instead of the OpenCV 5 formula Homebrew's opencv formula moved to OpenCV 5, whose pkg-config name is opencv5, so the configure step could not find opencv4. The CI now installs opencv@4 to stay on the same major version as the Linux and Windows builds. The project file falls back to opencv5 when opencv4 is absent so that a local checkout with only the current opencv formula still configures. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-macos.yml | 14 ++++++++------ DFTFringe.pro | 13 ++++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7a123de1..7a33e22e 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -23,8 +23,10 @@ jobs: - uses: actions/checkout@v4 # opencv pulls a large dependency tree, so this is the slowest step by far. + # opencv@4 rather than opencv, which is now OpenCV 5, so that all three + # platforms build against the same OpenCV major version. - name: Install dependencies - run: brew install qt qwt opencv armadillo + run: brew install qt qwt opencv@4 armadillo # Homebrew lives in /usr/local on Intel and /opt/homebrew on Apple silicon, # and Qt6 is split across several kegs, so everything is resolved at runtime @@ -34,14 +36,14 @@ jobs: QT_PREFIX="$(brew --prefix qt)" if [ ! -x "$QT_PREFIX/bin/qmake" ]; then QT_PREFIX="$(brew --prefix qtbase)"; fi echo "QT_PREFIX=$QT_PREFIX" >> "$GITHUB_ENV" - echo "PKG_CONFIG_PATH=$(brew --prefix qwt)/lib/pkgconfig:$(brew --prefix opencv)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" >> "$GITHUB_ENV" + echo "PKG_CONFIG_PATH=$(brew --prefix qwt)/lib/pkgconfig:$(brew --prefix opencv@4)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" >> "$GITHUB_ENV" # Prints everything needed to diagnose a failed configure step without # having to guess at the Homebrew layout from the outside. - name: Probe toolchain run: | echo "--- prefixes" - for f in qt qtbase qwt opencv armadillo; do + for f in qt qtbase qwt opencv@4 armadillo; do echo "$f -> $(brew --prefix $f 2>/dev/null || echo 'NOT INSTALLED')" done echo "--- qmake" @@ -52,11 +54,11 @@ jobs: echo "--- pkg-config packages" pkg-config --list-all | grep -iE "qwt|opencv|armadillo" || echo "(no matches)" echo "--- pkg-config resolution" - for p in armadillo opencv4 Qt6Qwt6; do - echo "$p: $(pkg-config --modversion $p 2>&1)" + for p in armadillo opencv4 opencv5 Qt6Qwt6; do + echo "$p: $(pkg-config --modversion $p 2>&1 | head -1)" done echo "--- qwt keg layout" - find "$(brew --prefix qwt)" -maxdepth 3 \( -name "*.pc" -o -name "*.framework" -o -name "libqwt*" \) 2>/dev/null || true + find -L "$(brew --prefix qwt)" -maxdepth 3 \( -name "*.pc" -o -name "*.framework" -o -name "libqwt*" \) 2>/dev/null || true # Mirrors the naming used by build-windows.yml so artifacts line up. - name: Resolve version strings diff --git a/DFTFringe.pro b/DFTFringe.pro index f5498814..9da3232c 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -114,7 +114,18 @@ macx { # hard coded here: /usr/local on Intel and /opt/homebrew on Apple silicon. # PKG_CONFIG_PATH must list the qwt, opencv and armadillo kegs. # See "How to build DFTFringe on MacOS" in README.md. - PKGCONFIG += armadillo opencv4 Qt6Qwt6 + PKGCONFIG += armadillo Qt6Qwt6 + + # Homebrew's opencv formula now installs OpenCV 5, whose pkg-config name is + # opencv5. The CI installs opencv@4 to stay on the same major version as the + # Linux and Windows builds, but a local checkout may only have OpenCV 5. + packagesExist(opencv4) { + PKGCONFIG += opencv4 + message(............OPENCV: opencv4) + } else { + PKGCONFIG += opencv5 + message(............OPENCV: opencv5) + } LIBS += -lz # zip compression library needed for cnpy.cpp From 168475525e0604e58aa87df81a42d869d3abc692 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 17:48:02 +0200 Subject: [PATCH 03/12] Point the macOS include path at qwt's framework headers Homebrew builds qwt as a macOS framework. Its pkg-config file advertises an include directory that does not hold the headers, so every translation unit including failed to compile. Co-Authored-By: Claude Opus 5 (1M context) --- DFTFringe.pro | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DFTFringe.pro b/DFTFringe.pro index 9da3232c..4684137a 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -127,6 +127,15 @@ macx { message(............OPENCV: opencv5) } + # Homebrew builds qwt as a macOS framework, so the headers sit inside the + # bundle rather than in the include directory its pkg-config file names. + # The sources include them unqualified, as , on every platform. + QWT_FRAMEWORK_HEADERS = $$system(pkg-config --variable=libdir Qt6Qwt6)/qwt.framework/Headers + exists($$QWT_FRAMEWORK_HEADERS) { + INCLUDEPATH += $$QWT_FRAMEWORK_HEADERS + message(.......QWT HEADERS: $$QWT_FRAMEWORK_HEADERS) + } + LIBS += -lz # zip compression library needed for cnpy.cpp message(........QT_VERSION: $$[QT_VERSION]) From d3b88f6d7a7d050482d4f9cc72d607495df1418d Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 17:53:28 +0200 Subject: [PATCH 04/12] Let Boost.Stacktrace build on macOS Boost.Stacktrace guards _Unwind_Backtrace behind _GNU_SOURCE, which is a glibc convention. On macOS the function comes from Apple's libunwind and needs no such define, so main.cpp was the only translation unit that failed to compile. Co-Authored-By: Claude Opus 5 (1M context) --- DFTFringe.pro | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DFTFringe.pro b/DFTFringe.pro index 4684137a..9b2b6072 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -138,6 +138,11 @@ macx { LIBS += -lz # zip compression library needed for cnpy.cpp + # Boost.Stacktrace guards _Unwind_Backtrace behind _GNU_SOURCE, which is a + # glibc convention. On macOS the function comes from Apple's libunwind and + # is available without it. + DEFINES += BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED + message(........QT_VERSION: $$[QT_VERSION]) message(.QT_INSTALL_PREFIX: $$[QT_INSTALL_PREFIX]) message(..............ARCHS: $$QMAKE_APPLE_DEVICE_ARCHS) From d6daa55856112d7aca0fb304d5dd10f21eabffb2 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 18:05:48 +0200 Subject: [PATCH 05/12] Add the bundle version keys when qmake has not emitted them qmake only templates CFBundleShortVersionString and CFBundleVersion when VERSION is a dotted number. Untagged builds carry a commit sha, so the keys are absent rather than wrong and PlistBuddy Set had nothing to act on. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-macos.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 7a33e22e..159022ad 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -98,10 +98,15 @@ jobs: "$QT_PREFIX/bin/macdeployqt" build/release/DFTFringe.app -verbose=1 # colormapviewerdlg looks for ColorMaps next to the executable. cp -R ColorMaps build/release/DFTFringe.app/Contents/MacOS/ - /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $BUNDLE_VERSION" \ - build/release/DFTFringe.app/Contents/Info.plist - /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUNDLE_VERSION" \ - build/release/DFTFringe.app/Contents/Info.plist + # qmake only templates the version keys when VERSION is a dotted + # number, and untagged builds carry a commit sha, so the keys may be + # absent rather than merely wrong. + PLIST=build/release/DFTFringe.app/Contents/Info.plist + for key in CFBundleShortVersionString CFBundleVersion; do + /usr/libexec/PlistBuddy -c "Set :$key $BUNDLE_VERSION" "$PLIST" 2>/dev/null \ + || /usr/libexec/PlistBuddy -c "Add :$key string $BUNDLE_VERSION" "$PLIST" + done + plutil -p "$PLIST" # Ad-hoc signature only. Without an Apple Developer ID the app cannot be # notarised, so users have to clear the quarantine flag themselves. See From c41804832ef52256eb935584e023bf5901655da8 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 18:22:08 +0200 Subject: [PATCH 06/12] Link only the OpenCV modules DFTFringe uses and publish the disk images opencv4.pc lists every module, so the bundle carried dnn, gapi and the OpenVINO stack and weighed 112 MB on arm64 and 141 MB on x86_64. Link the same six modules as the Linux and Windows builds instead. make-release now builds macOS alongside Windows and attaches both disk images to the draft release. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/make-release.yml | 14 +++++++++++++- DFTFringe.pro | 19 +++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 8830af96..fc3044c9 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -30,6 +30,10 @@ jobs: call-build-windows: needs: check-semver uses: ./.github/workflows/build-windows.yml + # one disk image per architecture, see build-macos.yml + call-build-macos: + needs: check-semver + uses: ./.github/workflows/build-macos.yml # linux build is mainly here to check it builds. We have no acrtifact now. call-build-linux: permissions: @@ -40,12 +44,16 @@ jobs: download-and-publish-artifacts: runs-on: ubuntu-latest - needs: call-build-windows + needs: [call-build-windows, call-build-macos] steps: # get artifact uploaded from build workflow - uses: actions/download-artifact@v8 with: name: DFTFringe-windows-build-artifact + - uses: actions/download-artifact@v4 + with: + pattern: DFTFringe-macos-*-build-artifact + merge-multiple: true # create the GitHub release and upload the artifacts - name: publish Release uses: softprops/action-gh-release@v3 @@ -53,6 +61,8 @@ jobs: body: | - edit this changelog - test the installer one last time + - the macOS disk images are not notarised, so first launch needs + right click then Open. See the README for details. - make the actual release from this draft # the release will be drafted so it needs to be manually published after release notes editions draft: true @@ -61,3 +71,5 @@ jobs: files: | DFTFringeInstaller_${{github.ref_name}}.exe Z_DFTFringe.exe.debug + DFTFringe-${{github.ref_name}}-arm64.dmg + DFTFringe-${{github.ref_name}}-x86_64.dmg diff --git a/DFTFringe.pro b/DFTFringe.pro index 9b2b6072..8e5f2764 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -120,12 +120,23 @@ macx { # opencv5. The CI installs opencv@4 to stay on the same major version as the # Linux and Windows builds, but a local checkout may only have OpenCV 5. packagesExist(opencv4) { - PKGCONFIG += opencv4 - message(............OPENCV: opencv4) + OPENCV_PACKAGE = opencv4 } else { - PKGCONFIG += opencv5 - message(............OPENCV: opencv5) + OPENCV_PACKAGE = opencv5 } + message(............OPENCV: $$OPENCV_PACKAGE) + + # Deliberately not PKGCONFIG: the .pc file links every OpenCV module, which + # pulls dnn, gapi and the OpenVINO stack into the bundle and roughly doubles + # its size. Link the same six modules the Linux and Windows builds use. + QMAKE_CXXFLAGS += $$system(pkg-config --cflags-only-I $$OPENCV_PACKAGE) + LIBS += $$system(pkg-config --libs-only-L $$OPENCV_PACKAGE) + LIBS += -lopencv_calib3d + LIBS += -lopencv_core + LIBS += -lopencv_features2d + LIBS += -lopencv_highgui + LIBS += -lopencv_imgcodecs + LIBS += -lopencv_imgproc # Homebrew builds qwt as a macOS framework, so the headers sit inside the # bundle rather than in the include directory its pkg-config file names. From 41ce241e3bd5de8a331c9e0aff683c9938d072b2 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 18:29:50 +0200 Subject: [PATCH 07/12] Sign the bundle inside out rather than with codesign --deep Apple deprecates --deep for signing and it can leave nested code invalidly signed. This bundle nests around twenty frameworks plus the Qt plugins, so sign those first and the app last, then verify the result. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-macos.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 159022ad..f7fbdb51 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -111,8 +111,19 @@ jobs: # Ad-hoc signature only. Without an Apple Developer ID the app cannot be # notarised, so users have to clear the quarantine flag themselves. See # "How to build DFTFringe on MacOS" in README.md. + # + # Signed inside out rather than with --deep, which Apple deprecates for + # signing and which can leave the nested frameworks invalidly signed. - name: Ad-hoc sign - run: codesign --force --deep --sign - build/release/DFTFringe.app + run: | + APP=build/release/DFTFringe.app + find "$APP/Contents/Frameworks" "$APP/Contents/PlugIns" \ + \( -name "*.dylib" -o -name "*.so" \) \ + -exec codesign --force --sign - --timestamp=none {} \; 2>/dev/null || true + find "$APP/Contents/Frameworks" -maxdepth 1 -name "*.framework" \ + -exec codesign --force --sign - --timestamp=none {} \; 2>/dev/null || true + codesign --force --sign - --timestamp=none "$APP" + codesign --verify --deep --strict --verbose=2 "$APP" - name: Verify the bundle is self contained run: | From 03dc54b048a32b4e0337118cf6b1d9dde8782de1 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 18:40:48 +0200 Subject: [PATCH 08/12] Document building and installing DFTFringe on macOS Covers the Homebrew dependencies, why opencv@4 rather than opencv, the PKG_CONFIG_PATH needed for the keg-only and split kegs, bundling with macdeployqt, and the Gatekeeper workaround the unsigned disk images need. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d67cdcc8..0908dbb6 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,71 @@ cd .. make -j4 ``` +# How to install DFTFringe on MacOS + +:point_right: Download the disk image matching your Mac from the +[latest release](https://github.com/githubdoe/dftfringe/releases/latest): +`arm64` for Apple silicon, `x86_64` for Intel. Open it and drag DFTFringe into +Applications. + +:warning: The application is not notarised, because that requires a paid Apple +Developer ID. macOS therefore refuses to open it on the first launch, usually by +quitting immediately without a message. Clear the quarantine flag once: + +``` +xattr -dr com.apple.quarantine /Applications/DFTFringe.app +``` + +Alternatively, try to open it, then go to System Settings, Privacy & Security, +and press "Open Anyway". Later launches need no special treatment. + +macOS 15 or later is required. The dependencies come from Homebrew, which builds +for the version of macOS it runs on, so the release cannot target anything older. + # How to build DFTFringe on MacOS -:building_construction: Under construction :building_construction: +Dependencies come from [Homebrew](https://brew.sh). `opencv@4` rather than +`opencv`, because the latter is now OpenCV 5, and the Linux and Windows builds +are on OpenCV 4: + +``` +brew install qt qwt opencv@4 armadillo +``` + +qmake finds these through pkg-config, so no path is hard coded in the project +file. `opencv@4` is keg-only and Qt6 is split across several kegs, so point +`PKG_CONFIG_PATH` at them. This works on both Apple silicon and Intel, where +Homebrew lives in `/opt/homebrew` and `/usr/local` respectively: + +``` +export PKG_CONFIG_PATH="$(brew --prefix qwt)/lib/pkgconfig:$(brew --prefix opencv@4)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" +$(brew --prefix qt)/bin/qmake DFTFringe.pro CONFIG+=release +make -j$(sysctl -n hw.ncpu) +``` + +That leaves `build/release/DFTFringe.app` linked against the Homebrew +directories. To make it self contained, copy the dependencies in and rewrite +their install names. `ColorMaps` has to sit next to the executable because +`colormapviewerdlg` looks for it in `applicationDirPath()`: + +``` +$(brew --prefix qt)/bin/macdeployqt build/release/DFTFringe.app +cp -R ColorMaps build/release/DFTFringe.app/Contents/MacOS/ +open build/release/DFTFringe.app +``` + +Notes: + +- The build targets the architecture of the machine it runs on. A universal + binary would mean rebuilding every Homebrew dependency for both architectures + and merging them with `lipo`, so the CI publishes one disk image per + architecture instead. See `.github/workflows/build-macos.yml`. +- The project file links only the six OpenCV modules DFTFringe uses. Letting + `opencv4.pc` decide pulls in `dnn`, `gapi` and the OpenVINO stack and roughly + doubles the size of the bundle. +- Homebrew builds qwt as a macOS framework, so its headers live inside + `qwt.framework/Headers` rather than in the include directory its pkg-config + file advertises. The project file adds that directory itself. # How to build DFTFringe on Windows From 8d37493da1b8a04acb695112df80edaa19959542 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Sun, 2 Aug 2026 18:44:00 +0200 Subject: [PATCH 09/12] Ship the colour maps in Contents/Resources so the bundle can be signed Contents/MacOS may hold nothing but code. Copying ColorMaps beside the executable made codesign report the .cmp files as unsigned code objects and invalidated the whole bundle signature, which is why macOS killed the app when it was launched from the Finder while running the binary directly still worked. colormapviewerdlg falls back to Contents/Resources on macOS, where the maps now live. The dialog looked next to the executable, which no macOS build has ever had, so this also makes the colour map viewer work for the first time. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-macos.yml | 6 ++++-- README.md | 7 ++++--- colormapviewerdlg.cpp | 8 ++++++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index f7fbdb51..4396f41c 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -96,8 +96,10 @@ jobs: - name: Bundle dependencies run: | "$QT_PREFIX/bin/macdeployqt" build/release/DFTFringe.app -verbose=1 - # colormapviewerdlg looks for ColorMaps next to the executable. - cp -R ColorMaps build/release/DFTFringe.app/Contents/MacOS/ + # Contents/MacOS may hold nothing but code, otherwise codesign rejects + # the bundle and macOS refuses to launch it. colormapviewerdlg falls + # back to Contents/Resources on macOS. + cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/ # qmake only templates the version keys when VERSION is a dotted # number, and untagged builds carry a commit sha, so the keys may be # absent rather than merely wrong. diff --git a/README.md b/README.md index 0908dbb6..3c5e4d36 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,13 @@ make -j$(sysctl -n hw.ncpu) That leaves `build/release/DFTFringe.app` linked against the Homebrew directories. To make it self contained, copy the dependencies in and rewrite -their install names. `ColorMaps` has to sit next to the executable because -`colormapviewerdlg` looks for it in `applicationDirPath()`: +their install names. The colour maps go into `Contents/Resources`, because +`Contents/MacOS` may hold nothing but code and `codesign` rejects a bundle with +data files in there: ``` $(brew --prefix qt)/bin/macdeployqt build/release/DFTFringe.app -cp -R ColorMaps build/release/DFTFringe.app/Contents/MacOS/ +cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/ open build/release/DFTFringe.app ``` diff --git a/colormapviewerdlg.cpp b/colormapviewerdlg.cpp index 6fab068f..326d0f7a 100644 --- a/colormapviewerdlg.cpp +++ b/colormapviewerdlg.cpp @@ -34,6 +34,14 @@ colorMapViewerDlg::colorMapViewerDlg(QWidget *parent) : { QSettings set; gpath = qApp->applicationDirPath() + "/ColorMaps"; +#ifdef Q_OS_MAC + // Inside an application bundle the executable sits in Contents/MacOS, which + // may hold nothing but code: codesign refuses to sign data files there and + // the resulting signature is rejected, so the colour maps ship one level up + // in Contents/Resources instead. + if (!QDir(gpath).exists()) + gpath = qApp->applicationDirPath() + "/../Resources/ColorMaps"; +#endif ui->setupUi(this); ui->path->setText(gpath); ui->listWidget->setViewMode(QListWidget::IconMode); From c69cd77c42bf3f45a5e789e803a9e30b1c0e3d8b Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Mon, 3 Aug 2026 17:15:11 +0200 Subject: [PATCH 10/12] Merge both architectures into one universal disk image Follows the review: a single download is less confusing than asking users to pick an architecture, and they cannot tell the difference anyway. The two matrix jobs now build a bundle each and a third job lipos every Mach-O file in them into one universal application, signs it and wraps it in a disk image. Signing happens only after the merge, since lipo invalidates it. The merge refuses to run if the two bundles do not contain the same files. Homebrew resolves formulae independently on the two runners, so a version bump landing between the jobs would otherwise silently produce an application built against two different sets of libraries. Also updates the action versions to match #350 and drops the working branch from the push trigger. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build-macos.yml | 150 +++++++++++++++++------------ .github/workflows/make-release.yml | 12 +-- DFTFringe.pro | 44 +++------ 3 files changed, 107 insertions(+), 99 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 4396f41c..7ff47a98 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -3,12 +3,14 @@ on: push: branches: - master - - macos-build pull_request: workflow_dispatch: workflow_call: jobs: + # Builds one bundle per architecture. They are merged into a single universal + # bundle by the job below, so nothing is signed here: lipo would invalidate the + # signature anyway. build-macos: strategy: fail-fast: false @@ -20,17 +22,15 @@ jobs: arch: x86_64 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - # opencv pulls a large dependency tree, so this is the slowest step by far. - # opencv@4 rather than opencv, which is now OpenCV 5, so that all three - # platforms build against the same OpenCV major version. + # opencv@4 rather than opencv, which is now OpenCV 5, to stay on the same + # major version as the Linux and Windows builds. - name: Install dependencies run: brew install qt qwt opencv@4 armadillo - # Homebrew lives in /usr/local on Intel and /opt/homebrew on Apple silicon, - # and Qt6 is split across several kegs, so everything is resolved at runtime - # rather than hard coded. + # Homebrew is in /usr/local on Intel and /opt/homebrew on Apple silicon, + # and Qt6 is split across several kegs. - name: Resolve Homebrew layout run: | QT_PREFIX="$(brew --prefix qt)" @@ -38,29 +38,7 @@ jobs: echo "QT_PREFIX=$QT_PREFIX" >> "$GITHUB_ENV" echo "PKG_CONFIG_PATH=$(brew --prefix qwt)/lib/pkgconfig:$(brew --prefix opencv@4)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" >> "$GITHUB_ENV" - # Prints everything needed to diagnose a failed configure step without - # having to guess at the Homebrew layout from the outside. - - name: Probe toolchain - run: | - echo "--- prefixes" - for f in qt qtbase qwt opencv@4 armadillo; do - echo "$f -> $(brew --prefix $f 2>/dev/null || echo 'NOT INSTALLED')" - done - echo "--- qmake" - "$QT_PREFIX/bin/qmake" -v - "$QT_PREFIX/bin/qmake" -query - echo "--- qt modules visible to qmake" - ls "$(brew --prefix)/share/qt/mkspecs/modules" 2>/dev/null | grep -iE "charts|datavis|opengl|printsupport" || echo "(module .pri files not found)" - echo "--- pkg-config packages" - pkg-config --list-all | grep -iE "qwt|opencv|armadillo" || echo "(no matches)" - echo "--- pkg-config resolution" - for p in armadillo opencv4 opencv5 Qt6Qwt6; do - echo "$p: $(pkg-config --modversion $p 2>&1 | head -1)" - done - echo "--- qwt keg layout" - find -L "$(brew --prefix qwt)" -maxdepth 3 \( -name "*.pc" -o -name "*.framework" -o -name "libqwt*" \) 2>/dev/null || true - - # Mirrors the naming used by build-windows.yml so artifacts line up. + # Same naming as build-windows.yml. - name: Resolve version strings run: | if [ "${{ github.event_name }}" = "pull_request" ]; then @@ -70,13 +48,12 @@ jobs: else WORKFLOW_VERSION="${{ github.sha }}" fi - # CFBundleShortVersionString only accepts a dotted numeric string, so a - # commit sha cannot go there. Tags give a real version, anything else 0.0.0. + # CFBundleShortVersionString only accepts a dotted number, so a commit + # sha cannot go there. BUNDLE_VERSION="$(printf '%s' "$WORKFLOW_VERSION" | sed -nE 's/^v?([0-9]+\.[0-9]+\.[0-9]+).*$/\1/p')" if [ -z "$BUNDLE_VERSION" ]; then BUNDLE_VERSION="0.0.0"; fi echo "WORKFLOW_VERSION=$WORKFLOW_VERSION" >> "$GITHUB_ENV" echo "BUNDLE_VERSION=$BUNDLE_VERSION" >> "$GITHUB_ENV" - echo "version=$WORKFLOW_VERSION bundle=$BUNDLE_VERSION" - name: Find and Replace MY_AUTOMATED_VERSION_STRING run: sed -i '' "s/MY_AUTOMATED_VERSION_STRING/${WORKFLOW_VERSION}/" DFTFringe.pro @@ -91,34 +68,92 @@ jobs: run: make -j$(sysctl -n hw.ncpu) - run: echo "::remove-matcher owner=uic-problem-matcher::" - # macdeployqt copies the Qt frameworks and the Homebrew dylibs the binary - # references into the bundle and rewrites their install names. - name: Bundle dependencies run: | "$QT_PREFIX/bin/macdeployqt" build/release/DFTFringe.app -verbose=1 - # Contents/MacOS may hold nothing but code, otherwise codesign rejects - # the bundle and macOS refuses to launch it. colormapviewerdlg falls - # back to Contents/Resources on macOS. + # Contents/MacOS may hold nothing but code or codesign rejects the + # bundle. colormapviewerdlg falls back to Contents/Resources on macOS. cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/ # qmake only templates the version keys when VERSION is a dotted - # number, and untagged builds carry a commit sha, so the keys may be - # absent rather than merely wrong. + # number, so for untagged builds they may be absent rather than wrong. PLIST=build/release/DFTFringe.app/Contents/Info.plist for key in CFBundleShortVersionString CFBundleVersion; do /usr/libexec/PlistBuddy -c "Set :$key $BUNDLE_VERSION" "$PLIST" 2>/dev/null \ || /usr/libexec/PlistBuddy -c "Add :$key string $BUNDLE_VERSION" "$PLIST" done - plutil -p "$PLIST" - # Ad-hoc signature only. Without an Apple Developer ID the app cannot be - # notarised, so users have to clear the quarantine flag themselves. See - # "How to build DFTFringe on MacOS" in README.md. - # + - name: Verify the bundle is self contained + run: | + otool -L build/release/DFTFringe.app/Contents/MacOS/DFTFringe \ + | tail -n +2 | awk '{print $1}' \ + | grep -vE "^(@rpath|@executable_path|/usr/lib|/System)" \ + && { echo "bundle references paths outside itself"; exit 1; } || true + + # Tarred because upload-artifact does not preserve the symlinks inside the + # Qt frameworks. + - name: Upload bundle + run: tar czf DFTFringe-${{ matrix.arch }}.tar.gz -C build/release DFTFringe.app + - uses: actions/upload-artifact@v7 + with: + name: DFTFringe-macos-${{ matrix.arch }}-bundle + path: DFTFringe-${{ matrix.arch }}.tar.gz + retention-days: 1 + + # Merges the two bundles into one universal application, signs it and wraps it + # in a disk image. Users get a single download that runs on both architectures. + universal-dmg: + needs: build-macos + runs-on: macos-15 + steps: + - uses: actions/download-artifact@v8 + with: + pattern: DFTFringe-macos-*-bundle + merge-multiple: true + + - name: Resolve version strings + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + WORKFLOW_VERSION="${{ github.event.pull_request.head.sha }}_${{ github.event.pull_request.base.sha }}" + elif [ "${{ startsWith(github.ref, 'refs/tags/v') }}" = "true" ]; then + WORKFLOW_VERSION="${{ github.ref_name }}" + else + WORKFLOW_VERSION="${{ github.sha }}" + fi + echo "WORKFLOW_VERSION=$WORKFLOW_VERSION" >> "$GITHUB_ENV" + + - name: Merge the two architectures + run: | + mkdir arm64 x86_64 + tar xzf DFTFringe-arm64.tar.gz -C arm64 + tar xzf DFTFringe-x86_64.tar.gz -C x86_64 + ARM="$PWD/arm64/DFTFringe.app" + INTEL="$PWD/x86_64/DFTFringe.app" + + # Homebrew resolves formulae independently on the two runners, so a + # version bump landing between the jobs would give the bundles + # different contents. Merging those silently would ship an + # application half built against two different sets of libraries. + diff <(cd "$ARM" && find . | sort) <(cd "$INTEL" && find . | sort) \ + || { echo "the two bundles do not contain the same files"; exit 1; } + + cp -R "$ARM" DFTFringe.app + UNIVERSAL="$PWD/DFTFringe.app" + find "$UNIVERSAL" -type f | while read -r f; do + rel="${f#$UNIVERSAL/}" + if file -b "$f" | grep -q "Mach-O"; then + lipo -create "$ARM/$rel" "$INTEL/$rel" -output "$f" + fi + done + + echo "--- architectures in the merged executable:" + lipo -info "$UNIVERSAL/Contents/MacOS/DFTFringe" + # Signed inside out rather than with --deep, which Apple deprecates for - # signing and which can leave the nested frameworks invalidly signed. + # signing. arm64 code has to carry at least an ad-hoc signature to run at + # all, and lipo invalidated whatever was there before. - name: Ad-hoc sign run: | - APP=build/release/DFTFringe.app + APP=DFTFringe.app find "$APP/Contents/Frameworks" "$APP/Contents/PlugIns" \ \( -name "*.dylib" -o -name "*.so" \) \ -exec codesign --force --sign - --timestamp=none {} \; 2>/dev/null || true @@ -127,25 +162,16 @@ jobs: codesign --force --sign - --timestamp=none "$APP" codesign --verify --deep --strict --verbose=2 "$APP" - - name: Verify the bundle is self contained - run: | - otool -L build/release/DFTFringe.app/Contents/MacOS/DFTFringe - echo "--- references outside the bundle and the system:" - otool -L build/release/DFTFringe.app/Contents/MacOS/DFTFringe \ - | tail -n +2 | awk '{print $1}' \ - | grep -vE "^(@rpath|@executable_path|/usr/lib|/System)" || echo "(none)" - - name: Create disk image run: | STAGE="$(mktemp -d)/DFTFringe" mkdir -p "$STAGE" - cp -R build/release/DFTFringe.app "$STAGE/" + cp -R DFTFringe.app "$STAGE/" ln -s /Applications "$STAGE/Applications" hdiutil create -volname "DFTFringe" -srcfolder "$STAGE" -ov -format UDZO \ - "DFTFringe-${WORKFLOW_VERSION}-${{ matrix.arch }}.dmg" + "DFTFringe-${WORKFLOW_VERSION}.dmg" - - name: Upload Artifact - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: - name: DFTFringe-macos-${{ matrix.arch }}-build-artifact - path: DFTFringe-${{ env.WORKFLOW_VERSION }}-${{ matrix.arch }}.dmg + name: DFTFringe-macos-build-artifact + path: DFTFringe-${{ env.WORKFLOW_VERSION }}.dmg diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index fc3044c9..a9c228f1 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -50,10 +50,9 @@ jobs: - uses: actions/download-artifact@v8 with: name: DFTFringe-windows-build-artifact - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: - pattern: DFTFringe-macos-*-build-artifact - merge-multiple: true + name: DFTFringe-macos-build-artifact # create the GitHub release and upload the artifacts - name: publish Release uses: softprops/action-gh-release@v3 @@ -61,8 +60,8 @@ jobs: body: | - edit this changelog - test the installer one last time - - the macOS disk images are not notarised, so first launch needs - right click then Open. See the README for details. + - the macOS disk image is not notarised, so the first launch needs + the quarantine flag cleared. See the README for details. - make the actual release from this draft # the release will be drafted so it needs to be manually published after release notes editions draft: true @@ -71,5 +70,4 @@ jobs: files: | DFTFringeInstaller_${{github.ref_name}}.exe Z_DFTFringe.exe.debug - DFTFringe-${{github.ref_name}}-arm64.dmg - DFTFringe-${{github.ref_name}}-x86_64.dmg + DFTFringe-${{github.ref_name}}.dmg diff --git a/DFTFringe.pro b/DFTFringe.pro index 8e5f2764..d587f288 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -97,9 +97,8 @@ macx { CONFIG += sdk_no_version_check CONFIG += link_pkgconfig - # Build for the host architecture only. Homebrew ships single architecture - # libraries, so a universal binary would need every dependency rebuilt and - # lipo'd. The CI produces one disk image per architecture instead. + # Homebrew libraries are single architecture, so each build targets its host. + # The CI merges an arm64 and an x86_64 build into one universal application. QMAKE_APPLE_DEVICE_ARCHS = $$QMAKE_HOST.arch CONFIG( debug, debug|release ) { DESTDIR = build/debug } @@ -110,25 +109,21 @@ macx { RCC_DIR = $$DESTDIR/.qrc UI_DIR = $$DESTDIR/.ui - # Dependencies are resolved through pkg-config so that no Homebrew prefix is - # hard coded here: /usr/local on Intel and /opt/homebrew on Apple silicon. - # PKG_CONFIG_PATH must list the qwt, opencv and armadillo kegs. - # See "How to build DFTFringe on MacOS" in README.md. + # pkg-config keeps the Homebrew prefix out of this file: it is /usr/local on + # Intel and /opt/homebrew on Apple silicon. See the README for the + # PKG_CONFIG_PATH the kegs need. PKGCONFIG += armadillo Qt6Qwt6 - # Homebrew's opencv formula now installs OpenCV 5, whose pkg-config name is - # opencv5. The CI installs opencv@4 to stay on the same major version as the - # Linux and Windows builds, but a local checkout may only have OpenCV 5. + # Homebrew's opencv formula is OpenCV 5 now. The CI installs opencv@4 to match + # the Linux and Windows builds, but a local checkout may only have OpenCV 5. packagesExist(opencv4) { OPENCV_PACKAGE = opencv4 } else { OPENCV_PACKAGE = opencv5 } - message(............OPENCV: $$OPENCV_PACKAGE) - # Deliberately not PKGCONFIG: the .pc file links every OpenCV module, which - # pulls dnn, gapi and the OpenVINO stack into the bundle and roughly doubles - # its size. Link the same six modules the Linux and Windows builds use. + # Not PKGCONFIG: the .pc file links every OpenCV module, which drags dnn, gapi + # and OpenVINO into the bundle. Link the six the other platforms use. QMAKE_CXXFLAGS += $$system(pkg-config --cflags-only-I $$OPENCV_PACKAGE) LIBS += $$system(pkg-config --libs-only-L $$OPENCV_PACKAGE) LIBS += -lopencv_calib3d @@ -138,27 +133,16 @@ macx { LIBS += -lopencv_imgcodecs LIBS += -lopencv_imgproc - # Homebrew builds qwt as a macOS framework, so the headers sit inside the - # bundle rather than in the include directory its pkg-config file names. - # The sources include them unqualified, as , on every platform. + # Homebrew builds qwt as a framework, so its headers are inside the bundle and + # not in the include directory its pkg-config file advertises. QWT_FRAMEWORK_HEADERS = $$system(pkg-config --variable=libdir Qt6Qwt6)/qwt.framework/Headers - exists($$QWT_FRAMEWORK_HEADERS) { - INCLUDEPATH += $$QWT_FRAMEWORK_HEADERS - message(.......QWT HEADERS: $$QWT_FRAMEWORK_HEADERS) - } + exists($$QWT_FRAMEWORK_HEADERS): INCLUDEPATH += $$QWT_FRAMEWORK_HEADERS LIBS += -lz # zip compression library needed for cnpy.cpp - # Boost.Stacktrace guards _Unwind_Backtrace behind _GNU_SOURCE, which is a - # glibc convention. On macOS the function comes from Apple's libunwind and - # is available without it. + # Boost.Stacktrace guards _Unwind_Backtrace behind _GNU_SOURCE, a glibc + # convention. On macOS it comes from Apple's libunwind and needs no define. DEFINES += BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED - - message(........QT_VERSION: $$[QT_VERSION]) - message(.QT_INSTALL_PREFIX: $$[QT_INSTALL_PREFIX]) - message(..............ARCHS: $$QMAKE_APPLE_DEVICE_ARCHS) - message(...........DESTDIR: $$DESTDIR) - message(.........PKGCONFIG: $$PKGCONFIG) } # Below are the includes for source files and other resources, sorted alphabetically. ################################## From e4db87eaba5ea31cc468d1b9051e50c89b061d35 Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Mon, 3 Aug 2026 17:32:56 +0200 Subject: [PATCH 11/12] Shorten the macOS README sections and add the build badge Rewritten with less fluff, and updated for the universal disk image. Drops the notes about OpenCV module selection and the qwt framework headers, which are explained where they are done in DFTFringe.pro rather than repeated here. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 59 +++++++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 3c5e4d36..946d8612 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DFTFringe -[![build-windows](https://github.com/githubdoe/DFTFringe/actions/workflows/build-windows.yml/badge.svg?branch=master)](https://github.com/githubdoe/DFTFringe/actions/workflows/build-windows.yml) [![build-linux](https://github.com/githubdoe/DFTFringe/actions/workflows/build-linux.yml/badge.svg?branch=master)](https://github.com/githubdoe/DFTFringe/actions/workflows/build-linux.yml) +[![build-windows](https://github.com/githubdoe/DFTFringe/actions/workflows/build-windows.yml/badge.svg?branch=master)](https://github.com/githubdoe/DFTFringe/actions/workflows/build-windows.yml) [![build-linux](https://github.com/githubdoe/DFTFringe/actions/workflows/build-linux.yml/badge.svg?branch=master)](https://github.com/githubdoe/DFTFringe/actions/workflows/build-linux.yml) [![build-macos](https://github.com/githubdoe/DFTFringe/actions/workflows/build-macos.yml/badge.svg?branch=master)](https://github.com/githubdoe/DFTFringe/actions/workflows/build-macos.yml) # Introduction @@ -52,39 +52,34 @@ make -j4 # How to install DFTFringe on MacOS -:point_right: Download the disk image matching your Mac from the -[latest release](https://github.com/githubdoe/dftfringe/releases/latest): -`arm64` for Apple silicon, `x86_64` for Intel. Open it and drag DFTFringe into -Applications. +:point_right: Download the disk image from the +[latest release](https://github.com/githubdoe/dftfringe/releases/latest), open it +and drag DFTFringe into Applications. It is a universal build, so it runs on both +Apple silicon and Intel. macOS 15 or later. -:warning: The application is not notarised, because that requires a paid Apple -Developer ID. macOS therefore refuses to open it on the first launch, usually by -quitting immediately without a message. Clear the quarantine flag once: +:warning: The app is not notarised, which needs a paid Apple Developer ID, so +macOS refuses to open it the first time and usually just quits without saying +anything. Clear the quarantine flag once and it behaves normally afterwards: ``` xattr -dr com.apple.quarantine /Applications/DFTFringe.app ``` -Alternatively, try to open it, then go to System Settings, Privacy & Security, -and press "Open Anyway". Later launches need no special treatment. - -macOS 15 or later is required. The dependencies come from Homebrew, which builds -for the version of macOS it runs on, so the release cannot target anything older. +You can also try to open it, then go to System Settings, Privacy & Security, and +press "Open Anyway". # How to build DFTFringe on MacOS -Dependencies come from [Homebrew](https://brew.sh). `opencv@4` rather than -`opencv`, because the latter is now OpenCV 5, and the Linux and Windows builds -are on OpenCV 4: +Dependencies come from [Homebrew](https://brew.sh). Use `opencv@4` and not +`opencv`, which is OpenCV 5 now: ``` brew install qt qwt opencv@4 armadillo ``` -qmake finds these through pkg-config, so no path is hard coded in the project -file. `opencv@4` is keg-only and Qt6 is split across several kegs, so point -`PKG_CONFIG_PATH` at them. This works on both Apple silicon and Intel, where -Homebrew lives in `/opt/homebrew` and `/usr/local` respectively: +qmake finds them through pkg-config, so nothing is hard coded in the project file. +`opencv@4` is keg-only and Qt6 is split across several kegs, so point +`PKG_CONFIG_PATH` at them: ``` export PKG_CONFIG_PATH="$(brew --prefix qwt)/lib/pkgconfig:$(brew --prefix opencv@4)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" @@ -92,11 +87,10 @@ $(brew --prefix qt)/bin/qmake DFTFringe.pro CONFIG+=release make -j$(sysctl -n hw.ncpu) ``` -That leaves `build/release/DFTFringe.app` linked against the Homebrew -directories. To make it self contained, copy the dependencies in and rewrite -their install names. The colour maps go into `Contents/Resources`, because -`Contents/MacOS` may hold nothing but code and `codesign` rejects a bundle with -data files in there: +That gives you `build/release/DFTFringe.app`, still linked against Homebrew. +`macdeployqt` copies the libraries in and rewrites their install names. The colour +maps have to go in `Contents/Resources`, because `Contents/MacOS` may hold nothing +but code and `codesign` rejects the bundle otherwise: ``` $(brew --prefix qt)/bin/macdeployqt build/release/DFTFringe.app @@ -104,18 +98,9 @@ cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/ open build/release/DFTFringe.app ``` -Notes: - -- The build targets the architecture of the machine it runs on. A universal - binary would mean rebuilding every Homebrew dependency for both architectures - and merging them with `lipo`, so the CI publishes one disk image per - architecture instead. See `.github/workflows/build-macos.yml`. -- The project file links only the six OpenCV modules DFTFringe uses. Letting - `opencv4.pc` decide pulls in `dnn`, `gapi` and the OpenVINO stack and roughly - doubles the size of the bundle. -- Homebrew builds qwt as a macOS framework, so its headers live inside - `qwt.framework/Headers` rather than in the include directory its pkg-config - file advertises. The project file adds that directory itself. +A build targets the machine it runs on, since Homebrew libraries are single +architecture. The CI builds both and merges them with `lipo` into the universal +application that ships. See `.github/workflows/build-macos.yml`. # How to build DFTFringe on Windows From 8446dfec09afb64180d62ba2056f09c65b5cd41f Mon Sep 17 00:00:00 2001 From: Chantepierre Date: Mon, 3 Aug 2026 17:40:33 +0200 Subject: [PATCH 12/12] Rewrites Mac OS Launch section to be clearer --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 946d8612..583ae57d 100644 --- a/README.md +++ b/README.md @@ -52,22 +52,22 @@ make -j4 # How to install DFTFringe on MacOS -:point_right: Download the disk image from the +Download the disk image from the [latest release](https://github.com/githubdoe/dftfringe/releases/latest), open it -and drag DFTFringe into Applications. It is a universal build, so it runs on both -Apple silicon and Intel. macOS 15 or later. +and drag DFTFringe into Applications. The required OS is MacOS 15 or later. -:warning: The app is not notarised, which needs a paid Apple Developer ID, so -macOS refuses to open it the first time and usually just quits without saying -anything. Clear the quarantine flag once and it behaves normally afterwards: +**The app is not notarised yet**, so macOS refuses to open it the first time and +will just quit without saying anything. To launch it : + +Right-click it -> "Open", then open "System Settings", go to "Privacy & Security", +find the DFTFringe security warning, and click "Open Anyway" + +Or, from the terminal : ``` xattr -dr com.apple.quarantine /Applications/DFTFringe.app ``` -You can also try to open it, then go to System Settings, Privacy & Security, and -press "Open Anyway". - # How to build DFTFringe on MacOS Dependencies come from [Homebrew](https://brew.sh). Use `opencv@4` and not