diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml new file mode 100644 index 00000000..11b07d57 --- /dev/null +++ b/.github/workflows/build-macos.yml @@ -0,0 +1,145 @@ +name: build-macos +on: + push: + branches: + - master + 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 }} + outputs: + workflow-version: ${{ steps.version.outputs.workflow-version }} + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: brew install qt qwt opencv@4 armadillo + + - 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" + + - name: Resolve version strings + id: version + run: | + if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + WORKFLOW_VERSION="${{ github.event.pull_request.head.sha }}_${{ github.event.pull_request.base.sha }}" + elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then + WORKFLOW_VERSION="$GITHUB_REF_NAME" + else + WORKFLOW_VERSION="$GITHUB_SHA" + fi + BUNDLE_VERSION="$(printf '%s' "$WORKFLOW_VERSION" | sed -nE 's/^v?([0-9]+\.[0-9]+\.[0-9]+).*$/\1/p')" + echo "WORKFLOW_VERSION=$WORKFLOW_VERSION" >> "$GITHUB_ENV" + echo "BUNDLE_VERSION=${BUNDLE_VERSION:-0.0.0}" >> "$GITHUB_ENV" + echo "workflow-version=$WORKFLOW_VERSION" >> "$GITHUB_OUTPUT" + + - 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 + cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/ + 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: | + if otool -L build/release/DFTFringe.app/Contents/MacOS/DFTFringe \ + | tail -n +2 | awk '{print $1}' \ + | grep -vE "^(@rpath|@executable_path|/usr/lib|/System)"; then + echo "bundle references the paths above, outside itself" + exit 1 + fi + + - 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 + + universal-dmg: + needs: build-macos + runs-on: macos-15 + env: + WORKFLOW_VERSION: ${{ needs.build-macos.outputs.workflow-version }} + steps: + - uses: actions/download-artifact@v8 + with: + pattern: DFTFringe-macos-*-bundle + merge-multiple: true + + - 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" + + 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" + + - name: Ad-hoc sign + run: | + APP=DFTFringe.app + find "$APP/Contents/Frameworks" "$APP/Contents/PlugIns" \ + \( -name "*.dylib" -o -name "*.so" \) -exec codesign --force --sign - {} + + find "$APP/Contents/Frameworks" -maxdepth 1 -name "*.framework" \ + -exec codesign --force --sign - {} + + codesign --force --sign - "$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 diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 8830af96..ebfa4590 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -30,6 +30,9 @@ jobs: call-build-windows: needs: check-semver uses: ./.github/workflows/build-windows.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 +43,15 @@ 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 @@ -53,6 +59,8 @@ jobs: 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 @@ -61,3 +69,4 @@ jobs: files: | DFTFringeInstaller_${{github.ref_name}}.exe Z_DFTFringe.exe.debug + DFTFringe-${{github.ref_name}}.dmg diff --git a/DFTFringe.pro b/DFTFringe.pro index e94e9cab..a14b9973 100644 --- a/DFTFringe.pro +++ b/DFTFringe.pro @@ -96,11 +96,8 @@ 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 + QMAKE_APPLE_DEVICE_ARCHS = $$QMAKE_HOST.arch CONFIG( debug, debug|release ) { DESTDIR = build/debug } CONFIG( release, debug|release ) { DESTDIR = build/release } @@ -109,32 +106,30 @@ 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) + + PKGCONFIG += armadillo Qt6Qwt6 + + packagesExist(opencv4) { + OPENCV_PACKAGE = opencv4 + } else { + OPENCV_PACKAGE = opencv5 + } + + 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 + + QWT_FRAMEWORK_HEADERS = $$system(pkg-config --variable=libdir Qt6Qwt6)/qwt.framework/Headers + exists($$QWT_FRAMEWORK_HEADERS): INCLUDEPATH += $$QWT_FRAMEWORK_HEADERS + + LIBS += -lz + + DEFINES += BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED } # Below are the includes for source files and other resources, sorted alphabetically. ################################## diff --git a/README.md b/README.md index d67cdcc8..5dee7906 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 @@ -31,6 +31,24 @@ Additional information and help is availlable at https://groups.io/g/Interferome :point_right: Follow the link and use the installer: [link to latest release](https://github.com/githubdoe/dftfringe/releases/latest). +# How to install DFTFringe on MacOS + +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 +``` + # How to build DFTFringe on Linux @@ -52,7 +70,32 @@ make -j4 # How to build DFTFringe on MacOS -:building_construction: Under construction :building_construction: +Dependencies come from [Homebrew](https://brew.sh). + +``` +brew install qt qwt opencv@4 armadillo +``` + +qmake finds them through pkg-config. +`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) +``` + +This produces `build/release/DFTFringe.app`, linked against Homebrew libraries. +`macdeployqt` copies the libraries in and rewrites their install names. The colour +maps have to be copied in `Contents/Resources`. + +``` +$(brew --prefix qt)/bin/macdeployqt build/release/DFTFringe.app +cp -R ColorMaps build/release/DFTFringe.app/Contents/Resources/ +open build/release/DFTFringe.app +``` + +To produce an universal build with `lipo` from arm and intel builds, you can take `.github/workflows/build-macos.yml` as reference. # How to build DFTFringe on Windows diff --git a/colormapviewerdlg.cpp b/colormapviewerdlg.cpp index 6fab068f..5b10c5b7 100644 --- a/colormapviewerdlg.cpp +++ b/colormapviewerdlg.cpp @@ -34,6 +34,10 @@ colorMapViewerDlg::colorMapViewerDlg(QWidget *parent) : { QSettings set; gpath = qApp->applicationDirPath() + "/ColorMaps"; +#ifdef Q_OS_MAC + if (!QDir(gpath).exists()) + gpath = qApp->applicationDirPath() + "/../Resources/ColorMaps"; +#endif ui->setupUi(this); ui->path->setText(gpath); ui->listWidget->setViewMode(QListWidget::IconMode);