Release 0.2.6: make Android FFI verification NDK-tool aware #42
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release pb-mapper UI | |
| on: | |
| push: | |
| tags: | |
| - "v[0-9]+.[0-9]+.[0-9]+" | |
| workflow_dispatch: | |
| env: | |
| UI_VERSION: ${{ github.ref_name }} | |
| permissions: | |
| contents: write | |
| jobs: | |
| get-release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - name: Generate UI release notes from changelog | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| VERSION="${GITHUB_REF_NAME#v}" | |
| awk -v ver="${VERSION}" ' | |
| $0 ~ "^## \\[" ver "\\]" {capture=1} | |
| capture { | |
| if ($0 ~ "^## \\[" && $0 !~ "^## \\[" ver "\\]") exit | |
| } | |
| ' CHANGELOG.md > changelog_section.md | |
| { | |
| echo "# pb-mapper UI Release ${GITHUB_REF_NAME}" | |
| echo | |
| echo "Cross-platform graphical interface for pb-mapper network tunneling tool." | |
| echo | |
| echo "## Downloads" | |
| echo "- **Windows**: Download the .exe installer or .zip portable version" | |
| echo "- **Linux**: AppImage, DEB, or other Linux packages" | |
| echo "- **macOS**: DMG installer" | |
| echo "- **Android**: APK files" | |
| echo "- **iOS**: Unsigned IPA (for development)" | |
| echo | |
| echo "## Latest Changelog" | |
| echo | |
| if [ -s changelog_section.md ]; then | |
| cat changelog_section.md | |
| else | |
| echo "_No changelog section found for version ${VERSION} in CHANGELOG.md._" | |
| fi | |
| } > ui_release_notes.md | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ui-release-notes | |
| path: ui_release_notes.md | |
| build-windows: | |
| runs-on: windows-latest | |
| needs: [get-release] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: ui-release-notes | |
| - name: Install make | |
| run: choco install make | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: 3.35.1 | |
| channel: "stable" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.88.0 | |
| - name: Build latest Windows FFI | |
| run: | | |
| make build-pb-mapper-ffi-windows | |
| - name: Build Windows app with latest FFI | |
| run: | | |
| make release-ui-windows-no-ffi | |
| - name: Verify Windows bundle uses latest FFI | |
| run: | | |
| $src = "ui/native/windows/x64/pb_mapper_ffi.dll" | |
| $dst = "ui/build/windows/x64/runner/Release/pb_mapper_ffi.dll" | |
| if (!(Test-Path $src)) { throw "FFI source dll not found: $src" } | |
| if (!(Test-Path $dst)) { throw "FFI dll not found in built UI bundle: $dst" } | |
| $srcHash = (Get-FileHash -Algorithm SHA256 $src).Hash | |
| $dstHash = (Get-FileHash -Algorithm SHA256 $dst).Hash | |
| if ($srcHash -ne $dstHash) { | |
| throw "FFI hash mismatch. source=$srcHash bundle=$dstHash" | |
| } | |
| - name: Upload Windows artifacts | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.ref_name }}-ui | |
| target_commitish: ${{ github.sha }} | |
| name: "pb-mapper UI ${{ github.ref_name }}" | |
| draft: ${{ github.event_name == 'workflow_dispatch' }} | |
| body_path: ui_release_notes.md | |
| files: ui/dist/*/* | |
| build-linux: | |
| runs-on: ubuntu-22.04 | |
| needs: [get-release] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: ui-release-notes | |
| - name: Set up Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: 3.35.1 | |
| channel: "stable" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.88.0 | |
| - name: Install dependencies | |
| run: | | |
| sudo apt-get update -y | |
| sudo apt-get install -y locate ninja-build libgtk-3-dev libayatana-appindicator3-dev libnotify-dev \ | |
| binutils coreutils desktop-file-utils fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-setuptools squashfs-tools strace util-linux zsync | |
| - name: Build latest Linux FFI | |
| run: | | |
| make build-pb-mapper-ffi-linux | |
| - name: Build Linux app with latest FFI | |
| run: | | |
| make build-ui-linux-release-no-ffi | |
| - name: Verify Linux bundle uses latest FFI | |
| run: | | |
| SRC_FFI="ui/native/linux/x64/libpb_mapper_ffi.so" | |
| DST_FFI="ui/build/linux/x64/release/bundle/lib/libpb_mapper_ffi.so" | |
| test -f "${SRC_FFI}" | |
| test -f "${DST_FFI}" | |
| SRC_HASH="$(sha256sum "${SRC_FFI}" | awk '{print $1}')" | |
| DST_HASH="$(sha256sum "${DST_FFI}" | awk '{print $1}')" | |
| if [ "${SRC_HASH}" != "${DST_HASH}" ]; then | |
| echo "FFI hash mismatch: src=${SRC_HASH} dst=${DST_HASH}" | |
| exit 1 | |
| fi | |
| - name: Package Linux artifacts | |
| run: | | |
| cd ui | |
| # Create directory structure for packaging | |
| mkdir -p dist/${UI_VERSION} | |
| # Create ZIP package | |
| cd build/linux/x64/release/bundle | |
| zip -r ../../../../../dist/${UI_VERSION}/pb-mapper-linux.zip . | |
| # Create AppImage (basic) | |
| cd ../../../../../ | |
| mkdir -p AppDir/usr/bin | |
| mkdir -p AppDir/usr/share/applications | |
| mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps | |
| # Copy binary and dependencies | |
| cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/ | |
| # Copy icon | |
| cp assets/app_icon.png AppDir/usr/share/icons/hicolor/256x256/apps/pb-mapper.png | |
| cp AppDir/usr/share/icons/hicolor/256x256/apps/pb-mapper.png AppDir/pb-mapper.png | |
| # Create desktop file | |
| cat > AppDir/usr/share/applications/pb-mapper.desktop << EOF | |
| [Desktop Entry] | |
| Name=pb-mapper | |
| Comment=Network port mapping and tunneling tool | |
| Exec=ui | |
| Icon=pb-mapper | |
| Terminal=false | |
| Type=Application | |
| Categories=Network; | |
| EOF | |
| # Copy desktop file to root | |
| cp AppDir/usr/share/applications/pb-mapper.desktop AppDir/ | |
| # Create AppRun | |
| cat > AppDir/AppRun << 'EOF' | |
| #!/bin/bash | |
| HERE="$(dirname "$(readlink -f "${0}")")" | |
| export LD_LIBRARY_PATH="${HERE}/usr/bin/lib:${LD_LIBRARY_PATH}" | |
| exec "${HERE}/usr/bin/ui" "$@" | |
| EOF | |
| chmod +x AppDir/AppRun | |
| # Build AppImage | |
| wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage | |
| chmod +x appimagetool-x86_64.AppImage | |
| ./appimagetool-x86_64.AppImage AppDir dist/${UI_VERSION}/pb-mapper-linux.AppImage | |
| - name: Upload Linux artifacts | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.ref_name }}-ui | |
| target_commitish: ${{ github.sha }} | |
| draft: ${{ github.event_name == 'workflow_dispatch' }} | |
| body_path: ui_release_notes.md | |
| files: ui/dist/${{ env.UI_VERSION }}/* | |
| build-android: | |
| runs-on: ubuntu-latest | |
| needs: [get-release] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: ui-release-notes | |
| - uses: actions/setup-java@v3 | |
| with: | |
| distribution: "liberica" | |
| java-version: "17" | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: 3.35.1 | |
| channel: "stable" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.88.0 | |
| - name: Set up Android NDK | |
| uses: nttld/setup-ndk@v1 | |
| with: | |
| ndk-version: r26d | |
| link-to-sdk: true | |
| - name: Build latest Android FFI | |
| run: | | |
| make build-pb-mapper-ffi-android | |
| - name: Build Android APKs with latest FFI | |
| run: | | |
| make build-ui-android-release-no-ffi | |
| - name: Verify Android APKs use latest FFI | |
| run: | | |
| set -euo pipefail | |
| tmp_dir="$(mktemp -d)" | |
| trap 'rm -rf "${tmp_dir}"' EXIT | |
| ndk_bin="" | |
| for ndk_var in ANDROID_NDK_HOME ANDROID_NDK_ROOT ANDROID_NDK_LATEST_HOME ANDROID_NDK; do | |
| ndk_home="${!ndk_var:-}" | |
| if [ -n "${ndk_home}" ] && [ -d "${ndk_home}/toolchains/llvm/prebuilt/linux-x86_64/bin" ]; then | |
| ndk_bin="${ndk_home}/toolchains/llvm/prebuilt/linux-x86_64/bin" | |
| break | |
| fi | |
| done | |
| readelf_bin="" | |
| strip_bin="" | |
| nm_bin="" | |
| if [ -n "${ndk_bin}" ]; then | |
| [ -x "${ndk_bin}/llvm-readelf" ] && readelf_bin="${ndk_bin}/llvm-readelf" | |
| [ -x "${ndk_bin}/llvm-strip" ] && strip_bin="${ndk_bin}/llvm-strip" | |
| [ -x "${ndk_bin}/llvm-nm" ] && nm_bin="${ndk_bin}/llvm-nm" | |
| fi | |
| if [ -z "${readelf_bin}" ] && command -v readelf >/dev/null 2>&1; then | |
| readelf_bin="$(command -v readelf)" | |
| fi | |
| if [ -z "${strip_bin}" ] && command -v llvm-strip >/dev/null 2>&1; then | |
| strip_bin="$(command -v llvm-strip)" | |
| fi | |
| if [ -z "${strip_bin}" ] && command -v objcopy >/dev/null 2>&1; then | |
| strip_bin="$(command -v objcopy)" | |
| fi | |
| if [ -z "${nm_bin}" ] && command -v nm >/dev/null 2>&1; then | |
| nm_bin="$(command -v nm)" | |
| fi | |
| echo "Android ELF tools: readelf=${readelf_bin:-none} strip=${strip_bin:-none} nm=${nm_bin:-none}" | |
| declare -A APK_BY_ABI=( | |
| ["arm64-v8a"]="app-arm64-v8a-release.apk" | |
| ["armeabi-v7a"]="app-armeabi-v7a-release.apk" | |
| ["x86_64"]="app-x86_64-release.apk" | |
| ) | |
| for abi in "${!APK_BY_ABI[@]}"; do | |
| src="ui/native/android/${abi}/libpb_mapper_ffi.so" | |
| apk="ui/build/app/outputs/flutter-apk/${APK_BY_ABI[$abi]}" | |
| extracted="${tmp_dir}/${abi}.apk.so" | |
| test -f "${src}" | |
| test -f "${apk}" | |
| unzip -p "${apk}" "lib/${abi}/libpb_mapper_ffi.so" > "${extracted}" | |
| test -s "${extracted}" | |
| src_hash="$(sha256sum "${src}" | awk '{print $1}')" | |
| apk_hash="$(sha256sum "${extracted}" | awk '{print $1}')" | |
| if [ "${src_hash}" = "${apk_hash}" ]; then | |
| continue | |
| fi | |
| src_build_id="" | |
| apk_build_id="" | |
| if [ -n "${readelf_bin}" ]; then | |
| src_build_id="$("${readelf_bin}" -n "${src}" 2>/dev/null | awk '/Build ID:/ {print $3; exit}' || true)" | |
| apk_build_id="$("${readelf_bin}" -n "${extracted}" 2>/dev/null | awk '/Build ID:/ {print $3; exit}' || true)" | |
| fi | |
| if [ -n "${src_build_id}" ] && [ "${src_build_id}" = "${apk_build_id}" ]; then | |
| echo "FFI raw hash mismatch for ${abi}, but ELF Build ID matches (${src_build_id}); accepting packaged artifact." | |
| continue | |
| fi | |
| src_norm="${tmp_dir}/${abi}.src.norm.so" | |
| apk_norm="${tmp_dir}/${abi}.apk.norm.so" | |
| cp "${src}" "${src_norm}" | |
| cp "${extracted}" "${apk_norm}" | |
| if [ -n "${strip_bin}" ]; then | |
| "${strip_bin}" --strip-debug "${src_norm}" >/dev/null 2>&1 || true | |
| "${strip_bin}" --strip-debug "${apk_norm}" >/dev/null 2>&1 || true | |
| fi | |
| src_norm_hash="$(sha256sum "${src_norm}" | awk '{print $1}')" | |
| apk_norm_hash="$(sha256sum "${apk_norm}" | awk '{print $1}')" | |
| if [ "${src_norm_hash}" = "${apk_norm_hash}" ]; then | |
| echo "FFI raw hash mismatch for ${abi}, but debug-stripped hashes match; accepting packaged artifact." | |
| continue | |
| fi | |
| src_symbols="" | |
| apk_symbols="" | |
| src_sym_hash="" | |
| apk_sym_hash="" | |
| if [ -n "${nm_bin}" ]; then | |
| src_symbols="$("${nm_bin}" -D --defined-only "${src}" 2>/dev/null | awk '{print $3}' | sort -u || true)" | |
| apk_symbols="$("${nm_bin}" -D --defined-only "${extracted}" 2>/dev/null | awk '{print $3}' | sort -u || true)" | |
| fi | |
| if [ -n "${src_symbols}" ] && [ -n "${apk_symbols}" ]; then | |
| src_sym_hash="$(printf "%s\n" "${src_symbols}" | sha256sum | awk '{print $1}')" | |
| apk_sym_hash="$(printf "%s\n" "${apk_symbols}" | sha256sum | awk '{print $1}')" | |
| fi | |
| if [ -n "${src_sym_hash}" ] && [ "${src_sym_hash}" = "${apk_sym_hash}" ]; then | |
| echo "FFI raw hash mismatch for ${abi}, but exported symbol hashes match; accepting packaged artifact." | |
| continue | |
| fi | |
| echo "FFI mismatch for ${abi}: src_hash=${src_hash} apk_hash=${apk_hash} src_build_id=${src_build_id:-none} apk_build_id=${apk_build_id:-none} src_norm_hash=${src_norm_hash} apk_norm_hash=${apk_norm_hash} src_sym_hash=${src_sym_hash:-none} apk_sym_hash=${apk_sym_hash:-none}" | |
| exit 1 | |
| done | |
| - name: Rename Android APKs | |
| run: | | |
| cd ui/build/app/outputs/flutter-apk | |
| mv app-arm64-v8a-release.apk pb-mapper-arm64-v8a.apk | |
| mv app-armeabi-v7a-release.apk pb-mapper-armeabi-v7a.apk | |
| mv app-x86_64-release.apk pb-mapper-x86_64.apk | |
| - name: Upload Android artifacts | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.ref_name }}-ui | |
| target_commitish: ${{ github.sha }} | |
| draft: ${{ github.event_name == 'workflow_dispatch' }} | |
| body_path: ui_release_notes.md | |
| files: | | |
| ui/build/app/outputs/flutter-apk/pb-mapper-arm64-v8a.apk | |
| ui/build/app/outputs/flutter-apk/pb-mapper-armeabi-v7a.apk | |
| ui/build/app/outputs/flutter-apk/pb-mapper-x86_64.apk | |
| build-macos: | |
| runs-on: macos-latest | |
| needs: [get-release] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: ui-release-notes | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: 3.35.1 | |
| channel: "stable" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.88.0 | |
| - name: Install appdmg | |
| run: | | |
| npm install -g appdmg | |
| - name: Build latest macOS FFI | |
| run: | | |
| make build-pb-mapper-ffi-macos | |
| - name: Build macOS app with latest FFI | |
| run: | | |
| make build-ui-macos-release-no-ffi | |
| - name: Verify macOS app bundle uses latest FFI | |
| run: | | |
| SRC_FFI="ui/native/macos/libpb_mapper_ffi.dylib" | |
| DST_FFI="$(find ui/build/macos/Build/Products/Release -type f -name libpb_mapper_ffi.dylib | head -n1)" | |
| test -f "${SRC_FFI}" | |
| test -n "${DST_FFI}" | |
| test -f "${DST_FFI}" | |
| SRC_HASH="$(shasum -a 256 "${SRC_FFI}" | awk '{print $1}')" | |
| DST_HASH="$(shasum -a 256 "${DST_FFI}" | awk '{print $1}')" | |
| if [ "${SRC_HASH}" != "${DST_HASH}" ]; then | |
| echo "FFI hash mismatch: src=${SRC_HASH} dst=${DST_HASH}" | |
| exit 1 | |
| fi | |
| - name: Package macOS artifacts | |
| run: | | |
| cd ui | |
| PROJECT_DIR=$(pwd) | |
| cd build/macos/Build/Products/Release | |
| cat>appdmg.json<<EOF | |
| { | |
| "title": "pb-mapper", | |
| "icon": "pb-mapper.app/Contents/Resources/AppIcon.icns", | |
| "contents": [ | |
| { "x": 448, "y": 344, "type": "link", "path": "/Applications" }, | |
| { "x": 192, "y": 344, "type": "file", "path": "pb-mapper.app" } | |
| ] | |
| } | |
| EOF | |
| mkdir dist | |
| echo "DMG_ASSET=ui/build/macos/Build/Products/Release/dist/pb-mapper-macos.dmg" >> $GITHUB_ENV | |
| appdmg appdmg.json dist/pb-mapper-macos.dmg | |
| - name: Upload macOS artifacts | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.ref_name }}-ui | |
| target_commitish: ${{ github.sha }} | |
| draft: ${{ github.event_name == 'workflow_dispatch' }} | |
| body_path: ui_release_notes.md | |
| files: | | |
| ${{env.DMG_ASSET}} | |
| build-ios: | |
| runs-on: macos-latest | |
| needs: [get-release] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: ui-release-notes | |
| - uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: 3.35.1 | |
| channel: "stable" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.88.0 | |
| - name: Build latest iOS FFI | |
| run: | | |
| make build-pb-mapper-ffi-ios | |
| - name: Build iOS app with latest FFI (no-codesign) | |
| run: | | |
| make build-ui-ios-release-no-ffi | |
| cd ui | |
| cd build/ios/iphoneos | |
| mkdir Payload | |
| mv Runner.app Payload/ | |
| zip -r pb-mapper-ios-unsigned.ipa Payload/ | |
| - name: Upload iOS artifacts | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ github.ref_name }}-ui | |
| target_commitish: ${{ github.sha }} | |
| draft: ${{ github.event_name == 'workflow_dispatch' }} | |
| body_path: ui_release_notes.md | |
| files: | | |
| ui/build/ios/iphoneos/pb-mapper-ios-unsigned.ipa |