From b35983f537f89abdfb0fbc5fac5023e8fa5b5c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lesimple?= Date: Mon, 29 Jun 2026 12:51:09 +0200 Subject: [PATCH] chore: workflows: release: add macos --- .github/workflows/make-mac-app.sh | 161 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 100 ++++++++++++++++++ lib/qpxtransport/qpx_transport.cpp | 2 +- 3 files changed, 262 insertions(+), 1 deletion(-) create mode 100755 .github/workflows/make-mac-app.sh diff --git a/.github/workflows/make-mac-app.sh b/.github/workflows/make-mac-app.sh new file mode 100755 index 0000000..4172faa --- /dev/null +++ b/.github/workflows/make-mac-app.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# Build a self-contained qpxtool.app bundle and wrap it in a .dmg. +# +# Usage: make-mac-app.sh +# e.g. 0.8.1-pl7 (no leading "v") +# x86_64 | arm64 +# directory where "make install DESTDIR=... prefix=/usr" +# placed the tree, i.e. it contains usr/bin, usr/lib, ... +# +# Qt frameworks (and other Homebrew dylibs) are bundled via macdeployqt; the +# qpxtool libraries and scan plugins are bundled and relinked by hand so the +# app runs on a machine that has neither qpxtool nor Homebrew installed. +set -e +set -x + +version="$1" +arch="${2:-$(uname -m)}" +staging="${3:-dist}" + +archive="qpxtool-v${version}-macos-${arch}.dmg" +[ -n "$GITHUB_OUTPUT" ] && echo "archive=$archive" >> "$GITHUB_OUTPUT" +echo "mac dmg is <$archive>" + +BREW_PREFIX="$(brew --prefix)" +APP="qpxtool.app" +CONTENTS="$APP/Contents" + +rm -rf "$APP" dist-mac +mkdir -p "$CONTENTS/MacOS" "$CONTENTS/Resources" "$CONTENTS/libs" \ + "$CONTENTS/Resources/usr/lib/qpxtool" + +# --- main GUI binary ------------------------------------------------------- +cp "$staging/usr/bin/qpxtool" "$CONTENTS/MacOS/qpxtool" +chmod +x "$CONTENTS/MacOS/qpxtool" + +# Optional CLI helpers, bundled alongside the GUI. +for tool in qscan qscand cdvdcontrol readdvd pxfw f1tattoo; do + [ -f "$staging/usr/bin/$tool" ] && cp "$staging/usr/bin/$tool" "$CONTENTS/MacOS/$tool" +done + +# --- Info.plist ------------------------------------------------------------ +cat > "$CONTENTS/Info.plist" << EOF + + + + + CFBundleName QPxTool + CFBundleDisplayName QPxTool + CFBundleIdentifier org.qpxtool.QPxTool + CFBundleVersion ${version} + CFBundleShortVersionString${version} + CFBundleExecutable qpxtool + CFBundleIconFile qpxtool.icns + CFBundlePackageType APPL + NSHighResolutionCapable + LSMinimumSystemVersion 11.0 + + +EOF + +# --- application icon (best effort) ---------------------------------------- +if [ -f gui/qpxtool.png ]; then + iconset="$(mktemp -d)/qpxtool.iconset" + mkdir -p "$iconset" + for sz in 16 32 64 128 256 512; do + sips -z $sz $sz gui/qpxtool.png --out "$iconset/icon_${sz}x${sz}.png" >/dev/null 2>&1 || true + sips -z $((sz*2)) $((sz*2)) gui/qpxtool.png --out "$iconset/icon_${sz}x${sz}@2x.png" >/dev/null 2>&1 || true + done + iconutil -c icns "$iconset" -o "$CONTENTS/Resources/qpxtool.icns" 2>/dev/null || true +fi + +# --- bundle Qt frameworks & other Homebrew deps via macdeployqt ------------ +# macdeployqt rewrites the GUI binary's Qt references to @executable_path/../Frameworks. +macdeployqt "$APP" -verbose=1 -no-strip + +# --- relink qpxtool's own libraries & plugins ------------------------------ +# Any reference resolving to one of these prefixes is one we bundle ourselves. +staging_libdir="$(cd "$staging/usr/lib" && pwd -P)" + +# Rewrite every qpxtool/@rpath dependency of $1 to @executable_path/../libs, +# copying the referenced library into Contents/libs the first time we see it. +rewrite_deps() { + local target="$1" + local dep libname src + for dep in $(otool -L "$target" | tail -n +2 | awk '{print $1}'); do + case "$dep" in + */libqpx*.dylib|"$staging_libdir"/*|@rpath/libqpx*) + libname="$(basename "$dep")" + if [ ! -f "$CONTENTS/libs/$libname" ]; then + # Resolve the real file: the staging libdir holds the actual dylibs. + src="" + [ -f "$staging_libdir/$libname" ] && src="$staging_libdir/$libname" + [ -z "$src" ] && [ -f "$dep" ] && src="$dep" + if [ -n "$src" ]; then + echo "Bundling lib: $libname from $src" + cp "$src" "$CONTENTS/libs/$libname" + chmod +w "$CONTENTS/libs/$libname" + install_name_tool -id "@executable_path/../libs/$libname" "$CONTENTS/libs/$libname" + # Recurse so transitive qpxtool deps get bundled & rewritten too. + rewrite_deps "$CONTENTS/libs/$libname" + else + echo "WARNING: could not locate $libname (dep $dep of $target)" + continue + fi + fi + install_name_tool -change "$dep" "@executable_path/../libs/$libname" "$target" + ;; + esac + done +} + +rewrite_deps "$CONTENTS/MacOS/qpxtool" +for tool in qscan qscand cdvdcontrol readdvd pxfw f1tattoo; do + [ -f "$CONTENTS/MacOS/$tool" ] && rewrite_deps "$CONTENTS/MacOS/$tool" +done + +# Scan plugins go where the APPDIR-based loader looks ($APPDIR/usr/lib/qpxtool). +for plug in "$staging/usr/lib/qpxtool/"libqscan_*; do + [ -f "$plug" ] || continue + dst="$CONTENTS/Resources/usr/lib/qpxtool/$(basename "$plug")" + cp "$plug" "$dst" + chmod +w "$dst" + rewrite_deps "$dst" +done + +# --- locale & docs --------------------------------------------------------- +if [ -d "$staging/usr/share/qpxtool/locale" ]; then + mkdir -p "$CONTENTS/Resources/share/qpxtool/locale" + cp "$staging/usr/share/qpxtool/locale/"*.qm "$CONTENTS/Resources/share/qpxtool/locale/" 2>/dev/null || true +fi +mkdir -p "$CONTENTS/Resources/documentation" +cp COPYING README README.md ChangeLog AUTHORS "$CONTENTS/Resources/documentation/" 2>/dev/null || true + +# --- launcher -------------------------------------------------------------- +# Wrap the real binary so we can point the plugin loader at the bundled +# plugins via APPDIR (the same env var honoured by the AppImage build). +mv "$CONTENTS/MacOS/qpxtool" "$CONTENTS/MacOS/qpxtool.bin" +cat > "$CONTENTS/MacOS/qpxtool" << 'EOF' +#!/bin/bash +DIR="$(cd "$(dirname "$0")/../.." && pwd)" +# $APPDIR/usr/lib/qpxtool is searched by qpxtool's plugin loader. +export APPDIR="$DIR/Contents/Resources" +exec "$DIR/Contents/MacOS/qpxtool.bin" "$@" +EOF +chmod +x "$CONTENTS/MacOS/qpxtool" "$CONTENTS/MacOS/qpxtool.bin" + +# --- ad-hoc re-sign -------------------------------------------------------- +# install_name_tool invalidates signatures; on Apple Silicon unsigned code +# can't be loaded, so re-sign everything we touched (and finally the bundle). +find "$CONTENTS/libs" "$CONTENTS/Resources/usr/lib/qpxtool" -type f \ + \( -name '*.dylib' -o -name 'libqscan_*' \) -print0 2>/dev/null | + while IFS= read -r -d '' f; do codesign --force --sign - "$f"; done +codesign --force --sign - "$CONTENTS/MacOS/qpxtool.bin" +codesign --force --deep --sign - "$APP" || true + +# --- package as .dmg ------------------------------------------------------- +mkdir -p dist-mac +mv "$APP" dist-mac/ +create-dmg "$archive" dist-mac + +echo "dist done ($archive)" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76f5033..1c830c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -190,3 +190,103 @@ jobs: name: "${{ steps.tag.outputs.relname }}" tag_name: "${{ steps.tag.outputs.tagname }}" files: ${{ steps.dist.outputs.archive }} + + macos: + strategy: + fail-fast: false + matrix: + include: + - arch: arm64 + runs-on: macos-15 + - arch: x86_64 + runs-on: macos-15-intel + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - name: install prerequisites + env: + HOMEBREW_NO_INSTALL_CLEANUP: 1 + run: brew install qt@6 create-dmg + - name: configure + id: conf + env: + CFLAGS: -L/opt/homebrew/opt/qt@6/lib -I/opt/homebrew/opt/qt@6/include + CXXFLAGS: -L/opt/homebrew/opt/qt@6/lib -I/opt/homebrew/opt/qt@6/include + PKG_CONFIG_PATH: /opt/homebrew/opt/qt@6/lib/pkgconfig + run: | + git describe --dirty --tags --match 'v*' + version="$(git describe --dirty --tags --match 'v*')" + sed -i '' -re "s/^#define VERSION.*/#define VERSION \"${version:1}-speed47\"/" */*/version.h + echo "version=${version:1}" >> $GITHUB_OUTPUT + export PATH="$(brew --prefix qt@6)/bin:$PATH" + ./configure --prefix=/usr --libdir=/usr/lib + - name: make + run: | + export PATH="$(brew --prefix qt@6)/bin:$PATH" + make -j$(sysctl -n hw.ncpu || echo 2) + - name: make install (staging) + run: | + export PATH="$(brew --prefix qt@6)/bin:$PATH" + make install DESTDIR="$PWD/staging" + - name: check executable + run: | + file staging/usr/bin/qpxtool + file staging/usr/bin/qpxtool | grep -q 'Mach-O' + - name: prepare dist + id: dist + run: | + export PATH="$(brew --prefix qt@6)/bin:$PATH" + ./.github/workflows/make-mac-app.sh "${{ steps.conf.outputs.version }}" "${{ matrix.arch }}" "$PWD/staging" + - name: verify dmg mounts and app launches + run: | + set -x + hdiutil attach "${{ steps.dist.outputs.archive }}" + open "/Volumes/qpxtool-"*/qpxtool.app + sleep 5 + pgrep -f qpxtool.bin && echo "qpxtool is running" + - name: Tag if needed + id: tag + if: github.ref_type == 'branch' + run: | + case "$GITHUB_REF_NAME" in + dev) tagname=devel + git tag -f $tagname + git push -f origin $tagname + echo "draft=true" >> $GITHUB_OUTPUT + echo "relname=Dev Build" >> $GITHUB_OUTPUT + ;; + master) tagname=latest + git tag -f $tagname + git push -f origin $tagname + echo "draft=false" >> $GITHUB_OUTPUT + echo "relname=Continuous Build" >> $GITHUB_OUTPUT + ;; + v*) tagname=$GITHUB_REF_NAME + echo "draft=true" >> $GITHUB_OUTPUT + echo "relname=$GITHUB_REF_NAME" >> $GITHUB_OUTPUT + ;; + *) exit 1;; + esac + echo "prerelease=true" >> $GITHUB_OUTPUT + echo "tagname=$tagname" >> $GITHUB_OUTPUT + - name: Delete old assets + if: github.ref_type == 'branch' + uses: mknejp/delete-release-assets@v1 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + tag: "refs/tags/${{ steps.tag.outputs.tagname }}" + fail-if-no-assets: false + fail-if-no-release: false + assets: | + qpxtool-*-macos-${{ matrix.arch }}.dmg + - name: Upload new assets + uses: softprops/action-gh-release@v2 + with: + token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: "${{ steps.tag.outputs.prerelease }}" + draft: "${{ steps.tag.outputs.draft }}" + name: "${{ steps.tag.outputs.relname }}" + tag_name: "${{ steps.tag.outputs.tagname }}" + files: ${{ steps.dist.outputs.archive }} diff --git a/lib/qpxtransport/qpx_transport.cpp b/lib/qpxtransport/qpx_transport.cpp index ac5c7dd..a987f2b 100644 --- a/lib/qpxtransport/qpx_transport.cpp +++ b/lib/qpxtransport/qpx_transport.cpp @@ -943,7 +943,7 @@ int Scsi_Command::transport(Direction dir, void* buf, size_t sz) { SCSITaskInterface** cmd; SCSITaskStatus stat; UInt64 bytes; - IOVirtualRange range = {(IOVirtualAddress)buf, sz}; + IOVirtualRange range = {(IOVirtualAddress)buf, static_cast(sz)}; /* cmd length fix */ // printf("CMD: (%2d) %02x\n", cdblen, cdb[0]);