Build and Release #237
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: Build and Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" # validated by set-metadata | |
| schedule: | |
| - cron: "0 4 * * *" | |
| workflow_dispatch: | |
| inputs: | |
| build_type: | |
| description: "Build type" | |
| required: false | |
| type: choice | |
| options: | |
| - nightly | |
| - production | |
| - beta | |
| default: nightly | |
| platforms: | |
| description: "Platforms to build ('all', or 'macos,linux,android,ios,windows')" | |
| required: false | |
| type: string | |
| default: "linux" | |
| linux_arch: | |
| description: "Linux arch to build when Linux is included" | |
| required: false | |
| type: choice | |
| options: | |
| - all | |
| - amd64 | |
| - arm64 | |
| default: all | |
| cleanup_on_failure: | |
| description: "Delete draft release on failure" | |
| required: false | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: write | |
| id-token: "write" | |
| concurrency: | |
| group: ${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| set-metadata: | |
| # Allow tag-push releases, scheduled builds from default branch, | |
| # and workflow_dispatch from any branch (non-main branches create | |
| # draft releases that are immediately deleted for testing). | |
| if: ${{ startsWith(github.ref, 'refs/tags/') || (github.event_name == 'schedule' && github.ref_name == github.event.repository.default_branch) || github.event_name == 'workflow_dispatch' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| build_type: ${{ steps.meta.outputs.build_type }} | |
| release_tag: ${{ steps.meta.outputs.release_tag }} | |
| version: ${{ steps.meta.outputs.version }} | |
| installer_base_name: ${{ steps.meta.outputs.installer_base_name }} | |
| platform: ${{ steps.meta.outputs.platform }} | |
| linux_arch: ${{ steps.meta.outputs.linux_arch }} | |
| is_test_run: ${{ steps.meta.outputs.is_test_run }} | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.ref }} | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - id: meta | |
| shell: bash | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| EVENT="${{ github.event_name }}" | |
| case "$EVENT" in | |
| schedule) | |
| # Scheduled nightly build | |
| BUILD_TYPE="nightly" | |
| VERSION=$(./scripts/ci/version.sh generate nightly) | |
| RELEASE_TAG="v${VERSION}" | |
| PLATFORM="all" | |
| LINUX_ARCH="all" | |
| ;; | |
| workflow_dispatch) | |
| # Manual trigger | |
| BUILD_TYPE="${{ github.event.inputs.build_type }}" | |
| PLATFORM="${{ github.event.inputs.platforms }}" | |
| LINUX_ARCH="${{ github.event.inputs.linux_arch }}" | |
| case "$BUILD_TYPE" in | |
| nightly|beta) | |
| VERSION=$(./scripts/ci/version.sh generate "$BUILD_TYPE") | |
| RELEASE_TAG="v${VERSION}" | |
| ;; | |
| production) | |
| echo "Error: Production builds must use git tags, not manual dispatch" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| ;; | |
| push) | |
| # Tag push (release build) - preserve original tag with 'v' prefix | |
| RELEASE_TAG=${GITHUB_REF#refs/tags/} | |
| # Validate the tag (version.sh strips 'v' for validation) | |
| ./scripts/ci/version.sh validate "$RELEASE_TAG" > /dev/null | |
| # Strip 'v' for VERSION (used by build workflows) | |
| VERSION="${RELEASE_TAG#v}" | |
| case "$RELEASE_TAG" in | |
| *-beta*) | |
| BUILD_TYPE="beta" | |
| ;; | |
| *) | |
| # Production: bare version or version with platform suffix | |
| # e.g., v9.0.15 (all platforms) or v9.0.15-android (single platform) | |
| if [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-(windows|macos|linux|android|ios))?$ ]]; then | |
| BUILD_TYPE="production" | |
| else | |
| echo "Error: Invalid tag format for build" >&2 | |
| echo " Use 'v1.0.0' for production (all platforms)" >&2 | |
| echo " Use 'v1.0.0-<platform>' for production (single platform)" >&2 | |
| echo " Use 'v1.0.0-beta' for beta" >&2 | |
| echo "Got: $RELEASE_TAG" >&2 | |
| exit 1 | |
| fi | |
| ;; | |
| esac | |
| # Extract platform suffix (if any) | |
| platform_suffix="${RELEASE_TAG##*-}" | |
| case "$platform_suffix" in | |
| windows) PLATFORM="windows" ;; | |
| macos) PLATFORM="macos" ;; | |
| linux) PLATFORM="linux" ;; | |
| android) PLATFORM="android" ;; | |
| ios) PLATFORM="ios" ;; | |
| *) PLATFORM="all" ;; | |
| esac | |
| # Strip platform suffix from VERSION (platform is tracked separately) | |
| case "$PLATFORM" in | |
| all) ;; | |
| *) VERSION="${VERSION%-$PLATFORM}" ;; | |
| esac | |
| case "$PLATFORM" in | |
| all|linux) LINUX_ARCH="all" ;; | |
| *) LINUX_ARCH="amd64" ;; | |
| esac | |
| ;; | |
| esac | |
| case "$LINUX_ARCH" in | |
| all|amd64|arm64) ;; | |
| *) | |
| echo "Error: Invalid linux_arch '$LINUX_ARCH' (expected: all, amd64, arm64)" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| # Installer base name - Makefile will append build type | |
| INSTALLER_BASE_NAME="lantern-installer" | |
| echo "Build Configuration:" | |
| echo " Event: $EVENT" | |
| echo " Build Type: $BUILD_TYPE" | |
| echo " Release Tag: $RELEASE_TAG" | |
| echo " Version: $VERSION" | |
| echo " Platform: $PLATFORM" | |
| echo " Linux Arch: $LINUX_ARCH" | |
| echo " Installer: $INSTALLER_BASE_NAME" | |
| # Test run: workflow_dispatch from non-default branch | |
| IS_TEST_RUN="false" | |
| if [[ "$EVENT" == "workflow_dispatch" ]]; then | |
| DEFAULT_BRANCH="${{ github.event.repository.default_branch }}" | |
| if [[ "${{ github.ref_name }}" != "$DEFAULT_BRANCH" ]]; then | |
| IS_TEST_RUN="true" | |
| echo "Test run: draft release will be deleted after build" | |
| fi | |
| fi | |
| echo "build_type=$BUILD_TYPE" >> $GITHUB_OUTPUT | |
| echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "platform=$PLATFORM" >> $GITHUB_OUTPUT | |
| echo "linux_arch=$LINUX_ARCH" >> $GITHUB_OUTPUT | |
| echo "installer_base_name=$INSTALLER_BASE_NAME" >> $GITHUB_OUTPUT | |
| echo "is_test_run=$IS_TEST_RUN" >> $GITHUB_OUTPUT | |
| - name: Update pubspec.yaml version | |
| shell: bash | |
| env: | |
| RELEASE_TAG: ${{ steps.meta.outputs.release_tag }} | |
| run: | | |
| # Strip 'v' prefix if present | |
| VERSION="${RELEASE_TAG#v}" | |
| # Extract base semver (strip suffixes like -beta, -nightly-...) | |
| BASE_VERSION=$(./scripts/ci/version.sh extract "$VERSION") | |
| CURRENT_BUILD=$(grep '^version:' pubspec.yaml | sed -E 's/^version: [0-9]+\.[0-9]+\.[0-9]+\+([0-9]+).*/\1/') | |
| BUILD_NUMBER=$((CURRENT_BUILD + 1)) | |
| FULL_VERSION="${BASE_VERSION}+${BUILD_NUMBER}" | |
| echo "Setting pubspec version: $FULL_VERSION" | |
| echo " Base version: $BASE_VERSION" | |
| echo " Build number: $BUILD_NUMBER" | |
| sed -i.bak -E "s/^version:.*/version: ${FULL_VERSION}/" pubspec.yaml | |
| grep '^version:' pubspec.yaml | |
| - name: Upload pubspec.yaml | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: pubspec | |
| path: pubspec.yaml | |
| release-notify: | |
| needs: [set-metadata] | |
| if: | | |
| needs.set-metadata.outputs.build_type == 'production' && | |
| needs.set-metadata.outputs.platform == 'all' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Notify Slack | |
| uses: slackapi/slack-github-action@v2.0.0 | |
| with: | |
| webhook: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| webhook-type: webhook-trigger | |
| payload: | | |
| { | |
| "text": "Production release v${{ needs.set-metadata.outputs.version }} awaiting approval before releasing on all platforms: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| } | |
| release-approval: | |
| needs: [set-metadata] | |
| if: | | |
| needs.set-metadata.outputs.build_type == 'production' && | |
| needs.set-metadata.outputs.platform == 'all' | |
| runs-on: ubuntu-latest | |
| environment: production | |
| steps: | |
| - run: echo "Approved" | |
| build-macos: | |
| needs: [set-metadata, release-create, release-approval] | |
| uses: ./.github/workflows/build-macos.yml | |
| secrets: inherit | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.release-create.result == 'success' && | |
| (needs.release-approval.result == 'success' || needs.release-approval.result == 'skipped') && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'macos')) | |
| with: | |
| version: ${{ needs.set-metadata.outputs.version }} | |
| build_type: ${{ needs.set-metadata.outputs.build_type }} | |
| installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| build-windows: | |
| needs: [set-metadata, release-create, release-approval] | |
| uses: ./.github/workflows/build-windows.yml | |
| secrets: inherit | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.release-create.result == 'success' && | |
| (needs.release-approval.result == 'success' || needs.release-approval.result == 'skipped') && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'windows')) | |
| with: | |
| version: ${{ needs.set-metadata.outputs.version }} | |
| build_type: ${{ needs.set-metadata.outputs.build_type }} | |
| installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| enable_ip_check: ${{ needs.set-metadata.outputs.build_type == 'nightly' }} | |
| build-linux: | |
| needs: [set-metadata, release-create, release-approval] | |
| uses: ./.github/workflows/build-linux.yml | |
| secrets: inherit | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.release-create.result == 'success' && | |
| (needs.release-approval.result == 'success' || needs.release-approval.result == 'skipped') && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'linux')) | |
| with: | |
| version: ${{ needs.set-metadata.outputs.version }} | |
| build_type: ${{ needs.set-metadata.outputs.build_type }} | |
| installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| linux_arch: ${{ needs.set-metadata.outputs.linux_arch }} | |
| build-ios: | |
| needs: [set-metadata, release-create, release-approval] | |
| uses: ./.github/workflows/build-ios.yml | |
| secrets: inherit | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.release-create.result == 'success' && | |
| (needs.release-approval.result == 'success' || needs.release-approval.result == 'skipped') && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'ios')) | |
| with: | |
| version: ${{ needs.set-metadata.outputs.version }} | |
| build_type: ${{ needs.set-metadata.outputs.build_type }} | |
| installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| build-android: | |
| needs: [set-metadata, release-create, release-approval] | |
| uses: ./.github/workflows/build-android.yml | |
| secrets: inherit | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.release-create.result == 'success' && | |
| (needs.release-approval.result == 'success' || needs.release-approval.result == 'skipped') && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'android')) | |
| with: | |
| version: ${{ needs.set-metadata.outputs.version }} | |
| build_type: ${{ needs.set-metadata.outputs.build_type }} | |
| installer_base_name: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| release-create: | |
| needs: set-metadata | |
| runs-on: ubuntu-latest | |
| env: | |
| BUILD_TYPE: ${{ needs.set-metadata.outputs.build_type }} | |
| RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Strip 'v' prefix for display | |
| VERSION="${RELEASE_TAG#v}" | |
| case "$BUILD_TYPE" in | |
| production) | |
| PRERELEASE_FLAG="" | |
| TITLE="Lantern $VERSION" | |
| ;; | |
| beta) | |
| PRERELEASE_FLAG="--prerelease" | |
| TITLE="Beta $VERSION" | |
| ;; | |
| nightly) | |
| PRERELEASE_FLAG="--prerelease" | |
| TITLE="Nightly $VERSION" | |
| ;; | |
| esac | |
| WORKFLOW_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| gh release create "$RELEASE_TAG" \ | |
| --draft \ | |
| $PRERELEASE_FLAG \ | |
| --title "$TITLE" \ | |
| --notes "Build [in progress](${WORKFLOW_URL})..." | |
| upload-s3: | |
| needs: | |
| [ | |
| set-metadata, | |
| build-macos, | |
| build-windows, | |
| build-linux, | |
| build-android, | |
| build-ios, | |
| ] | |
| if: | | |
| !cancelled() && | |
| (needs.build-macos.result == 'success' || needs.build-macos.result == 'skipped') && | |
| (needs.build-windows.result == 'success' || needs.build-windows.result == 'skipped') && | |
| (needs.build-linux.result == 'success' || needs.build-linux.result == 'skipped') && | |
| (needs.build-android.result == 'success' || needs.build-android.result == 'skipped') && | |
| (needs.build-ios.result == 'success' || needs.build-ios.result == 'skipped') && | |
| (needs.build-macos.result == 'success' || needs.build-windows.result == 'success' || | |
| needs.build-linux.result == 'success' || needs.build-android.result == 'success' || | |
| needs.build-ios.result == 'success') | |
| runs-on: ubuntu-latest | |
| env: | |
| BUILD_TYPE: ${{ needs.set-metadata.outputs.build_type }} | |
| RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} | |
| INSTALLER_BASE_NAME: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| PLATFORM: ${{ needs.set-metadata.outputs.platform }} | |
| LINUX_ARCH: ${{ needs.set-metadata.outputs.linux_arch }} | |
| BUCKET: ${{ vars.S3_RELEASES_BUCKET }} | |
| GITHUB_REF_NAME: ${{ github.ref_name }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| - name: Upload to S3 | |
| shell: bash | |
| run: | | |
| # Strip 'v' prefix for S3 paths | |
| VERSION="${RELEASE_TAG#v}" | |
| ./scripts/ci/publish-to-s3.sh \ | |
| "$BUILD_TYPE" \ | |
| "$VERSION" \ | |
| "$INSTALLER_BASE_NAME" \ | |
| "$PLATFORM" | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| - name: Format GitHub job summary | |
| shell: bash | |
| run: ./scripts/ci/format.sh job-summary >> "$GITHUB_STEP_SUMMARY" | |
| env: | |
| RELEASE_TAG: ${{ env.RELEASE_TAG }} | |
| - name: Format Slack message | |
| id: slack_msg | |
| env: | |
| RELEASE_TAG: ${{ env.RELEASE_TAG }} | |
| run: | | |
| text=$(./scripts/ci/format.sh slack) | |
| echo "text<<EOF" >> "$GITHUB_OUTPUT" | |
| echo "$text" >> "$GITHUB_OUTPUT" | |
| echo "EOF" >> "$GITHUB_OUTPUT" | |
| - name: Notify Slack | |
| uses: slackapi/slack-github-action@v2.0.0 | |
| with: | |
| webhook: ${{ secrets.SLACK_WEBHOOK_URL }} | |
| webhook-type: webhook-trigger | |
| payload: | | |
| { | |
| "text": "${{ steps.slack_msg.outputs.text }}" | |
| } | |
| upload-release-artifacts: | |
| needs: | |
| [ | |
| set-metadata, | |
| release-create, | |
| build-macos, | |
| build-windows, | |
| build-linux, | |
| build-android, | |
| build-ios, | |
| ] | |
| if: | | |
| !cancelled() && | |
| needs.release-create.result == 'success' && | |
| (needs.build-macos.result == 'success' || needs.build-macos.result == 'skipped') && | |
| (needs.build-windows.result == 'success' || needs.build-windows.result == 'skipped') && | |
| (needs.build-linux.result == 'success' || needs.build-linux.result == 'skipped') && | |
| (needs.build-android.result == 'success' || needs.build-android.result == 'skipped') && | |
| (needs.build-ios.result == 'success' || needs.build-ios.result == 'skipped') && | |
| (needs.build-macos.result == 'success' || needs.build-windows.result == 'success' || | |
| needs.build-linux.result == 'success' || needs.build-android.result == 'success' || | |
| needs.build-ios.result == 'success') | |
| runs-on: ubuntu-latest | |
| env: | |
| BUILD_TYPE: ${{ needs.set-metadata.outputs.build_type }} | |
| RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} | |
| INSTALLER_BASE_NAME: ${{ needs.set-metadata.outputs.installer_base_name }} | |
| PLATFORM: ${{ needs.set-metadata.outputs.platform }} | |
| LINUX_ARCH: ${{ needs.set-metadata.outputs.linux_arch }} | |
| BUCKET: ${{ vars.S3_RELEASES_BUCKET }} | |
| GITHUB_REF_NAME: ${{ github.ref_name }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| - name: Update release notes | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| GITHUB_SHA: ${{ github.sha }} | |
| RELEASE_TAG: ${{ env.RELEASE_TAG }} | |
| run: | | |
| # Generate release notes | |
| ./scripts/ci/format.sh release-notes > release_notes.md | |
| # Update the draft release with proper notes | |
| gh release edit "$RELEASE_TAG" --notes-file release_notes.md | |
| - name: Upload artifacts to GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Construct full filename with build type (Makefile appends it) | |
| FULL_NAME="${INSTALLER_BASE_NAME}" | |
| [[ -n "$BUILD_TYPE" && "$BUILD_TYPE" != "production" ]] && FULL_NAME="${FULL_NAME}-${BUILD_TYPE}" | |
| upload_if_exists() { | |
| local file="$1" | |
| if [[ -f "$file" ]]; then | |
| echo "Uploading $file to GitHub release..." | |
| gh release upload "$RELEASE_TAG" "$file" --clobber | |
| fi | |
| } | |
| upload_if_exists "lantern-installer-dmg/${FULL_NAME}.dmg" | |
| upload_if_exists "lantern-installer-exe/${FULL_NAME}.exe" | |
| upload_if_exists "lantern-installer-apk/${FULL_NAME}.apk" | |
| upload_if_exists "lantern-installer-deb-amd64/${FULL_NAME}.deb" | |
| upload_if_exists "lantern-installer-rpm-amd64/${FULL_NAME}.rpm" | |
| upload_if_exists "lantern-installer-deb-arm64/${FULL_NAME}-arm64.deb" | |
| upload_if_exists "lantern-installer-rpm-arm64/${FULL_NAME}-arm64.rpm" | |
| upload_if_exists "lantern-installer-deb/${FULL_NAME}.deb" | |
| upload_if_exists "lantern-installer-rpm/${FULL_NAME}.rpm" | |
| upload_if_exists "lantern-installer-ipa/${FULL_NAME}.ipa" | |
| # Post-publish steps (only for non-nightly production/beta builds) | |
| upload-google-play: | |
| needs: [set-metadata, build-android] | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.build-android.result == 'success' && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'android')) && | |
| (needs.set-metadata.outputs.build_type == 'beta' || needs.set-metadata.outputs.build_type == 'production') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download AAB artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: lantern-installer-aab | |
| path: aab | |
| - name: Download mapping.txt | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: play-mapping | |
| path: play | |
| - name: Download native debug symbols | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: play-debug-symbols | |
| path: play | |
| - name: Pick Play track | |
| id: track | |
| run: | | |
| bt="${{ needs.set-metadata.outputs.build_type }}" | |
| if [ "$bt" = "beta" ]; then | |
| echo "track=beta" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "track=production" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Upload to Google Play | |
| uses: r0adkll/upload-google-play@v1.1.3 | |
| with: | |
| serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} | |
| packageName: org.getlantern.lantern | |
| releaseFiles: aab/*.aab | |
| track: ${{ steps.track.outputs.track }} | |
| status: completed | |
| mappingFile: play/mapping.txt | |
| debugSymbols: play/debug-symbols.zip | |
| upload-testflight: | |
| needs: [set-metadata, build-ios] | |
| if: | | |
| !cancelled() && | |
| needs.set-metadata.result == 'success' && | |
| needs.build-ios.result == 'success' && | |
| (needs.set-metadata.outputs.platform == 'all' || contains(needs.set-metadata.outputs.platform, 'ios')) && | |
| (needs.set-metadata.outputs.build_type == 'beta' || needs.set-metadata.outputs.build_type == 'production') | |
| runs-on: macos-14 | |
| steps: | |
| - name: Download iOS Artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: lantern-installer-ipa | |
| path: . | |
| - name: Construct IPA filename | |
| id: filename | |
| shell: bash | |
| run: | | |
| FULL_NAME="${{ needs.set-metadata.outputs.installer_base_name }}" | |
| BUILD_TYPE="${{ needs.set-metadata.outputs.build_type }}" | |
| [[ -n "$BUILD_TYPE" && "$BUILD_TYPE" != "production" ]] && FULL_NAME="${FULL_NAME}-${BUILD_TYPE}" | |
| echo "ipa=${FULL_NAME}.ipa" >> "$GITHUB_OUTPUT" | |
| - name: Upload to TestFlight | |
| uses: apple-actions/upload-testflight-build@v3.0.1 | |
| with: | |
| app-path: ${{ steps.filename.outputs.ipa }} | |
| issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }} | |
| api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }} | |
| api-private-key: ${{ secrets.APPSTORE_API_PRIVATE_KEY }} | |
| update-appcast: | |
| needs: [set-metadata, release-finalize] | |
| if: | | |
| !cancelled() && | |
| needs.release-finalize.result == 'success' && | |
| (needs.set-metadata.outputs.platform == 'all' || | |
| contains(needs.set-metadata.outputs.platform, 'macos') || | |
| contains(needs.set-metadata.outputs.platform, 'windows') || | |
| contains(needs.set-metadata.outputs.platform, 'linux')) && | |
| needs.set-metadata.outputs.build_type != 'nightly' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Install Flutter | |
| uses: subosito/flutter-action@v2.22.0 | |
| with: | |
| channel: stable | |
| flutter-version-file: .github/flutter-version.yaml | |
| - name: Install Python dependencies | |
| run: python3 -m pip install -r scripts/requirements.txt | |
| - name: Update appcast.xml | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_REPOS_GH_TOKEN }} | |
| BUILD_TYPE: ${{ needs.set-metadata.outputs.build_type }} | |
| RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} | |
| BUCKET: ${{ vars.S3_RELEASES_BUCKET }} | |
| run: | | |
| # Strip 'v' prefix for S3 paths | |
| VERSION="${RELEASE_TAG#v}" | |
| python3 scripts/generate_appcast.py | |
| # appcast, like installers, always upload a versioned copy alongside "latest" | |
| aws s3 cp appcast.xml "s3://${BUCKET}/releases/${BUILD_TYPE}/${VERSION}/appcast.xml" --acl public-read | |
| aws s3 cp appcast.xml "s3://${BUCKET}/releases/${BUILD_TYPE}/latest/appcast.xml" --acl public-read | |
| if [[ "$BUILD_TYPE" == "production" ]]; then | |
| aws s3 cp appcast.xml "s3://${BUCKET}/releases/appcast.xml" --acl public-read | |
| fi | |
| # Upload appcast.xml to GitHub release, but | |
| # not git because we may be in detached HEAD | |
| gh release upload "$RELEASE_TAG" appcast.xml --clobber | |
| release-finalize: | |
| needs: | |
| [ | |
| set-metadata, | |
| upload-s3, | |
| upload-release-artifacts, | |
| upload-google-play, | |
| upload-testflight, | |
| ] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| env: | |
| RELEASE_TAG: ${{ needs.set-metadata.outputs.release_tag }} | |
| BUILD_TYPE: ${{ needs.set-metadata.outputs.build_type }} | |
| CLEANUP_ON_FAILURE: ${{ github.event.inputs.cleanup_on_failure != 'false' }} | |
| IS_TEST_RUN: ${{ needs.set-metadata.outputs.is_test_run }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Publish release on success | |
| if: | | |
| env.IS_TEST_RUN != 'true' && | |
| env.BUILD_TYPE != 'nightly' && | |
| needs.upload-s3.result == 'success' && | |
| needs.upload-release-artifacts.result == 'success' && | |
| (needs.upload-google-play.result == 'success' || needs.upload-google-play.result == 'skipped') && | |
| (needs.upload-testflight.result == 'success' || needs.upload-testflight.result == 'skipped') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| echo "All steps succeeded - publishing draft release" | |
| gh release edit "$RELEASE_TAG" --draft=false | |
| - name: Delete draft release | |
| if: | | |
| env.IS_TEST_RUN == 'true' || | |
| env.BUILD_TYPE == 'nightly' || | |
| (env.CLEANUP_ON_FAILURE == 'true' && | |
| !(needs.upload-s3.result == 'success' && | |
| needs.upload-release-artifacts.result == 'success' && | |
| (needs.upload-google-play.result == 'success' || needs.upload-google-play.result == 'skipped') && | |
| (needs.upload-testflight.result == 'success' || needs.upload-testflight.result == 'skipped'))) | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| if [[ "$IS_TEST_RUN" == "true" ]]; then | |
| echo "Test run from non-main branch - deleting draft release" | |
| elif [[ "$BUILD_TYPE" == "nightly" ]]; then | |
| echo "Nightly build - deleting draft release" | |
| else | |
| echo "Build failed or cancelled - cleaning up draft release" | |
| fi | |
| attempt=0 | |
| max_retries=5 | |
| while [ "$attempt" -lt "$max_retries" ]; do | |
| attempt=$((attempt + 1)) | |
| echo "Deleting release $RELEASE_TAG (attempt $attempt/$max_retries)..." | |
| if gh release delete "$RELEASE_TAG" --yes --cleanup-tag 2>&1; then | |
| echo "Release $RELEASE_TAG deleted" | |
| break | |
| fi | |
| if [ "$attempt" -eq "$max_retries" ]; then | |
| echo "Failed to delete release after $max_retries attempts" | |
| exit 1 | |
| fi | |
| sleep 3 | |
| done | |
| - name: Report failure (cleanup disabled) | |
| if: | | |
| env.IS_TEST_RUN != 'true' && | |
| env.BUILD_TYPE != 'nightly' && | |
| env.CLEANUP_ON_FAILURE != 'true' && | |
| !(needs.upload-s3.result == 'success' && | |
| needs.upload-release-artifacts.result == 'success' && | |
| (needs.upload-google-play.result == 'success' || needs.upload-google-play.result == 'skipped') && | |
| (needs.upload-testflight.result == 'success' || needs.upload-testflight.result == 'skipped')) | |
| run: | | |
| echo "Build failed or cancelled - draft release preserved for inspection" | |
| echo "Draft: https://github.com/getlantern/lantern/releases/tag/$RELEASE_TAG" | |
| exit 1 |