fix(secretstore): fix silent failures when libsecret can't unlock #131
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
| # Copyright (C) 2021-2026 Saturneric <eric@bktus.com> | |
| # | |
| # This file is part of GpgFrontend. | |
| # | |
| # GpgFrontend is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # GpgFrontend is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. | |
| # | |
| # The initial version of the source code is inherited from | |
| # the gpg4usb project, which is under GPL-3.0-or-later. | |
| # | |
| # All the source code of GpgFrontend was modified and released by | |
| # Saturneric <eric@bktus.com> starting on May 12, 2021. | |
| # | |
| # SPDX-License-Identifier: GPL-3.0-or-later | |
| name: Build | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*" | |
| paths-ignore: | |
| - "resource/lfs/locale/**" | |
| - "**.md" | |
| pull_request: | |
| branches: | |
| - main | |
| paths-ignore: | |
| - "resource/lfs/locale/**" | |
| - "**.md" | |
| workflow_dispatch: | |
| inputs: | |
| build_mode: | |
| description: "Build mode" | |
| required: true | |
| default: "nightly" | |
| type: choice | |
| options: | |
| - nightly | |
| - release | |
| env: | |
| BUILD_TYPE: RelWithDebInfo | |
| GNUPG_VERSION: "2.5.20" | |
| jobs: | |
| build: | |
| strategy: | |
| matrix: | |
| os: | |
| [ | |
| "ubuntu-22.04", | |
| "ubuntu-24.04-arm", | |
| "macos-15-intel", | |
| "macos-15", | |
| "macos-26-intel", | |
| "macos-26", | |
| "windows-2022", | |
| ] | |
| # Portable vs installed is a compile-time decision | |
| # (GPGFRONTEND_BUILD_PORTABLE decides where the profile, and with it the | |
| # user's keys, lives), so each flavour needs its own configure + build. | |
| # They run as separate matrix jobs on purpose: the generated build | |
| # headers land in the source tree, so two flavours cannot share one | |
| # checkout. | |
| flavor: ["installed", "portable"] | |
| exclude: | |
| # macOS ships as a signed, notarized .app bundle installed to | |
| # /Applications; a portable bundle has no meaning there (and the App | |
| # Store / sandbox variants forbid it outright). | |
| - os: "macos-15-intel" | |
| flavor: "portable" | |
| - os: "macos-15" | |
| flavor: "portable" | |
| - os: "macos-26-intel" | |
| flavor: "portable" | |
| - os: "macos-26" | |
| flavor: "portable" | |
| runs-on: ${{ matrix.os }} | |
| continue-on-error: true | |
| steps: | |
| - name: Set git to use LF(Windows) or CRLF(MacOS) line endings | |
| run: | | |
| git config --global core.autocrlf false | |
| git config --global core.eol lf | |
| if: runner.os == 'Windows' || runner.os == 'macOS' | |
| - uses: actions/checkout@v6 | |
| with: | |
| lfs: "false" | |
| submodules: recursive | |
| - name: Setup Build Mode | |
| shell: bash | |
| run: | | |
| echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV | |
| # Identifier the artifacts are named after. On Linux the runner label | |
| # ("ubuntu-24.04-arm") is the wrong thing to publish: the build host's | |
| # distro is not what an AppImage runs on, and the "-arm" suffix would | |
| # read "arm-aarch64" next to the architecture. Just say "linux" — the | |
| # architecture already keeps the two images apart. Windows and macOS | |
| # keep their runner label. | |
| if [[ "${{ runner.os }}" == "Linux" ]]; then | |
| echo "OS_IDENTIFIER=linux" >> $GITHUB_ENV | |
| else | |
| echo "OS_IDENTIFIER=${{ matrix.os }}" >> $GITHUB_ENV | |
| fi | |
| # Build flavour: "portable" keeps the profile beside the application, | |
| # "installed" uses the OS user-data location. Compile-time only. | |
| # "installed" is the default flavour, so only "portable" is spelled | |
| # out in artifact names. | |
| if [[ "${{ matrix.flavor }}" == "portable" ]]; then | |
| echo "GPGFRONTEND_BUILD_PORTABLE=ON" >> $GITHUB_ENV | |
| echo "FLAVOR_SUFFIX=-portable" >> $GITHUB_ENV | |
| else | |
| echo "GPGFRONTEND_BUILD_PORTABLE=OFF" >> $GITHUB_ENV | |
| echo "FLAVOR_SUFFIX=" >> $GITHUB_ENV | |
| fi | |
| # Single-branch (trunk + tags) model: | |
| # - a version tag (v*) -> stable release build | |
| # - a push to main -> nightly build | |
| # - a pull request -> PR validation build | |
| # - workflow_dispatch -> honour the chosen input | |
| if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| BUILD_MODE="${{ github.event.inputs.build_mode }}" | |
| if [[ "${BUILD_MODE}" == "release" ]]; then | |
| BUILD_TYPE_EFFECTIVE="Release" | |
| else | |
| BUILD_TYPE_EFFECTIVE="RelWithDebInfo" | |
| fi | |
| elif [[ "${GITHUB_EVENT_NAME}" == "pull_request" ]]; then | |
| BUILD_MODE="pr" | |
| BUILD_TYPE_EFFECTIVE="RelWithDebInfo" | |
| elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then | |
| BUILD_MODE="release" | |
| BUILD_TYPE_EFFECTIVE="Release" | |
| else | |
| BUILD_MODE="nightly" | |
| BUILD_TYPE_EFFECTIVE="RelWithDebInfo" | |
| fi | |
| # Stable release builds drop the "Testing" suffix from the app name. | |
| if [[ "${BUILD_MODE}" == "release" ]]; then | |
| GPGFRONTEND_BUILD_STABLE="ON" | |
| else | |
| GPGFRONTEND_BUILD_STABLE="OFF" | |
| fi | |
| echo "BUILD_MODE=${BUILD_MODE}" >> $GITHUB_ENV | |
| echo "BUILD_TYPE=${BUILD_TYPE_EFFECTIVE}" >> $GITHUB_ENV | |
| echo "GPGFRONTEND_BUILD_STABLE=${GPGFRONTEND_BUILD_STABLE}" >> $GITHUB_ENV | |
| echo "BUILD_TYPE_LOWER=$(echo ${BUILD_TYPE_EFFECTIVE} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV | |
| echo "SANDBOX_CMAKE_FLAG=" >> $GITHUB_ENV | |
| echo "CODE_SIGNING_IDENTITY=${{secrets.DEVELOPER_ID_CODE_SIGN_IDENTITY}}" >> $GITHUB_ENV | |
| echo "PROVISIONING_PROFILE_UUID=${{secrets.DEVELOPER_ID_PROVISIONING_PROFILE_UUID}}" >> $GITHUB_ENV | |
| echo "Build mode: ${BUILD_MODE}" | |
| echo "Build type: ${BUILD_TYPE_EFFECTIVE}" | |
| echo "Build flavor: ${{ matrix.flavor }}" | |
| - name: ccache | |
| uses: hendrikmuhs/ccache-action@v1.2 | |
| with: | |
| key: ${{ github.job }}-${{ matrix.os }}-${{ matrix.flavor }}-${{ env.BUILD_TYPE }} | |
| - name: Install Dependence (Linux) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get -y install build-essential binutils git autoconf automake gettext texinfo | |
| sudo apt-get -y install gcc g++ ninja-build | |
| sudo apt-get -y install libarchive-dev libssl-dev libsodium-dev | |
| sudo apt-get -y install gpgsm libxcb-xinerama0 libxcb-icccm4-dev libcups2-dev libdrm-dev libegl1-mesa-dev | |
| sudo apt-get -y install libfuse2 libgcrypt20-dev libnss3-dev libpci-dev libpulse-dev libudev-dev libxtst-dev | |
| sudo apt-get -y install libglu1-mesa-dev libfontconfig1-dev libx11-xcb-dev libxcb-image0 gyp | |
| sudo apt-get -y install libglu1-mesa-dev libfontconfig1-dev libx11-xcb-dev libxcb-* libxkbcommon-x11-0 | |
| sudo apt-get -y install libwayland-cursor0 libwayland-egl1 | |
| # libsecret is dlopen'd, never linked. Installed only so the AppImage | |
| # can carry a copy built against the same glib it bundles. | |
| sudo apt-get -y install libsecret-1-0 | |
| if: runner.os == 'Linux' | |
| - name: Codesign Configuration (macOS) | |
| run: | | |
| APP_CERT_PATH=$RUNNER_TEMP/app_certificate.p12 | |
| PP_PATH=$RUNNER_TEMP/${{secrets.DEVELOPER_ID_PROVISIONING_PROFILE_UUID}}.provisionprofile | |
| echo -n "${{secrets.DEVELOPER_ID_PROVISIONING_PROFILE_DATA}}" | base64 --decode -o $PP_PATH | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| # Import the certificates and provisioning profile into the keychain | |
| echo -n "${{secrets.DEVELOP_ID_CERT}}" | base64 --decode -o $APP_CERT_PATH | |
| security create-keychain -p gpgfrontend build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p gpgfrontend build.keychain | |
| security import $APP_CERT_PATH -k build.keychain -P ${{secrets.DEVELOP_ID_CERT_PWD}} -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k gpgfrontend build.keychain | |
| security set-keychain-settings -lut 3600 | |
| PROFILE_DIR="$HOME/Library/MobileDevice/Provisioning Profiles" | |
| mkdir -p "$PROFILE_DIR" | |
| cp "$PP_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/${{env.PROVISIONING_PROFILE_UUID}}.provisionprofile" | |
| if: runner.os == 'macOS' | |
| - name: Install Qt6 | |
| uses: jurplel/install-qt-action@v4 | |
| with: | |
| version: "6.10.3" | |
| cache: "true" | |
| if: runner.os == 'Linux' || runner.os == 'macOS' | |
| - name: Install Dependence (macOS) | |
| run: | | |
| brew install --formula automake texinfo libarchive googletest create-dmg libsodium | |
| if: runner.os == 'macOS' | |
| - name: Set up MinGW (Windows) | |
| uses: msys2/setup-msys2@v2 | |
| id: msys2 | |
| with: | |
| update: false | |
| release: false | |
| cache: true | |
| install: >- | |
| git | |
| zip | |
| unzip | |
| msys2-devel | |
| base-devel | |
| msys2-runtime-devel | |
| mingw-w64-x86_64-gcc | |
| mingw-w64-x86_64-make | |
| mingw-w64-x86_64-cmake | |
| mingw-w64-x86_64-qt6-base | |
| mingw-w64-x86_64-qt6-tools | |
| mingw-w64-x86_64-ninja | |
| mingw-w64-x86_64-libarchive | |
| mingw-w64-x86_64-gtest | |
| mingw-w64-x86_64-autotools | |
| mingw-w64-x86_64-texinfo | |
| mingw-w64-x86_64-libassuan | |
| mingw-w64-x86_64-ccache | |
| mingw-w64-x86_64-rust | |
| mingw-w64-x86_64-libsodium | |
| if: runner.os == 'Windows' | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| if: runner.os == 'Linux' || runner.os == 'macOS' | |
| # The Rust crate does not see the portable flag, so both flavours produce | |
| # the same cargo output and deliberately share one cache entry. | |
| - name: Cache Cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: | | |
| rust -> build/cargo | |
| shared-key: rust-${{ runner.os }}-${{ matrix.os }}-${{ env.BUILD_TYPE }} | |
| cache-on-failure: true | |
| if: runner.os == 'Linux' || runner.os == 'macOS' | |
| # rust-cache cannot locate the msys2/mingw cargo, so cache the registry and | |
| # Corrosion's target dir directly for the Windows build. | |
| - name: Cache Cargo (Windows) | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry/index | |
| ~/.cargo/registry/cache | |
| ~/.cargo/git/db | |
| ${{github.workspace}}/build/cargo | |
| key: cargo-win-${{ matrix.os }}-${{ env.BUILD_TYPE }}-${{ hashFiles('rust/Cargo.lock') }} | |
| restore-keys: | | |
| cargo-win-${{ matrix.os }}-${{ env.BUILD_TYPE }}- | |
| if: runner.os == 'Windows' | |
| - name: Build GpgME (macOS) | |
| run: | | |
| cd ${{github.workspace}}/third_party/gpgme | |
| export CC="ccache gcc" | |
| export CXX="ccache g++" | |
| ./autogen.sh | |
| mkdir -p build && cd build | |
| ../configure --enable-static \ | |
| --disable-shared \ | |
| --enable-silent-rules \ | |
| --disable-dependency-tracking \ | |
| --enable-languages=cl \ | |
| --disable-gpgconf-test \ | |
| --disable-gpg-test \ | |
| --disable-gpgsm-test \ | |
| --disable-g13-test | |
| make -j$(sysctl -n hw.logicalcpu) | |
| sudo make install | |
| ccache -s | |
| if: runner.os == 'macOS' | |
| - name: Build GpgME (Windows) | |
| shell: msys2 {0} | |
| run: | | |
| cd $(cygpath -u "${{github.workspace}}") | |
| cd third_party/gpgme | |
| export CC="ccache gcc" | |
| export CXX="ccache g++" | |
| export CFLAGS="${CFLAGS} -Wno-int-conversion -Wno-incompatible-pointer-types" | |
| ./autogen.sh | |
| mkdir -p build && cd build | |
| ../configure --enable-maintainer-mode \ | |
| --enable-static \ | |
| --disable-shared \ | |
| --enable-silent-rules \ | |
| --disable-dependency-tracking \ | |
| --enable-languages=cl \ | |
| --disable-gpgconf-test \ | |
| --disable-gpg-test \ | |
| --disable-gpgsm-test \ | |
| --disable-g13-test \ | |
| --enable-w32-glib | |
| make -j$(nproc) | |
| make install | |
| ccache -s | |
| if: runner.os == 'Windows' | |
| - name: Cache googletest (Linux) | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{github.workspace}}/third_party/googletest | |
| key: gtest-${{ matrix.os }}-v1.15.2 | |
| if: runner.os == 'Linux' | |
| - name: Build googletest (Linux) | |
| run: | | |
| if [ ! -f "${{github.workspace}}/third_party/googletest/build/build.ninja" ]; then | |
| rm -rf ${{github.workspace}}/third_party/googletest | |
| git clone --depth 1 --branch v1.15.2 https://github.com/google/googletest.git ${{github.workspace}}/third_party/googletest | |
| cd ${{github.workspace}}/third_party/googletest | |
| mkdir build && cd build | |
| cmake -G Ninja -DBUILD_SHARED_LIBS=ON \ | |
| -DCMAKE_C_COMPILER_LAUNCHER=ccache \ | |
| -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ | |
| .. | |
| ninja | |
| else | |
| echo "Reusing cached googletest build" | |
| fi | |
| cd ${{github.workspace}}/third_party/googletest/build | |
| sudo ninja install | |
| if: runner.os == 'Linux' | |
| - name: Build GpgFrontend (macOS) | |
| run: | | |
| MACOS_MAJOR=$(sw_vers -productVersion | cut -d. -f1) | |
| MACOS_MINOR=$(sw_vers -productVersion | cut -d. -f2) | |
| if [[ "$MACOS_MAJOR" == "13" ]]; then | |
| DEPLOY_TARGET="13.0" | |
| elif [[ "$MACOS_MAJOR" == "14" ]]; then | |
| DEPLOY_TARGET="14.0" | |
| elif [[ "$MACOS_MAJOR" == "15" ]]; then | |
| DEPLOY_TARGET="15.0" | |
| elif [[ "$MACOS_MAJOR" == "26" ]]; then | |
| DEPLOY_TARGET="26.0" | |
| else | |
| DEPLOY_TARGET="${MACOS_MAJOR}.${MACOS_MINOR}" | |
| fi | |
| echo "Set MacOS Deployment Target: $DEPLOY_TARGET" | |
| cmake -B ${{github.workspace}}/build -G Xcode \ | |
| -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ | |
| -DCMAKE_OSX_DEPLOYMENT_TARGET=${DEPLOY_TARGET} \ | |
| -DGPGFRONTEND_XCODE_CODE_SIGN_IDENTITY="${{env.CODE_SIGNING_IDENTITY}}" \ | |
| -DGPGFRONTEND_XCODE_TEAM_ID="${{secrets.GPGFRONTEND_XCODE_TEAM_ID}}" \ | |
| -DGPGFRONTEND_XCODE_PROVISIONING_PROFILE_UUID="${{env.PROVISIONING_PROFILE_UUID}}" \ | |
| -DGPGFRONTEND_BUILD_STABLE=${{env.GPGFRONTEND_BUILD_STABLE}} \ | |
| -DGPGFRONTEND_BUILD_PORTABLE=OFF \ | |
| -DGPGFRONTEND_LINK_GPGME_INTO_CORE=ON | |
| xcodebuild -list -project ${{github.workspace}}/build/GpgFrontend.xcodeproj | |
| echo "CODE_SIGNING_IDENTITY=${{env.CODE_SIGNING_IDENTITY}}" | |
| echo "PROFILE_UUID=${{env.PROVISIONING_PROFILE_UUID}}" | |
| ls -la "$HOME/Library/MobileDevice/Provisioning Profiles/" | |
| security cms -D -i "$HOME/Library/MobileDevice/Provisioning Profiles/${{env.PROVISIONING_PROFILE_UUID}}.provisionprofile" | plutil -p - | |
| security find-identity -v -p codesigning | |
| cd ${{github.workspace}}/build/ | |
| xcodebuild -project ${{github.workspace}}/build/GpgFrontend.xcodeproj \ | |
| -scheme GpgFrontend \ | |
| -configuration "${{env.BUILD_TYPE}}" \ | |
| -archivePath ${{github.workspace}}/build/GpgFrontend.xcarchive \ | |
| archive \ | |
| CODE_SIGN_STYLE=Manual \ | |
| DEVELOPMENT_TEAM="${{secrets.GPGFRONTEND_XCODE_TEAM_ID}}" \ | |
| CODE_SIGN_IDENTITY="${{env.CODE_SIGNING_IDENTITY}}" | |
| mkdir ${{github.workspace}}/build/package | |
| xcodebuild -exportArchive -archivePath ${{github.workspace}}/build/GpgFrontend.xcarchive \ | |
| -exportOptionsPlist ${{github.workspace}}/build/ExportOptions.plist \ | |
| -exportPath ${{github.workspace}}/build/package/ | |
| if: runner.os == 'macOS' | |
| - name: Deploy Qt & Code Sign (macOS) | |
| run: | | |
| # Use macdeployqt to deploy the Qt frameworks and plugins, and sign | |
| # the app bundle in one step. | |
| macdeployqt ${{github.workspace}}/build/package/GpgFrontend.app \ | |
| -verbose=2 \ | |
| -always-overwrite \ | |
| -codesign="${{env.CODE_SIGNING_IDENTITY}}" | |
| # Sign the app bundle with the specified identity and provisioning profile. | |
| codesign -s "${{env.CODE_SIGNING_IDENTITY}}" \ | |
| -f --deep --options=runtime --timestamp \ | |
| ${{github.workspace}}/build/package/GpgFrontend.app | |
| codesign --verify --deep --strict --verbose=4 \ | |
| ${{github.workspace}}/build/package/GpgFrontend.app | |
| if: runner.os == 'macOS' | |
| - name: Package & Sign App Bundle (macOS) | |
| run: | | |
| security -v unlock-keychain -p gpgfrontend | |
| ditto -c -k --keepParent ${{github.workspace}}/build/package/GpgFrontend.app ${{github.workspace}}/build/GpgFrontend.app.zip | |
| hdiutil create ${{github.workspace}}/build/tmp.dmg -ov \ | |
| -volname "GpgFrontend" -fs HFS+ -srcfolder ${{github.workspace}}/build/package/ | |
| mkdir ${{github.workspace}}/build/upload-artifact | |
| create-dmg --codesign "${{secrets.DEVELOPER_ID_CODE_SIGN_IDENTITY}}" --volicon "${{github.workspace}}/resource/lfs/icns/GpgFrontend.icns" --volname GpgFrontend --app-drop-link 600 185 --window-size 800 400 ${{github.workspace}}/build/upload-artifact/GpgFrontend.dmg ${{github.workspace}}/build/package/GpgFrontend.app | |
| mv ${{github.workspace}}/build/upload-artifact/GpgFrontend.dmg \ | |
| ${{github.workspace}}/build/upload-artifact/GpgFrontend-${{env.OS_IDENTIFIER}}-${{env.SHORT_SHA}}.dmg | |
| mv ${{github.workspace}}/build/GpgFrontend.app.zip \ | |
| ${{github.workspace}}/build/GpgFrontend-${{env.OS_IDENTIFIER}}-${{env.SHORT_SHA}}.zip | |
| if: runner.os == 'macOS' | |
| - name: Notarize macOS Build (macOS) | |
| run: | | |
| xcrun notarytool submit \ | |
| --apple-id ${{secrets.APPLE_DEVELOPER_ID}} \ | |
| --team-id ${{secrets.APPLE_DEVELOPER_TEAM_ID}} \ | |
| --password ${{secrets.APPLE_DEVELOPER_ID_SECRET}} \ | |
| ${{github.workspace}}/build/GpgFrontend-${{env.OS_IDENTIFIER}}-${{env.SHORT_SHA}}.zip | |
| if: runner.os == 'macOS' | |
| - name: Build GpgFrontend (Linux) | |
| run: | | |
| export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:$LD_LIBRARY_PATH | |
| cmake -B ${{github.workspace}}/build -G Ninja \ | |
| -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ | |
| -DGPGFRONTEND_BUILD_APP_IMAGE=ON \ | |
| -DGPGFRONTEND_BUILD_STABLE=${{env.GPGFRONTEND_BUILD_STABLE}} \ | |
| -DGPGFRONTEND_BUILD_PORTABLE=${{env.GPGFRONTEND_BUILD_PORTABLE}} \ | |
| -DGPGFRONTEND_LINK_GPGME_INTO_CORE=ON | |
| cmake --build ${{ github.workspace }}/build \ | |
| --config ${{ env.BUILD_TYPE }} \ | |
| --parallel \ | |
| --verbose | |
| ccache -s | |
| if: runner.os == 'Linux' | |
| - name: Package App Image (Linux) | |
| run: | | |
| QT_PLUGIN_DIR=$(qmake -query QT_INSTALL_PLUGINS) | |
| echo "Found Qt plugin dir: $QT_PLUGIN_DIR" | |
| # enter the sqldrivers directory | |
| cd $QT_PLUGIN_DIR/sqldrivers | |
| # remove all non-sqlite drivers to reduce the size of the final AppImage | |
| find . -type f ! -name '*sqlite*' -delete | |
| ls -l | |
| # return to the root of the repository | |
| cd ${{github.workspace}} | |
| mkdir ${{github.workspace}}/build/upload-artifact | |
| cd ${{github.workspace}}/build/upload-artifact | |
| ARCH=$(uname -m) | |
| if [[ "$ARCH" == "x86_64" ]]; then | |
| wget -c -nv https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage | |
| mv linuxdeployqt-continuous-x86_64.AppImage linuxdeployqt-continuous.AppImage | |
| EXTRA_ARGS="" | |
| elif [[ "$ARCH" == "aarch64" ]]; then | |
| wget -c -nv https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-aarch64.AppImage | |
| mv linuxdeployqt-continuous-aarch64.AppImage linuxdeployqt-continuous.AppImage | |
| mkdir -p ${{github.workspace}}/build/artifacts/AppDir/usr/share/doc/libc6/ | |
| touch ${{github.workspace}}/build/artifacts/AppDir/usr/share/doc/libc6/copyright | |
| EXTRA_ARGS="-unsupported-allow-new-glibc" | |
| fi | |
| APP_DIR="${{github.workspace}}/build/artifacts/AppDir" | |
| # The AppImage bundles libglib/libgobject/libgio and its AppRun puts | |
| # them ahead of the host's copies, so a host libsecret built against a | |
| # newer glib cannot resolve its own symbols and the system keychain | |
| # simply disappears -- see linuxdeployqt issue 544. Carrying our own | |
| # copy is what makes the dependency closure self-consistent. It has to | |
| # be staged before linuxdeployqt runs: a file dropped in afterwards | |
| # gets neither an rpath nor its own dependencies deployed. | |
| LIBSECRET_SRC="/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/libsecret-1.so.0" | |
| test -f "$LIBSECRET_SRC" | |
| cp -L "$LIBSECRET_SRC" "$APP_DIR/usr/lib/libsecret-1.so.0" | |
| chmod u+x linuxdeployqt-continuous.AppImage | |
| export LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64:$LD_LIBRARY_PATH | |
| ./linuxdeployqt-continuous.AppImage \ | |
| ${{github.workspace}}/build/artifacts/AppDir/usr/share/applications/*.desktop \ | |
| $EXTRA_ARGS \ | |
| -no-translations \ | |
| -extra-plugins=iconengines,platforms,sqldrivers/libqsqlite.so \ | |
| -appimage \ | |
| -executable=$APP_DIR/usr/lib/libsecret-1.so.0 \ | |
| -executable-dir=${{github.workspace}}/build/artifacts/AppDir/usr/lib/modules | |
| # Without the rpath patch the staged copy cannot find its own | |
| # dependencies, which is the bug this whole step exists to fix, so it | |
| # fails the build rather than shipping a silent regression. | |
| echo "--- deployed credential-store closure ---" | |
| ls -l "$APP_DIR/usr/lib" | grep -E 'secret|glib|gobject|gio|gcrypt' || true | |
| readelf -d "$APP_DIR/usr/lib/libsecret-1.so.0" | grep -E 'RUNPATH|RPATH|SONAME|NEEDED' || true | |
| readelf -d "$APP_DIR/usr/lib/libsecret-1.so.0" | grep -q 'ORIGIN' \ | |
| || { echo "libsecret was not rpath-patched by linuxdeployqt"; exit 1; } | |
| # linuxdeployqt names the image after the .desktop entry, so both | |
| # flavours would come out as Gpg_Frontend-<arch>.AppImage and collide | |
| # once the release job merges every runner's artifacts into one | |
| # directory. Rename to the same scheme the other platforms use. | |
| rm -f linuxdeployqt-continuous.AppImage | |
| for image in Gpg_Frontend*.AppImage; do | |
| mv "$image" \ | |
| "GpgFrontend-${{env.SHORT_SHA}}-${{env.OS_IDENTIFIER}}-${ARCH}${{env.FLAVOR_SUFFIX}}.AppImage" | |
| done | |
| ls -l | |
| if: runner.os == 'Linux' | |
| - name: Build GpgFrontend (Windows) | |
| shell: msys2 {0} | |
| run: | | |
| cd $(cygpath -u "${{github.workspace}}") | |
| cmake -G "Ninja" -S . -B build \ | |
| -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ | |
| -DGPGFRONTEND_BUILD_STABLE=${{env.GPGFRONTEND_BUILD_STABLE}} \ | |
| -DGPGFRONTEND_BUILD_PORTABLE=${{env.GPGFRONTEND_BUILD_PORTABLE}} \ | |
| -DGPGFRONTEND_LINK_GPGME_INTO_CORE=ON | |
| cmake --build build \ | |
| --config ${{ env.BUILD_TYPE }} \ | |
| --parallel \ | |
| --verbose | |
| ccache -s | |
| if: runner.os == 'Windows' | |
| - name: Download GnuPG Binary Release (Windows) | |
| shell: msys2 {0} | |
| run: | | |
| export URL="https://ftp.bktus.com/GnuPG/${{env.GNUPG_VERSION}}" | |
| export KEY_URL="https://bktus.com/pgp/saturneric-code-signing.asc" | |
| export KEY_FPR="12F7E8858CF15BEC9975FF3C5CA3DA246843FD03" | |
| export KEY_FILE="saturneric-code-signing.asc" | |
| export FILE="gnupg.zip" | |
| export CHECKSUM_FILE="SHA256SUMS.txt" | |
| export SIGNATURE_FILE="gnupg.zip.sig" | |
| export GNUPGHOME=$(mktemp -d) | |
| cd $(cygpath -u "${{github.workspace}}") | |
| mkdir -p build/downloads | |
| curl -fL --retry 3 -o build/downloads/$FILE $URL/$FILE | |
| curl -fL --retry 3 -o build/downloads/$CHECKSUM_FILE $URL/$CHECKSUM_FILE | |
| curl -fL --retry 3 -o build/downloads/$KEY_FILE $KEY_URL | |
| curl -fL --retry 3 -o build/downloads/$SIGNATURE_FILE $URL/$SIGNATURE_FILE | |
| gpg --import build/downloads/$KEY_FILE | |
| # Trust is pinned to this exact fingerprint, not to whatever the | |
| # downloaded key file happens to contain. | |
| if ! KEY_INFO=$(gpg --batch --with-colons --list-keys "$KEY_FPR"); then | |
| echo "Imported key does not match fingerprint $KEY_FPR!" >&2 | |
| exit 1 | |
| fi | |
| EXPIRES=$(echo "$KEY_INFO" | awk -F: '/^pub:/ {print $7; exit}') | |
| if [ -n "$EXPIRES" ]; then | |
| echo "Signing key expires: $(date -u -d "@$EXPIRES")" | |
| if [ "$EXPIRES" -le "$(date +%s)" ]; then | |
| echo "Signing key has expired!" >&2 | |
| exit 1 | |
| fi | |
| else | |
| echo "Signing key has no expiration date" | |
| fi | |
| # VALIDSIG carries the primary key fingerprint as its last field, so | |
| # this also rejects a valid signature from any other imported key. | |
| if ! gpg --status-fd 1 --verify build/downloads/$SIGNATURE_FILE \ | |
| build/downloads/$FILE | grep "VALIDSIG" | grep -q "$KEY_FPR"; then | |
| echo "GnuPG signature verification failed!" >&2 | |
| exit 1 | |
| fi | |
| CHECKSUM=$(grep "$FILE\$" build/downloads/$CHECKSUM_FILE | awk '{print $1}') | |
| ACTUAL_CHECKSUM=$(sha256sum build/downloads/$FILE | awk '{print $1}') | |
| echo "Expected Checksum: $CHECKSUM" | |
| echo "Actual Checksum: $ACTUAL_CHECKSUM" | |
| if [ "$CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then | |
| echo "Checksum verification failed!" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p build/artifacts/gnupg | |
| unzip -o build/downloads/$FILE -d build/artifacts/gnupg/ | |
| ls -l build/artifacts/gnupg/ | |
| if: runner.os == 'Windows' | |
| # Payload staging is flavour-independent: the portable ZIP and the MSI are | |
| # both built from this same tree, only from a differently configured build. | |
| - name: Stage Payload (Windows) | |
| shell: msys2 {0} | |
| run: | | |
| cd $(cygpath -u "${{github.workspace}}") | |
| cp PrivacyPolicy.md build/artifacts/ | |
| cp README.md build/artifacts/ | |
| cp SECURITY.md build/artifacts/ | |
| cp TRANSLATORS build/artifacts/ | |
| cp COPYING build/artifacts/ | |
| cp gpgfrontend.ico build/artifacts/bin/ | |
| rm -rf build/artifacts/bin/*.a | |
| rm -rf build/artifacts/bin/modules/*.a | |
| mv build/artifacts/bin/modules build/artifacts/modules | |
| cd build | |
| # Deploy every gf_* library rather than a hand-kept list, so a newly | |
| # registered library (gf_res was the last one) cannot be forgotten. | |
| for lib in ./artifacts/bin/libgf_*.dll; do | |
| windeployqt6 --no-translations --force "$lib" | |
| done | |
| windeployqt6 --no-translations --force ./artifacts/bin/GpgFrontend.exe | |
| # A module may need Qt modules the app itself never links (Qt6Xml, | |
| # Qt6Network, ...), so each one still has to be scanned. But --dir | |
| # sends what it needs into bin/, which is the first directory the | |
| # loader searches, instead of duplicating the whole Qt runtime next | |
| # to every module. | |
| for module in ./artifacts/modules/*.dll; do | |
| windeployqt6 --no-translations --force --dir ./artifacts/bin "$module" | |
| done | |
| mkdir -p upload-artifact | |
| if: runner.os == 'Windows' | |
| - name: Package Portable Archive (Windows) | |
| shell: msys2 {0} | |
| run: | | |
| cd $(cygpath -u "${{github.workspace}}")/build/artifacts | |
| zip -r ../upload-artifact/GpgFrontend-${{env.SHORT_SHA}}-${{env.OS_IDENTIFIER}}-x86_64-portable.zip * | |
| if: runner.os == 'Windows' && matrix.flavor == 'portable' | |
| - name: Build MSI Installer (Windows) | |
| shell: pwsh | |
| run: | | |
| # Version always tracks the CMake project() version, no wxs edit needed. | |
| $match = Select-String -Path CMakeLists.txt ` | |
| -Pattern '^\s*VERSION\s+([0-9]+\.[0-9]+\.[0-9]+)' | Select-Object -First 1 | |
| if (-not $match) { throw "Could not extract project version from CMakeLists.txt" } | |
| $version = $match.Matches[0].Groups[1].Value | |
| Write-Host "Project version: $version" | |
| # Toolset and extension versions must match: an unpinned extension resolves | |
| # to the latest major (7.x), which a WiX 5 host cannot load (wixext5 vs wixext7). | |
| $wixVersion = '5.0.2' | |
| dotnet tool install --global wix --version $wixVersion | |
| wix extension add -g WixToolset.UI.wixext/$wixVersion | |
| wix extension add -g WixToolset.Util.wixext/$wixVersion | |
| New-Item -ItemType Directory -Force -Path "${{github.workspace}}/build/upload-artifact" | Out-Null | |
| # -arch x64 is mandatory: the package is MsiPackageType=x64. | |
| # PayloadDir / ProductVersion / IconSource / BrandingDir override the wxs | |
| # defaults for this runner (its defaults assume a build run from resource/wix). | |
| wix build -arch x64 ` | |
| -ext WixToolset.UI.wixext/$wixVersion ` | |
| -ext WixToolset.Util.wixext/$wixVersion ` | |
| -d PayloadDir="${{github.workspace}}/build/artifacts" ` | |
| -d ProductVersion="$version" ` | |
| -d IconSource="${{github.workspace}}/gpgfrontend.ico" ` | |
| -d BrandingDir="${{github.workspace}}/resource/wix" ` | |
| -o "${{github.workspace}}/build/upload-artifact/GpgFrontend-${{env.SHORT_SHA}}-${{env.OS_IDENTIFIER}}-x86_64.msi" ` | |
| resource/wix/Package.wxs | |
| # wix drops a .wixpdb next to the .msi; it is build metadata, not a deliverable. | |
| Remove-Item -Force -ErrorAction SilentlyContinue ` | |
| "${{github.workspace}}/build/upload-artifact/*.wixpdb" | |
| # An MSI installs into Program Files and keeps its data in the user | |
| # profile, so it is only ever built from the installed flavour. | |
| if: runner.os == 'Windows' && matrix.flavor == 'installed' | |
| - name: Upload Artifact (Linux) | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: gpgfrontend-${{matrix.os}}-${{matrix.flavor}}-${{env.BUILD_TYPE_LOWER}}-${{env.SHORT_SHA}} | |
| path: ${{github.workspace}}/build/upload-artifact/GpgFrontend-*.AppImage* | |
| if: runner.os == 'Linux' | |
| - name: Upload Artifact (macOS) | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: gpgfrontend-${{env.OS_IDENTIFIER}}-${{matrix.flavor}}-${{env.BUILD_TYPE_LOWER}}-${{env.SHORT_SHA}} | |
| path: ${{github.workspace}}/build/upload-artifact/* | |
| if: runner.os == 'macOS' | |
| - name: Upload Artifact (Windows) | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: gpgfrontend-${{matrix.os}}-${{matrix.flavor}}-${{env.BUILD_TYPE_LOWER}}-${{env.SHORT_SHA}} | |
| path: ${{github.workspace}}/build/upload-artifact/* | |
| if: runner.os == 'Windows' | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| # Only publish the rolling nightly release from pushes to the main branch. | |
| # Stable releases (version tags) are packaged manually/offline. | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| environment: nightly-release-approval | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| lfs: "false" | |
| submodules: recursive | |
| - name: Re-create nightly tag | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| cd ${{github.workspace}} | |
| gh release delete nightly --repo saturneric/GpgFrontend --cleanup-tag --yes || true | |
| git tag -f nightly $GITHUB_SHA | |
| git push origin nightly --force | |
| - name: Download Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts/ | |
| pattern: gpgfrontend-* | |
| merge-multiple: true | |
| - name: Ignore Non-Artifact Files | |
| run: | | |
| # Remove MAS specific .pkg files if they exist, since they are not | |
| # needed for the release and we only want to keep the main artifacts | |
| # (dmg, AppImage, zip). | |
| find artifacts/ -type f -name '*.pkg' -delete | |
| - name: Generate SHA256 checksums | |
| run: | | |
| sha256sum artifacts/* > artifacts/SHA256SUMS.txt | |
| cat artifacts/SHA256SUMS.txt | |
| - name: List files | |
| run: ls -rl artifacts/ | |
| - name: Generate Nightly Release Title | |
| id: release_title | |
| run: echo "title=Nightly Build $(date +'%Y-%m-%d')" >> $GITHUB_OUTPUT | |
| - name: Update Nightly Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: nightly | |
| name: ${{ steps.release_title.outputs.title }} | |
| draft: false | |
| prerelease: true | |
| body_path: ${{ github.workspace }}/.github/NIGHTLY_RELEASE.md | |
| files: | | |
| artifacts/* |