name: ci/gh-actions/build # Legacy builds are manual only. Release artifacts are built in Builds/wowlet. on: [workflow_dispatch] # NOTE (wownero-rebase branch): the Arch + macOS jobs from upstream Feather's build.yml are # temporarily disabled to get a fast, cheap iteration loop while bringing up the build against the # wownero monero submodule (gui branch). Restore the full matrix from upstream once Ubuntu is green. jobs: build-ubuntu-without-scanner: name: "Ubuntu 24.04 (no scanner)" runs-on: ubuntu-latest container: image: ubuntu:24.04 steps: - name: update apt run: apt update - name: install dependencies run: apt -y install curl bzip2 git cmake build-essential ccache libssl-dev libunbound-dev libboost-all-dev libzmq3-dev libqrencode-dev qt6-base-dev qt6-svg-dev qt6-websockets-dev qt6-multimedia-dev qt6-wayland-dev qt6-declarative-dev libzip-dev libsodium-dev libgcrypt20-dev libx11-xcb-dev qml6-module-qtquick qml6-module-qtquick-layouts qml6-module-qtquick-window qml6-module-qtqml-workerscript qml6-module-qtqml-models protobuf-compiler libprotobuf-dev libhidapi-dev libzxing-dev libusb-dev libusb-1.0-0-dev patchelf - name: configure git run: git config --global --add safe.directory '*' - uses: actions/checkout@v4 with: submodules: recursive - name: fetch wownerod to embed (single-file build) run: | mkdir -p /tmp/wownerod-embed curl -L -o /tmp/wownero.tar.bz2 \ https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/wownero-x86_64-linux-gnu-v0.11.4.0.tar.bz2 tar xjf /tmp/wownero.tar.bz2 -C /tmp find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \; chmod +x /tmp/wownerod-embed/wownerod ls -la /tmp/wownerod-embed - name: fetch tor to embed run: | mkdir -p /tmp/tor-embed curl -L -o /tmp/teb.tgz \ https://archive.torproject.org/tor-package-archive/torbrowser/15.0/tor-expert-bundle-linux-x86_64-15.0.tar.gz mkdir -p /tmp/teb && tar xzf /tmp/teb.tgz -C /tmp/teb cp /tmp/teb/tor/tor /tmp/teb/tor/libssl.so.3 /tmp/teb/tor/libevent-2.1.so.7 /tmp/teb/tor/libcrypto.so.3 /tmp/tor-embed/ # expert-bundle tor has no rpath; point it (+ its libs) at $ORIGIN so the embedded copy finds # its libs in the extract dir on any distro for f in tor libssl.so.3 libevent-2.1.so.7 libcrypto.so.3; do patchelf --set-rpath '$ORIGIN' /tmp/tor-embed/$f; done chmod +x /tmp/tor-embed/tor ls -la /tmp/tor-embed - name: build run: | mkdir build cd build # ARCH=x86-64: portable generic baseline. NEVER 'native' for a release — the CI runner's # CPU (often newer than users') would bake in unsupported instructions → SIGILL on launch. # wowlet: TOR_VERSION must EXACTLY match `tor --version` of the embedded binary, else # TorManager::unpackBins() sees embedded > the unpacked copy and rewrites Tor every launch. # The tor-expert-bundle's RELEASE number (15.0) is NOT its tor version (0.4.8.x) — derive it. TORVER=$(/tmp/tor-embed/tor --version | grep -oE '[0-9]+(\.[0-9]+){3}' | head -1) echo "embedded tor version: $TORVER" cmake -DWITH_SCANNER=OFF -DARCH=x86-64 -DWOWNEROD_DIR=/tmp/wownerod-embed -DTOR_DIR=/tmp/tor-embed -DTOR_VERSION="$TORVER" .. cmake --build . -j $(nproc) - name: package AppImage run: | mkdir -p AppDir/usr/bin AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/256x256/apps cp build/bin/feather AppDir/usr/bin/wowlet chmod +x AppDir/usr/bin/wowlet cp src/assets/wowlet.desktop AppDir/usr/share/applications/wowlet.desktop cp src/assets/images/appicons/256x256.png AppDir/usr/share/icons/hicolor/256x256/apps/wowlet.png curl -sL -o linuxdeploy https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage curl -sL -o linuxdeploy-plugin-qt https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage chmod +x linuxdeploy linuxdeploy-plugin-qt export APPIMAGE_EXTRACT_AND_RUN=1 export QMAKE="$(command -v qmake6 || echo /usr/bin/qmake6)" export EXTRA_QT_PLUGINS="svg;multimedia;tls" export QML_SOURCES_PATHS="$PWD/src/assets/qml" export PATH="$PWD:$PATH" # Populate AppDir (libs + Qt plugins + QML modules) — no --output yet, so we can patch QML first. ./linuxdeploy --appdir AppDir --plugin qt \ -d AppDir/usr/share/applications/wowlet.desktop \ -i AppDir/usr/share/icons/hicolor/256x256/apps/wowlet.png # --------------------------------------------------------------------------------------- # Make the QML bundle COMPLETE and deterministic (no whack-a-mole). # # qmlimportscanner only reads `import` lines in our source QML; it does NOT follow the # `depends`/`default import`/singleton chains inside a Qt module's own qmldir. So transitive # modules (QtQuick -> QtQml.WorkerScript/Models, etc.) get silently skipped. Rather than # enumerate them one by one, blanket-copy the whole QtQuick + QtQml module TREES, then # resolve each copied plugin .so's linked Qt libs into usr/lib ourselves (linuxdeploy only # does that for modules IT deployed, not for ones we copy manually). # --------------------------------------------------------------------------------------- QMLDIR=$(qmake6 -query QT_INSTALL_QML) mkdir -p AppDir/usr/qml AppDir/usr/lib # Blanket-copy the full module trees we depend on (QtQuick incl. Layouts/Window, QtQml incl. # WorkerScript/Models). -n = don't clobber what linuxdeploy already (correctly) deployed. for m in QtQuick QtQml; do cp -rn "$QMLDIR/$m" AppDir/usr/qml/ 2>/dev/null || true done # Resolve the DT_NEEDED Qt6 libs of EVERY bundled QML plugin .so into usr/lib. This closes # the dlopen gap for the trees we copied by hand (libQt6Quick, libQt6Qml, libQt6QmlModels, # libQt6QmlWorkerScript, libQt6QuickLayouts, etc.). -L follows symlinks, -n won't clobber. find AppDir/usr/qml -name '*.so' -print0 | while IFS= read -r -d '' so; do ldd "$so" 2>/dev/null | awk '/=> \//{print $3}' \ | grep -E '/(libQt6|libicu|libpcre2|libdouble-conversion|libzstd|libb2|libpng|libharfbuzz|libmd4c|libglib|libgraphite)' \ | while read -r lib; do cp -L -n "$lib" AppDir/usr/lib/ 2>/dev/null || true; done done # Sanity: linuxdeploy-plugin-qt writes AppDir/usr/bin/qt.conf with `Imports=qml`/`Qml2Imports=qml` # (Prefix=../), which makes usr/qml the relocatable runtime import path. Confirm it's there. echo "=== qt.conf ===" && cat AppDir/usr/bin/qt.conf 2>/dev/null || echo "WARNING: no qt.conf written by linuxdeploy-plugin-qt" echo "=== bundled QtQuick/QtQml submodules ===" && (ls AppDir/usr/qml/QtQuick AppDir/usr/qml/QtQml 2>/dev/null | sed 's/^/ /') # Guard: mining.qml must NOT pull in QtQuick.Controls/Templates (we removed that import). # If a future edit re-adds a Controls type, this fails loudly instead of shipping a white tab. if grep -q 'import QtQuick.Controls' src/assets/qml/mining.qml; then echo "ERROR: mining.qml imports QtQuick.Controls again — bundle QtQuick/Templates+Controls trees or remove the import." >&2 exit 1 fi # Package (linuxdeploy already wrote AppRun + .desktop + .DirIcon into AppDir root). curl -sL -o appimagetool https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x appimagetool ARCH=x86_64 ./appimagetool AppDir wowlet-x86_64.AppImage ls -la wowlet-x86_64.AppImage - name: upload wowlet AppImage uses: actions/upload-artifact@v4 with: name: wowlet-x86_64-linux-AppImage path: wowlet-x86_64.AppImage if-no-files-found: error build-windows-mingw: name: "Windows x86_64 (mingw cross, depends)" runs-on: ubuntu-latest env: HOST: x86_64-w64-mingw32 steps: - name: free disk space + add swap # depends unpacks + builds Qt6/boost/openssl from source. Free / of GitHub bloat # (dotnet/android/ghc/CodeQL) AND add swap — 16G RAM alone OOMs the parallel Qt6 compile. run: | echo "disk before:"; df -h / | tail -1 sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup /usr/local/lib/android \ /opt/hostedtoolcache/CodeQL /usr/share/swift 2>/dev/null || true sudo apt-get clean echo "disk after:"; df -h / | tail -1 sudo fallocate -l 10G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile echo "mem:"; free -h - name: update apt run: sudo apt-get update - name: install dependencies # depends cross-build deps: the mingw posix cross-compiler + native build tools depends needs. # NO host Qt6 here (unlike the AppImage job) — depends builds a STATIC Qt6 for mingw itself. run: | sudo apt-get -y install curl ca-certificates bzip2 xz-utils unzip zip git cmake build-essential ccache \ python3 perl pkg-config g++-mingw-w64-x86-64-posix gcc-mingw-w64-x86-64-posix \ autoconf automake libtool gettext bison - name: select posix mingw alternatives # mingw32.mk wants the -posix threading variant (std::thread/mutex/cv need it). run: | sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix - name: configure git run: git config --global --add safe.directory '*' - uses: actions/checkout@v4 with: submodules: recursive # depends is the slow part (static Qt6/boost/openssl/tor from source). Cache the source tarballs # and the compiled-package cache so only the first run pays the ~1h cost; warm runs are minutes. - name: restore depends sources cache id: cache-sources uses: actions/cache/restore@v4 with: path: contrib/depends/sources key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }} restore-keys: | depends-sources- - name: restore depends built cache (mingw) id: cache-built uses: actions/cache/restore@v4 with: path: contrib/depends/built key: depends-built-mingw32-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }} restore-keys: | depends-built-mingw32- - name: build depends (static Qt6 + tor + boost + openssl for mingw) # -j2 not -j(nproc): the parallel static Qt6 compile OOMs the 16G runner at -j4. Slower but safe. run: make -C contrib/depends HOST="$HOST" -j2 # Save depends with if:always() so the expensive toolchain banks even when the LATER wowlet.exe # build fails — actions/cache's auto post-save is skipped on job failure, which kept re-paying ~2.5h. - name: save depends sources cache if: always() && steps.cache-sources.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: contrib/depends/sources key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }} - name: save depends built cache (mingw) if: always() && steps.cache-built.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: contrib/depends/built key: depends-built-mingw32-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }} - name: fetch wownerod.exe to embed run: | mkdir -p /tmp/wownerod-embed curl -L -o /tmp/wownero-win.zip \ https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/wownero-x86_64-w64-mingw32-v0.11.4.0.zip # -j flattens the versioned subdir so wownerod.exe is a DIRECT child (WownerodQrcGenerator # globs ${WOWNEROD_DIR}/* non-recursively). unzip -j /tmp/wownero-win.zip 'wownero-x86_64-w64-mingw32-v0.11.4.0/wownerod.exe' -d /tmp/wownerod-embed ls -la /tmp/wownerod-embed - name: build wowlet.exe run: | mkdir build && cd build # ARCH=x86-64: portable baseline. wownero's CMake defaults ARCH=native, which under a cross # compiler bakes the CI runner's instructions into the Windows binary -> SIGILL on older CPUs. cmake --toolchain "$GITHUB_WORKSPACE/contrib/depends/$HOST/share/toolchain.cmake" \ -DARCH=x86-64 \ -DWITH_SCANNER=OFF \ -DCHECK_UPDATES=OFF \ -DTOR_INSTALLED=On \ -DFEATHER_TARGET_TRIPLET="$HOST" \ -DWOWNEROD_DIR=/tmp/wownerod-embed \ .. cmake --build . -j2 ls -la bin/ - name: package zip # Fully static build: just wowlet.exe + the depends-built tor.exe + license. No Qt/runtime DLLs. run: | DISTDIR=wowlet-x86_64-w64-mingw32 mkdir -p "$DISTDIR/tor" cp build/bin/feather.exe "$DISTDIR/wowlet.exe" cp "contrib/depends/$HOST/Tor/tor.exe" "$DISTDIR/tor/tor.exe" (cp LICENSE "$DISTDIR/LICENSE.txt" 2>/dev/null || cp COPYING "$DISTDIR/LICENSE.txt" 2>/dev/null) || true (cd "$DISTDIR" && find . -type f | sort | zip -X "../wowlet-x86_64-windows.zip" -@) unzip -l wowlet-x86_64-windows.zip - name: upload wowlet Windows zip uses: actions/upload-artifact@v4 with: name: wowlet-x86_64-windows path: wowlet-x86_64-windows.zip if-no-files-found: error build-linux-cross: name: "Linux ${{ matrix.arch }} (depends AppImage)" runs-on: ubuntu-latest continue-on-error: true # arm64/riscv64 deferred to v5.0.1; must not fail the release run strategy: fail-fast: false matrix: include: - arch: arm64 host: aarch64-linux-gnu xcompiler: g++-aarch64-linux-gnu gcc-aarch64-linux-gnu cmake_arch: armv8-a wownerod: wownero-aarch64-linux-gnu-v0.11.4.0.tar.bz2 - arch: riscv64 host: riscv64-linux-gnu xcompiler: g++-riscv64-linux-gnu gcc-riscv64-linux-gnu cmake_arch: rv64gc wownerod: wownero-riscv64-linux-gnu-v0.11.4.0.tar.bz2 env: HOST: ${{ matrix.host }} steps: - name: free disk space + add swap run: | sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/.ghcup /usr/local/lib/android \ /opt/hostedtoolcache/CodeQL /usr/share/swift 2>/dev/null || true sudo apt-get clean sudo fallocate -l 10G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile df -h / | tail -1; free -h | head -2 - name: install dependencies # depends builds static Qt6/tor/boost/openssl for the target; we supply the linux cross-compiler # ($(host)-gcc) + squashfs-tools (mksquashfs) for the AppImage. run: | sudo apt-get update sudo apt-get -y install curl ca-certificates bzip2 xz-utils unzip git cmake build-essential ccache \ python3 perl pkg-config gperf squashfs-tools meson ninja-build ${{ matrix.xcompiler }} \ autoconf automake libtool gettext bison - name: configure git run: git config --global --add safe.directory '*' - uses: actions/checkout@v4 with: submodules: recursive - name: restore depends sources cache id: cache-sources uses: actions/cache/restore@v4 with: path: contrib/depends/sources key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }} restore-keys: | depends-sources- - name: restore depends built cache id: cache-built uses: actions/cache/restore@v4 with: path: contrib/depends/built key: depends-built-${{ matrix.host }}-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }} restore-keys: | depends-built-${{ matrix.host }}- - name: build depends (static Qt6 + tor + boost + openssl) run: make -C contrib/depends HOST="$HOST" -j2 - name: save depends sources cache if: always() && steps.cache-sources.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: contrib/depends/sources key: depends-sources-${{ hashFiles('contrib/depends/packages/*') }} - name: save depends built cache if: always() && steps.cache-built.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: contrib/depends/built key: depends-built-${{ matrix.host }}-${{ hashFiles('contrib/depends/packages/*', 'contrib/depends/hosts/*', 'contrib/depends/toolchain.cmake.in') }} - name: fetch wownerod to embed run: | mkdir -p /tmp/wownerod-embed curl -L -o /tmp/wownero.tar.bz2 \ https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/${{ matrix.wownerod }} tar xjf /tmp/wownero.tar.bz2 -C /tmp find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \; chmod +x /tmp/wownerod-embed/wownerod ls -la /tmp/wownerod-embed - name: build wowlet run: | mkdir build && cd build # ARCH pinned (NOT wownero's native default — a cross compiler would bake the wrong ISA -> SIGILL). cmake --toolchain "$GITHUB_WORKSPACE/contrib/depends/$HOST/share/toolchain.cmake" \ -DARCH=${{ matrix.cmake_arch }} \ -DWITH_SCANNER=OFF \ -DCHECK_UPDATES=OFF \ -DFEATHER_TARGET_TRIPLET="$HOST" \ -DTOR_DIR="$GITHUB_WORKSPACE/contrib/depends/$HOST/Tor" \ -DTOR_VERSION=0.4.9.6 \ -DWOWNEROD_DIR=/tmp/wownerod-embed \ .. cmake --build . -j2 ls -la bin/ - name: package AppImage # build-appimage.sh wraps build/bin/feather with the depends-built ${HOST}/runtime (cross-arch). run: | bash contrib/AppImage/build-appimage.sh mv feather.AppImage "wowlet-${{ matrix.arch }}.AppImage" ls -la "wowlet-${{ matrix.arch }}.AppImage" - name: upload wowlet AppImage uses: actions/upload-artifact@v4 with: name: wowlet-${{ matrix.arch }}-linux-AppImage path: wowlet-${{ matrix.arch }}.AppImage if-no-files-found: error