diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35a5631b..5928f65b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,14 +116,9 @@ jobs: - name: Run shellcheck run: ./scripts/ci-run-shellcheck.sh - ci: + ci-linux: needs: [tag, clang-format, cppcheck, shellcheck] - - strategy: - matrix: - os: [ubuntu-22.04, macos-13, macos-14] - - runs-on: ${{ matrix.os }} + runs-on: ubuntu-22.04 timeout-minutes: 15 env: @@ -133,23 +128,13 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up Linux - if: runner.os == 'Linux' + - name: Set up apt dependencies run: | sudo apt update sudo apt install -y rpm alien tmux sudo apt remove -y jq - - name: Set up macOS (AMD64 and ARM64) - if: runner.os == 'macOS' - run: | - brew install --quiet coreutils tree autoconf automake libtool tmux sqlite3 - brew uninstall jq - - # --- Build --- - - name: Build on Linux (${{ env.AMD64_LINUX_GCC }}) - if: runner.os == 'Linux' env: PREFIX: ${{ env.AMD64_LINUX_GCC }} CC: gcc @@ -161,7 +146,6 @@ jobs: ./scripts/ci-create-rpm-package.sh - name: Build on Linux (${{ env.AMD64_LINUX_CLANG }}) - if: runner.os == 'Linux' env: PREFIX: ${{ env.AMD64_LINUX_CLANG }} CC: clang @@ -172,26 +156,6 @@ jobs: ./scripts/ci-create-debian-package.sh ./scripts/ci-create-rpm-package.sh - - name: Build on macOS (${{ env.AMD64_MACOSX_GCC }}) - if: matrix.os == 'macos-13' - env: - PREFIX: ${{ env.AMD64_MACOSX_GCC }} - CC: gcc-13 - MAKE: make - RUN_TESTS: true - run: ./scripts/ci-build.sh - - - name: Build on macOS (${{ env.ARM64_MACOSX_GCC }}) - if: matrix.os == 'macos-14' - env: - PREFIX: ${{ env.ARM64_MACOSX_GCC }} - CC: gcc-13 - MAKE: make - RUN_TESTS: true - run: ./scripts/ci-build.sh - - # --- Upload build artifacts --- - - name: Prepare build artifacts for upload run: ./scripts/ci-prepare-artifacts-for-upload.sh @@ -206,7 +170,6 @@ jobs: run: ./scripts/ci-verify-attestations.sh - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.zip) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.zip @@ -217,7 +180,6 @@ jobs: if-no-files-found: error - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.zip) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.zip @@ -228,7 +190,6 @@ jobs: if-no-files-found: error - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.deb) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.deb @@ -239,7 +200,6 @@ jobs: if-no-files-found: error - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.deb) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.deb @@ -250,7 +210,6 @@ jobs: if-no-files-found: error - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.rpm) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.rpm @@ -261,7 +220,6 @@ jobs: if-no-files-found: error - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.rpm) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.rpm @@ -271,30 +229,7 @@ jobs: retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} if-no-files-found: error - - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_MACOSX_GCC }}.zip) - if: matrix.os == 'macos-13' - uses: actions/upload-artifact@v4 - env: - ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_MACOSX_GCC }}.zip - with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_NAME }} - retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} - if-no-files-found: error - - - name: Upload (zsv-${{ env.TAG }}-${{ env.ARM64_MACOSX_GCC }}.zip) - if: matrix.os == 'macos-14' - uses: actions/upload-artifact@v4 - env: - ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.ARM64_MACOSX_GCC }}.zip - with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_NAME }} - retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} - if-no-files-found: error - - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.tar.gz) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_GCC }}.tar.gz @@ -305,7 +240,6 @@ jobs: if-no-files-found: error - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.tar.gz) - if: runner.os == 'Linux' uses: actions/upload-artifact@v4 env: ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_LINUX_CLANG }}.tar.gz @@ -315,22 +249,72 @@ jobs: retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} if-no-files-found: error - - name: Upload (zsv-${{ env.TAG }}-${{ env.AMD64_MACOSX_GCC }}.tar.gz) - if: matrix.os == 'macos-13' - uses: actions/upload-artifact@v4 + - name: Upload release artifacts + if: startsWith(github.ref, 'refs/tags/v') + run: ./scripts/ci-upload-release-artifacts.sh + + ci-macos: + needs: [tag, clang-format, cppcheck, shellcheck] + + strategy: + matrix: + os: [macos-13, macos-14] + + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + + env: + TAG: ${{ needs.tag.outputs.TAG }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up homebrew dependencies + run: brew install --quiet coreutils tree autoconf automake libtool tmux sqlite3 + + - name: Set PREFIX and ZIP env var env: - ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.AMD64_MACOSX_GCC }}.tar.gz + PREFIX: ${{ runner.arch == 'X64' && env.AMD64_MACOSX_GCC || env.ARM64_MACOSX_GCC }} + run: | + { + echo "PREFIX=$PREFIX" + echo "ZIP=zsv-$TAG-$PREFIX.zip" + } | tee -a "$GITHUB_ENV" + + - name: Build on macOS (${{ env.AMD64_MACOSX_GCC }}) + env: + CC: gcc-13 + MAKE: make + RUN_TESTS: true + SKIP_TAR_ARCHIVE: true + run: ./scripts/ci-build.sh + + - name: Prepare build artifacts for upload + run: ./scripts/ci-prepare-artifacts-for-upload.sh + + - name: Codesign and notarize (${{ env.PREFIX }}) + if: startsWith(github.ref, 'refs/tags/v') + env: + MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }} + MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + run: ./scripts/ci-macos-codesign-and-notarize.sh "$PWD/$ARTIFACT_DIR/$ZIP" + + - name: Attest build artifacts for release + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/attest-build-provenance@v2 with: - name: ${{ env.ARTIFACT_NAME }} - path: ${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_NAME }} - retention-days: ${{ env.ARTIFACT_RETENTION_DAYS }} - if-no-files-found: error + subject-path: ${{ env.ARTIFACT_DIR }}/* + + - name: Verify attestations of release artifacts + if: startsWith(github.ref, 'refs/tags/v') + run: ./scripts/ci-verify-attestations.sh - - name: Upload (zsv-${{ env.TAG }}-${{ env.ARM64_MACOSX_GCC }}.tar.gz) - if: matrix.os == 'macos-14' + - name: Upload (${{ env.ZIP }}) uses: actions/upload-artifact@v4 env: - ARTIFACT_NAME: zsv-${{ env.TAG }}-${{ env.ARM64_MACOSX_GCC }}.tar.gz + ARTIFACT_NAME: ${{ env.ZIP }} with: name: ${{ env.ARTIFACT_NAME }} path: ${{ env.ARTIFACT_DIR }}/${{ env.ARTIFACT_NAME }} @@ -533,7 +517,7 @@ jobs: steps: - name: Set up dependencies shell: sh - run: apk add bash gcc make musl-dev perl ncurses-dev ncurses-static tmux file sqlite curl zip wget tar + run: apk add bash gcc make musl-dev perl ncurses-dev ncurses-static tmux file sqlite curl zip wget tar git - name: Checkout uses: actions/checkout@v4 @@ -710,8 +694,8 @@ jobs: with: path: playground - deploy-playground: - if: ${{ github.ref_name == 'main' }} + deploy-wasm-playground: + if: ${{ github.ref_name == 'main' || startsWith(github.ref, 'refs/tags/v') }} needs: ci-wasm runs-on: ubuntu-22.04 diff --git a/scripts/ci-macos-codesign-and-notarize.sh b/scripts/ci-macos-codesign-and-notarize.sh new file mode 100755 index 00000000..360dafcb --- /dev/null +++ b/scripts/ci-macos-codesign-and-notarize.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +set -e + +echo "[INF] Running $0" + +# Startup checks + +if [ "$#" -ne 1 ] || [ "$1" = "" ]; then + echo "[ERR] Usage: $0 [ARCHIVE.zip]" + echo "[ERR] Following environment variables are required:" + echo "[ERR] - MACOS_CERT_P12 (base64 encoded)" + echo "[ERR] - MACOS_CERT_PASSWORD (plaintext)" + echo "[ERR] - APPLE_APP_SPECIFIC_PASSWORD (plaintext)" + exit 1 +fi + +if [ "$CI" != true ] || [ "$RUNNER_OS" != "macOS" ]; then + echo "[ERR] Must be run in GitHub Actions CI on a macOS runner!" + exit 1 +fi + +# Inputs (CLI arguments + environment variables) + +APP_ARCHIVE=${1:-} +APP_IDENTIFIER="com.liquidaty.zsv" +APP_TEAM_ID="HXK8Y6Q9K2" +APP_IDENTITY="Developer ID Application: matt wong ($APP_TEAM_ID)" + +MACOS_CERT_P12=${MACOS_CERT_P12:-} +MACOS_CERT_PASSWORD=${MACOS_CERT_PASSWORD:-} + +APPLE_ID="matt@liquidaty.com" +APPLE_APP_SPECIFIC_PASSWORD=${APPLE_APP_SPECIFIC_PASSWORD:-} + +# Validations + +echo "[INF] Validating arguments and environment variables" + +if [ ! -f "$APP_ARCHIVE" ]; then + echo "[ERR] Invalid archive! [$APP_ARCHIVE]" + echo "[ERR] Archive does not exist or is not a file!" + exit 1 +elif ! echo "$APP_ARCHIVE" | grep '.zip$' >/dev/null || + ! file --mime "$APP_ARCHIVE" | grep 'application/zip' >/dev/null; then + echo "[ERR] Invalid archive type! [$APP_ARCHIVE]" + echo "[ERR] Only .zip archive is supported!" + exit 1 +fi + +if [ "$MACOS_CERT_P12" = "" ]; then + echo "[ERR] MACOS_CERT_P12 is not set!" + exit 1 +elif [ "$MACOS_CERT_PASSWORD" = "" ]; then + echo "[ERR] MACOS_CERT_PASSWORD is not set!" + exit 1 +elif [ "$APPLE_APP_SPECIFIC_PASSWORD" = "" ]; then + echo "[ERR] APPLE_APP_SPECIFIC_PASSWORD is not set!" + exit 1 +fi + +echo "[INF] PWD : $PWD" +echo "[INF] APP_ARCHIVE : $APP_ARCHIVE" +echo "[INF] APP_IDENTIFIER : $APP_IDENTIFIER" +echo "[INF] APP_TEAM_ID : $APP_TEAM_ID" +echo "[INF] APP_IDENTITY : $APP_IDENTITY" +echo "[INF] MACOS_CERT_P12 : $MACOS_CERT_P12" +echo "[INF] MACOS_CERT_PASSWORD : $MACOS_CERT_PASSWORD" +echo "[INF] APPLE_ID : $APPLE_ID" +echo "[INF] APPLE_APP_SPECIFIC_PASSWORD : $APPLE_APP_SPECIFIC_PASSWORD" + +echo "[INF] Validated inputs and environment variables successfully!" + +# Archive + +echo "[INF] Setting up temporary directory and archive" +TMP_ARCHIVE=$(basename "$APP_ARCHIVE") +TMP_DIR="$RUNNER_TEMP/codesign-$RUNNER_ARCH-$RUNNER_OS" +rm -rf "$TMP_DIR" +mkdir -p "$TMP_DIR" +cp "$APP_ARCHIVE" "$TMP_DIR/$TMP_ARCHIVE" +cd "$TMP_DIR" +unzip "$TMP_ARCHIVE" +rm "$TMP_ARCHIVE" +ls -hl +echo "[INF] Set up temporary directory archive successfully!" + +# Keychain + Certificate + +echo "[INF] Setting up keychain and importing certificate" +KEYCHAIN="build.keychain" +CERTIFICATE="$RUNNER_TEMP/codesign-cert-$RUNNER_ARCH-$RUNNER_OS.p12" +echo "$MACOS_CERT_P12" | base64 --decode >"$CERTIFICATE" +security create-keychain -p actions "$KEYCHAIN" +security default-keychain -s "$KEYCHAIN" +security unlock-keychain -p actions "$KEYCHAIN" +security set-keychain-settings -t 3600 -u "$KEYCHAIN" +security import "$CERTIFICATE" -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" -A -t cert -f pkcs12 -T /usr/bin/codesign +security set-key-partition-list -S apple-tool:,apple: -s -k actions "$KEYCHAIN" +security find-identity -v "$KEYCHAIN" +rm "$CERTIFICATE" +echo "[INF] Set up keychain and imported certificate successfully!" + +# Codesigning + +echo "[INF] Codesigning" + +echo "[INF] Codesigning all files and subdirectories" +find "$TMP_DIR"/bin "$TMP_DIR"/include "$TMP_DIR"/lib -type f -exec \ + codesign --verbose --deep --force --verify --options=runtime --timestamp \ + --sign "$APP_IDENTITY" --identifier "$APP_IDENTIFIER" {} + +echo "[INF] Codesigned all files and subdirectories successfully!" + +echo "[INF] Creating final archive [$PWD/$TMP_ARCHIVE]" +zip -r ./"$TMP_ARCHIVE" ./bin ./include ./lib +echo "[INF] Created final archive successfully!" + +echo "[INF] Codesigning final archive [$PWD/$TMP_ARCHIVE]" +codesign --verbose --force --verify --options=runtime --timestamp \ + --sign "$APP_IDENTITY" --identifier "$APP_IDENTIFIER" "$TMP_ARCHIVE" +echo "[INF] Codesigned final archive successfully!" + +echo "[INF] Codesigned successfully!" + +# Notarization + +echo "[INF] Notarizing [$PWD/$TMP_ARCHIVE]" +NOTARIZATION_OUTPUT=$(xcrun notarytool submit "$TMP_ARCHIVE" \ + --apple-id "$APPLE_ID" \ + --password "$APPLE_APP_SPECIFIC_PASSWORD" \ + --team-id "$APP_TEAM_ID" \ + --output-format json \ + --wait) +echo "[INF] NOTARIZATION_OUTPUT: $NOTARIZATION_OUTPUT" +if ! echo "$NOTARIZATION_OUTPUT" | jq -e '.status == "Accepted"' >/dev/null; then + echo "[ERR] Failed to notarize! See above output for errors." + exit 1 +fi +echo "[INF] Notarized successfully!" + +echo "[INF] Placing final archive [$PWD/$TMP_ARCHIVE => $APP_ARCHIVE]" +cp -f "$TMP_DIR/$TMP_ARCHIVE" "$APP_ARCHIVE" +echo "[INF] Placed final archive successfully!" + +# Cleanup + +security delete-keychain "$KEYCHAIN" +rm -rf "$TMP_DIR" + +echo "[INF] --- [DONE] ---" diff --git a/scripts/ci-prepare-artifacts-for-upload.sh b/scripts/ci-prepare-artifacts-for-upload.sh index aad40607..f6174485 100755 --- a/scripts/ci-prepare-artifacts-for-upload.sh +++ b/scripts/ci-prepare-artifacts-for-upload.sh @@ -28,8 +28,8 @@ prepare() { [ "$1" = "" ] && return for ARTIFACT_NAME in *."$1"; do [ -e "$ARTIFACT_NAME" ] || break - FILE_PREFIX="$(echo "$ARTIFACT_NAME" | cut -c -${#ARTIFACT_PREFIX})" - [ "$FILE_PREFIX" = "$ARTIFACT_PREFIX" ] && continue + FILE_PREFIX="$(echo "$ARTIFACT_NAME" | cut -d '-' -f1)" + [ "$FILE_PREFIX" = "zsv" ] && continue UPDATED_ARTIFACT_NAME="$ARTIFACT_PREFIX-$ARTIFACT_NAME" echo "[INF] [$ARTIFACT_NAME] => [$UPDATED_ARTIFACT_NAME]" mv -f "$ARTIFACT_NAME" "$UPDATED_ARTIFACT_NAME"