Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
name: build-macos
on:
push:
branches:
- master
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
matrix:
include:
- os: macos-15
arch: arm64
- os: macos-15-intel
arch: x86_64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6

# 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 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)"
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@4)/lib/pkgconfig:$(brew --prefix armadillo)/lib/pkgconfig:$(brew --prefix)/lib/pkgconfig" >> "$GITHUB_ENV"

# Same naming as build-windows.yml.
- 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 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"

- 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::"

- name: Bundle dependencies
run: |
"$QT_PREFIX/bin/macdeployqt" build/release/DFTFringe.app -verbose=1
# 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, 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

- 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. 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=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: Create disk image
run: |
STAGE="$(mktemp -d)/DFTFringe"
mkdir -p "$STAGE"
cp -R DFTFringe.app "$STAGE/"
ln -s /Applications "$STAGE/Applications"
hdiutil create -volname "DFTFringe" -srcfolder "$STAGE" -ov -format UDZO \
"DFTFringe-${WORKFLOW_VERSION}.dmg"

- uses: actions/upload-artifact@v7
with:
name: DFTFringe-macos-build-artifact
path: DFTFringe-${{ env.WORKFLOW_VERSION }}.dmg
12 changes: 11 additions & 1 deletion .github/workflows/make-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -40,19 +44,24 @@ 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@v8
with:
name: DFTFringe-macos-build-artifact
# create the GitHub release and upload the artifacts
- name: publish Release
uses: softprops/action-gh-release@v3
with:
body: |
- edit this changelog
- test the installer one last time
- 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
Expand All @@ -61,3 +70,4 @@ jobs:
files: |
DFTFringeInstaller_${{github.ref_name}}.exe
Z_DFTFringe.exe.debug
DFTFringe-${{github.ref_name}}.dmg
68 changes: 38 additions & 30 deletions DFTFringe.pro
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,10 @@ 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
# 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 }
CONFIG( release, debug|release ) { DESTDIR = build/release }
Expand All @@ -109,32 +108,41 @@ 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

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(...........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)

# 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 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
}

# 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
LIBS += -lopencv_core
LIBS += -lopencv_features2d
LIBS += -lopencv_highgui
LIBS += -lopencv_imgcodecs
LIBS += -lopencv_imgproc

# 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

LIBS += -lz # zip compression library needed for cnpy.cpp

# 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
}

# Below are the includes for source files and other resources, sorted alphabetically. ##################################
Expand Down
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -50,9 +50,57 @@ cd ..
make -j4
```

# How to install DFTFringe on MacOS
Comment thread
atsju marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's quite strange to have

  • install Win
  • build Linux
  • install Mac
  • build Mac
  • build Win

Maybe reorder to :

  • install Win
  • install Mac
  • build Linux (there is no install)
  • build Mac or Win
  • build Win or Mac


Download the disk image from the
[latest release](https://github.com/githubdoe/dftfringe/releases/latest), open it
and drag DFTFringe into Applications. The required OS is MacOS 15 or later.

**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
Comment thread
atsju marked this conversation as resolved.
```

# How to build DFTFringe on MacOS

:building_construction: Under construction :building_construction:
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 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"
$(brew --prefix qt)/bin/qmake DFTFringe.pro CONFIG+=release
make -j$(sysctl -n hw.ncpu)
```

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
cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/
open build/release/DFTFringe.app
```

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

Expand Down
8 changes: 8 additions & 0 deletions colormapviewerdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading