mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
ci(release): refactor release workflow to be callable (#3330)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
f2b4b87944
commit
015bf123b2
3 changed files with 176 additions and 90 deletions
77
.github/workflows/create-or-promote-release.yml
vendored
Normal file
77
.github/workflows/create-or-promote-release.yml
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
name: Create or Promote Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
base_version:
|
||||
description: 'Base version for the release (e.g., 2.3.0)'
|
||||
required: true
|
||||
channel:
|
||||
description: 'The channel to create a release for or promote to'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- internal
|
||||
- closed
|
||||
- open
|
||||
- production
|
||||
dry_run:
|
||||
description: 'If true, calculates the tag but does not push it or start the release'
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create-tag:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
new_tag: ${{ steps.calculate_new_tag.outputs.new_tag }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Calculate new release tag
|
||||
id: calculate_new_tag
|
||||
run: |
|
||||
BASE_VERSION="${{ inputs.base_version }}"
|
||||
CHANNEL="${{ inputs.channel }}"
|
||||
|
||||
if [[ "$CHANNEL" == "production" ]]; then
|
||||
# Production tags are simple, without a channel or increment
|
||||
NEW_TAG="v${BASE_VERSION}"
|
||||
else
|
||||
# Pre-release channels get an incrementing number
|
||||
LATEST_TAG=$(git tag --list "v${BASE_VERSION}-${CHANNEL}.*" --sort=-v:refname | head -n 1)
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
INCREMENT=1
|
||||
else
|
||||
INCREMENT=$(echo "$LATEST_TAG" | sed -n "s/.*-${CHANNEL}\.\([0-9]*\)/\1/p" | awk '{print $1+1}')
|
||||
fi
|
||||
|
||||
NEW_TAG="v${BASE_VERSION}-${CHANNEL}.${INCREMENT}"
|
||||
fi
|
||||
|
||||
echo "Calculated new tag: $NEW_TAG"
|
||||
echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create and push new tag
|
||||
if: ${{ !inputs.dry_run }}
|
||||
run: |
|
||||
git tag ${{ steps.calculate_new_tag.outputs.new_tag }}
|
||||
git push origin ${{ steps.calculate_new_tag.outputs.new_tag }}
|
||||
|
||||
call-release-workflow:
|
||||
if: ${{ !inputs.dry_run }}
|
||||
needs: create-tag
|
||||
uses: ./.github/workflows/release.yml
|
||||
with:
|
||||
tag_name: ${{ needs.create-tag.outputs.new_tag }}
|
||||
release_type: ${{ inputs.channel == 'internal' && 'internal' || 'promotion' }}
|
||||
secrets: inherit
|
||||
96
.github/workflows/release.yml
vendored
96
.github/workflows/release.yml
vendored
|
|
@ -1,13 +1,38 @@
|
|||
name: Make Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'The tag that triggered the release'
|
||||
required: true
|
||||
type: string
|
||||
release_type:
|
||||
description: 'Type of release (internal or promotion)'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
GSERVICES:
|
||||
required: true
|
||||
KEYSTORE:
|
||||
required: true
|
||||
KEYSTORE_FILENAME:
|
||||
required: true
|
||||
KEYSTORE_PROPERTIES:
|
||||
required: true
|
||||
DATADOG_APPLICATION_ID:
|
||||
required: true
|
||||
DATADOG_CLIENT_TOKEN:
|
||||
required: true
|
||||
GOOGLE_MAPS_API_KEY:
|
||||
required: true
|
||||
GOOGLE_PLAY_JSON_KEY:
|
||||
required: true
|
||||
GRADLE_ENCRYPTION_KEY:
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: ${{ github.workflow }}-${{ inputs.tag_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
|
|
@ -26,6 +51,7 @@ jobs:
|
|||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 21
|
||||
|
|
@ -43,7 +69,7 @@ jobs:
|
|||
|
||||
- name: Determine Version Name from Tag
|
||||
id: get_version_name
|
||||
run: echo "APP_VERSION_NAME=$(echo ${GITHUB_REF_NAME#v} | sed 's/-.*//')" >> $GITHUB_OUTPUT
|
||||
run: echo "APP_VERSION_NAME=$(echo ${{ inputs.tag_name }} | sed 's/-.*//' | sed 's/v//')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract VERSION_CODE_OFFSET from config.properties
|
||||
id: get_version_code_offset
|
||||
|
|
@ -59,15 +85,16 @@ jobs:
|
|||
VERSION_CODE=$((COMMIT_COUNT + OFFSET))
|
||||
echo "versionCode=$VERSION_CODE" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
# This matches the reproducible versionCode strategy: versionCode = git commit count + offset
|
||||
|
||||
release-google:
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare-build-info
|
||||
environment: Release
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 21
|
||||
|
|
@ -76,7 +103,6 @@ jobs:
|
|||
java-version: '21'
|
||||
distribution: 'jetbrains'
|
||||
- name: Setup Gradle
|
||||
if: contains(github.ref_name, '-internal')
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
|
||||
|
|
@ -95,7 +121,7 @@ jobs:
|
|||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
GOOGLE_PLAY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
|
||||
run: |
|
||||
rm -f ./app/google-services.json # Ensure clean state
|
||||
rm -f ./app/google-services.json
|
||||
echo $GSERVICES > ./app/google-services.json
|
||||
echo $KEYSTORE | base64 -di > ./app/$KEYSTORE_FILENAME
|
||||
echo "$KEYSTORE_PROPERTIES" > ./keystore.properties
|
||||
|
|
@ -113,12 +139,11 @@ jobs:
|
|||
- name: Determine Fastlane Lane
|
||||
id: fastlane_lane
|
||||
run: |
|
||||
TAG_NAME="${{ github.ref_name }}"
|
||||
if [[ "$TAG_NAME" == *"-internal"* ]]; then
|
||||
if [[ "${{ inputs.tag_name }}" == *"-internal"* ]]; then
|
||||
echo "lane=internal" >> $GITHUB_OUTPUT
|
||||
elif [[ "$TAG_NAME" == *"-closed"* ]]; then
|
||||
elif [[ "${{ inputs.tag_name }}" == *"-closed"* ]]; then
|
||||
echo "lane=closed" >> $GITHUB_OUTPUT
|
||||
elif [[ "$TAG_NAME" == *"-open"* ]]; then
|
||||
elif [[ "${{ inputs.tag_name }}" == *"-open"* ]]; then
|
||||
echo "lane=open" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "lane=production" >> $GITHUB_OUTPUT
|
||||
|
|
@ -131,7 +156,6 @@ jobs:
|
|||
run: bundle exec fastlane ${{ steps.fastlane_lane.outputs.lane }}
|
||||
|
||||
- name: Upload Google AAB artifact
|
||||
if: contains(github.ref_name, '-internal')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: google-aab
|
||||
|
|
@ -139,7 +163,6 @@ jobs:
|
|||
retention-days: 1
|
||||
|
||||
- name: Upload Google APK artifact
|
||||
if: contains(github.ref_name, '-internal')
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: google-apk
|
||||
|
|
@ -147,7 +170,6 @@ jobs:
|
|||
retention-days: 1
|
||||
|
||||
- name: Attest Google artifacts provenance
|
||||
if: contains(github.ref_name, '-internal')
|
||||
uses: actions/attest-build-provenance@v3
|
||||
with:
|
||||
subject-path: |
|
||||
|
|
@ -155,13 +177,14 @@ jobs:
|
|||
app/build/outputs/apk/google/release/app-google-release.apk
|
||||
|
||||
release-fdroid:
|
||||
if: contains(github.ref_name, '-internal')
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare-build-info
|
||||
environment: Release
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ inputs.tag_name }}
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 21
|
||||
|
|
@ -210,41 +233,26 @@ jobs:
|
|||
with:
|
||||
subject-path: app/build/outputs/apk/fdroid/release/app-fdroid-release.apk
|
||||
|
||||
create-internal-release:
|
||||
github-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare-build-info, release-google, release-fdroid]
|
||||
if: contains(github.ref_name, '-internal')
|
||||
environment: Release
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: ./artifacts
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
|
||||
name: ${{ github.ref_name }}
|
||||
generate_release_notes: true
|
||||
files: ./artifacts/*/*
|
||||
draft: true
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
promote-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare-build-info, release-google]
|
||||
if: "!contains(github.ref_name, '-internal')"
|
||||
steps:
|
||||
- name: Determine Release Properties
|
||||
id: release_properties
|
||||
run: |
|
||||
TAG_NAME="${{ github.ref_name }}"
|
||||
if [[ "$TAG_NAME" == *"-closed"* ]]; then
|
||||
if [[ "${{ inputs.release_type }}" == "internal" ]]; then
|
||||
echo "draft=true" >> $GITHUB_OUTPUT
|
||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ inputs.tag_name }}" == *"-closed"* ]]; then
|
||||
echo "draft=false" >> $GITHUB_OUTPUT
|
||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "$TAG_NAME" == *"-open"* ]]; then
|
||||
elif [[ "${{ inputs.tag_name }}" == *"-open"* ]]; then
|
||||
echo "draft=false" >> $GITHUB_OUTPUT
|
||||
echo "prerelease=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
|
|
@ -252,12 +260,12 @@ jobs:
|
|||
echo "prerelease=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Update GitHub Release
|
||||
- name: Create or Update GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
|
||||
name: ${{ github.ref_name }}
|
||||
tag_name: ${{ inputs.tag_name }}
|
||||
name: ${{ inputs.tag_name }}
|
||||
generate_release_notes: true
|
||||
files: ./artifacts/*/*
|
||||
draft: ${{ steps.release_properties.outputs.draft }}
|
||||
prerelease: ${{ steps.release_properties.outputs.prerelease }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,59 @@
|
|||
# Meshtastic-Android Release Process (Condensed)
|
||||
# Meshtastic-Android Release Process
|
||||
|
||||
This guide summarizes the steps for releasing a new version of Meshtastic-Android. The process is automated via GitHub Actions and Fastlane, triggered by pushing a Git tag from a `release/*` branch.
|
||||
This guide summarizes the steps for releasing a new version of Meshtastic-Android. The process is fully automated via GitHub Actions and Fastlane.
|
||||
|
||||
## Overview
|
||||
- **Tagging:** Push a tag (`vX.X.X[-track.Y]`) from a `release/*` branch to start the release workflow.
|
||||
- **CI Automation:** Builds both flavors, uploads to Google Play (correct track), and creates/updates a draft GitHub release.
|
||||
- **Changelog:** Release notes are auto-generated from PR labels via [`.github/release.yml`](.github/release.yml). Label PRs for accurate changelogs.
|
||||
- **Draft Release:** All tags for the same base version (e.g., `v2.3.5`) update the same draft release. The release title uses the full tag (e.g., `v2.3.5-internal.1`).
|
||||
|
||||
## Tagging & Tracks
|
||||
- **Internal:** `vX.X.X-internal.Y`
|
||||
- **Closed:** `vX.X.X-closed.Y`
|
||||
- **Open:** `vX.X.X-open.Y`
|
||||
- **Production:** `vX.X.X`
|
||||
- Increment `.Y` for fixes/iterations.
|
||||
The entire release process is managed by a single, manually-triggered GitHub Action: **`Create or Promote Release`**.
|
||||
|
||||
- **Trigger:** To start a new release or promote an existing one, a developer manually runs the workflow from the GitHub Actions tab.
|
||||
- **Inputs:** The workflow requires two inputs:
|
||||
1. `version`: The base version number you are releasing (e.g., `2.4.0`).
|
||||
2. `channel`: The release channel you are targeting (`internal`, `closed`, `open`, or `production`).
|
||||
- **Automation:** The workflow handles everything automatically:
|
||||
- Calculates the correct Git tag based on the channel (e.g., `v2.4.0-internal.1` or `v2.4.0`).
|
||||
- Pushes the new tag to the repository.
|
||||
- Calls a reusable workflow that builds the app, deploys it to the correct Google Play track, and attaches the artifacts (`.aab`/`.apk`) to a GitHub Release.
|
||||
- **Changelog:** Release notes are auto-generated from PR labels. Ensure PRs are labeled correctly to maintain an accurate changelog.
|
||||
|
||||
## Release Steps
|
||||
1. **Branch:** Create `release/X.X.X` from `main`. Only critical fixes allowed.
|
||||
2. **Tag & Push:** Tag the release commit and push (see below).
|
||||
3. **CI:** Wait for CI to finish. Artifacts are uploaded, and a draft GitHub release is created/updated.
|
||||
4. **Verify:** Check Google Play Console and GitHub draft release.
|
||||
5. **Promote:** Tag the same commit for the next track as needed.
|
||||
6. **Finalize:**
|
||||
- **Production:** Publish the GitHub release, then promote in Google Play Console.
|
||||
- **Other tracks:** Verify with testers.
|
||||
7. **Merge:** After production, merge `release/X.X.X` back to `main` and delete the branch.
|
||||
|
||||
## Tagging Example
|
||||
```bash
|
||||
# On release branch
|
||||
git tag v2.3.5-internal.1
|
||||
git push origin v2.3.5-internal.1
|
||||
# For fixes:
|
||||
git tag v2.3.5-internal.2
|
||||
git push origin v2.3.5-internal.2
|
||||
# Promote:
|
||||
git tag v2.3.5-closed.1
|
||||
git push origin v2.3.5-closed.1
|
||||
```
|
||||
### 1. Start an Internal Release
|
||||
|
||||
## Manual Checklist
|
||||
- [ ] Verify build in Google Play Console
|
||||
- [ ] Review and publish GitHub draft release (for production)
|
||||
- [ ] Merge release branch to main after production
|
||||
- [ ] Label PRs for changelog accuracy
|
||||
1. Navigate to the **Actions** tab in the GitHub repository.
|
||||
2. Select the **`Create or Promote Release`** workflow.
|
||||
3. Click the **"Run workflow"** dropdown.
|
||||
4. Enter the base `version` (e.g., `2.4.0`).
|
||||
5. Select the `internal` channel.
|
||||
6. Click **"Run workflow"**.
|
||||
|
||||
The workflow will create an incremental internal tag (e.g., `v2.4.0-internal.1`) and publish a **draft** pre-release on GitHub.
|
||||
|
||||
### 2. Promote to the Next Channel
|
||||
|
||||
Once an internal build has been verified, you can promote it to a wider audience.
|
||||
|
||||
1. Run the **`Create or Promote Release`** workflow again with the same base `version`.
|
||||
2. Select the next channel in the sequence (e.g., `closed`, then `open`).
|
||||
3. The workflow will create a new incremental tag for that channel (e.g., `v2.4.0-closed.1`) and create a **published** pre-release on GitHub.
|
||||
|
||||
### 3. Promote to Production
|
||||
|
||||
After testing is complete on all pre-release channels, you can create the final public release.
|
||||
|
||||
1. Run the **`Create or Promote Release`** workflow one last time.
|
||||
2. Use the same base `version`.
|
||||
3. Select the `production` channel.
|
||||
4. The workflow will create a clean version tag (e.g., `v2.4.0`) and create a **published, stable** (non-prerelease) release on GitHub.
|
||||
|
||||
### 4. Post-Release
|
||||
|
||||
1. **Verify:** Check the Google Play Console to ensure the build is available on the correct track.
|
||||
2. **Merge:** Merge the release branch (if one was used for stabilization) back into `main`.
|
||||
|
||||
## Build Attestations & Provenance
|
||||
|
||||
All release artifacts are accompanied by explicit GitHub build attestations (provenance). After each artifact is uploaded in the release workflow, a provenance attestation is generated using the `actions/attest-build-provenance` action. This provides cryptographic proof that the artifacts were built by our trusted GitHub Actions workflow, ensuring supply chain integrity and allowing users to verify the origin of each release.
|
||||
All release artifacts are accompanied by explicit GitHub build attestations (provenance). This provides cryptographic proof that the artifacts were built by our trusted GitHub Actions workflow, ensuring supply chain integrity.
|
||||
|
||||
- Attestations are generated immediately after each artifact upload in the workflow.
|
||||
- You can view and verify provenance in the GitHub UI under each release asset.
|
||||
- For more details, see [GitHub's documentation on build provenance](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#provenance-attestations).
|
||||
|
||||
> **Note:** The GitHub release is always attached to the base version tag (e.g., `v2.3.5`). All track tags for the same version update the same draft release. Look for the draft under the base version tag.
|
||||
|
||||
> **Best Practice:** Always promote the last verified build from the previous track to the next track. Do not introduce new changes between tracks unless absolutely necessary. This ensures consistency, traceability, and minimizes risk.
|
||||
- You can view and verify provenance in the GitHub UI under each release asset.
|
||||
- For more details, see [GitHub's documentation on build provenance](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#provenance-attestations).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue