Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions .agents/commands/release.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: "Create a new release: bump version, create PR, build mainnet, tag, draft release"
description: "Create a new release: bump version, create PR, run release workflow, tag, draft release"
allowed_tools: Bash, Read, Edit, Write, Glob, Grep, AskUserQuestion, mcp__github__create_pull_request, mcp__github__list_pull_requests, mcp__github__pull_request_read, mcp__github__get_file_contents, mcp__github__update_pull_request
---

Expand Down Expand Up @@ -202,22 +202,40 @@ gh release edit v{newVersionName} --notes-file /tmp/release-notes.md

Print the path to the release notes file so the user can share it for review.

### 7. Build Mainnet Release
### 7. Run Store Release Workflow

```bash
just release
release_ref="v{newVersionName}"
release_artifact_dir=".ai/release-artifacts-{newVersionName}"
run_url="$(gh workflow run release.yml --ref "$release_ref")"
run_id="${run_url##*/}"
Comment thread
ovitrif marked this conversation as resolved.
Outdated
gh run watch "$run_id" --exit-status
Comment thread
ovitrif marked this conversation as resolved.
Outdated
workflow_run_url="$(gh run view "$run_id" --json url --jq .url)"
run_number="$(gh run view "$run_id" --json number --jq .number)"
rm -rf "$release_artifact_dir"
mkdir -p "$release_artifact_dir"
gh run download "$run_id" \
--name "bitkit-release-{newVersionCode}-${run_number}" \
--dir "$release_artifact_dir"
if command -v sha256sum >/dev/null; then
(cd "$release_artifact_dir" && sha256sum -c SHA256SUMS.txt)
else
(cd "$release_artifact_dir" && shasum -a 256 -c SHA256SUMS.txt)
fi
```

Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk`
Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab`
Expected APK path: `.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}-universal.apk`
Expected AAB path: `.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}.aab`

Verify both files exist. If the build fails, stop and report the error to the user.
Verify both files exist. If the workflow fails or the artifact checksum verification fails, stop and report the error to the user.

### 8. Upload APK to Draft Release
Store `workflow_run_url` for the summary.

### 8. Upload Workflow APK to Draft Release

```bash
gh release upload v{newVersionName} \
app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk
.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}-universal.apk
```

### 9. Return to Master
Expand All @@ -235,14 +253,16 @@ Version bump PR: {PR URL}
Release branch: release-{newVersionName}
Tag: v{newVersionName}
Draft release: {release URL}
Release workflow: {workflow run URL}
Artifacts: .ai/release-artifacts-{newVersionName}
APK uploaded: bitkit-mainnet-release-{newVersionCode}-universal.apk
Store release notes: .ai/release-notes-{newVersionName}.md

Next steps:
- Share release notes with Jacobo for review
- QA the APK
- If patching the release branch: increment only versionCode, re-tag, rebuild, and re-upload
- Submit to Play Store when QA passes
- QA the workflow-built APK
- Submit the workflow-built AAB to Play Store when QA passes
- If patching the release branch: increment only versionCode, re-tag, rerun the release workflow, and re-upload
- Publish the draft release on GitHub after store release
- Merge release branch PR into master
```
56 changes: 52 additions & 4 deletions .github/workflows/release-internal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ concurrency:
env:
TERM: xterm-256color
FORCE_COLOR: 1
BITKIT_NDK_VERSION: 28.1.13356709

jobs:
build-internal:
Expand All @@ -35,6 +36,16 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5

- name: Setup Android NDK
run: |
set -euo pipefail
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
test -n "$android_sdk_root"
sdkmanager_path="$android_sdk_root/cmdline-tools/latest/bin/sdkmanager"
test -x "$sdkmanager_path"
yes | "$sdkmanager_path" --licenses >/dev/null || true
"$sdkmanager_path" --install "ndk;$BITKIT_NDK_VERSION"

- name: Decode mainnet release google-services.json
env:
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
Expand Down Expand Up @@ -65,6 +76,32 @@ jobs:
KEY_PASSWORD: ${{ secrets.INTERNAL_KEY_PASSWORD }}
run: ./gradlew assembleMainnetRelease --no-daemon --stacktrace

- name: Verify native libraries are stripped
run: |
set -euo pipefail
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
test -n "$android_sdk_root"
llvm_readelf_path="$(find "$android_sdk_root/ndk/$BITKIT_NDK_VERSION" -name llvm-readelf -print -quit)"
test -x "$llvm_readelf_path"

native_count=0
extract_root="$(mktemp -d)"
trap 'rm -rf "$extract_root"' EXIT
while IFS= read -r -d '' artifact_path; do
extract_dir="$extract_root/$(basename "$artifact_path")"
mkdir -p "$extract_dir"
unzip -q "$artifact_path" 'lib/*/*.so' -d "$extract_dir" 2>/dev/null || true
while IFS= read -r -d '' native_path; do
native_count=$((native_count + 1))
if "$llvm_readelf_path" -S "$native_path" | grep -q '\.debug_'; then
native_entry="${native_path#"$extract_dir"/}"
echo "Native library contains debug sections: $artifact_path:$native_entry"
exit 1
fi
done < <(find "$extract_dir" -name '*.so' -print0)
done < <(find app/build/outputs/apk/mainnet/release -type f -name '*.apk' -print0)
test "$native_count" -gt 0

- name: Verify internal release signature
run: |
set -euo pipefail
Expand All @@ -86,14 +123,25 @@ jobs:
set -euo pipefail
artifact_dir="$RUNNER_TEMP/internal-release"
mkdir -p "$artifact_dir"
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
xargs -0 -I {} cp {} "$artifact_dir/"
(cd "$artifact_dir" && sha256sum *.apk > SHA256SUMS.txt)
mapfile -d '' apk_paths < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
test "${#apk_paths[@]}" -gt 0

for apk_path in "${apk_paths[@]}"; do
cp "$apk_path" "$artifact_dir/"
done

apk_file="$(basename "${apk_paths[0]}")"
build_number="${apk_file#bitkit-mainnet-release-}"
build_number="${build_number%%-*}"
[[ "$build_number" =~ ^[0-9]+$ ]]

(cd "$artifact_dir" && sha256sum -- *.apk > SHA256SUMS.txt)
echo "artifact_name=bitkit-release-internal-$build_number-$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"

- name: Upload internal artifacts
uses: actions/upload-artifact@v6
with:
name: bitkit-internal-release-${{ github.run_number }}
name: ${{ steps.artifacts.outputs.artifact_name }}
path: ${{ steps.artifacts.outputs.artifact_dir }}
retention-days: 30
63 changes: 57 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ concurrency:
env:
TERM: xterm-256color
FORCE_COLOR: 1
BITKIT_NDK_VERSION: 28.1.13356709

jobs:
build-release:
Expand All @@ -35,6 +36,16 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5

- name: Setup Android NDK
run: |
set -euo pipefail
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
test -n "$android_sdk_root"
sdkmanager_path="$android_sdk_root/cmdline-tools/latest/bin/sdkmanager"
test -x "$sdkmanager_path"
yes | "$sdkmanager_path" --licenses >/dev/null || true
"$sdkmanager_path" --install "ndk;$BITKIT_NDK_VERSION"

- name: Decode mainnet release google-services.json
env:
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
Expand Down Expand Up @@ -65,6 +76,32 @@ jobs:
KEY_PASSWORD: ${{ secrets.BITKIT_KEY_PASSWORD }}
run: ./gradlew assembleMainnetRelease bundleMainnetRelease --no-daemon --stacktrace

- name: Verify native libraries are stripped
run: |
set -euo pipefail
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
test -n "$android_sdk_root"
llvm_readelf_path="$(find "$android_sdk_root/ndk/$BITKIT_NDK_VERSION" -name llvm-readelf -print -quit)"
Comment thread
ovitrif marked this conversation as resolved.
Outdated
test -x "$llvm_readelf_path"

native_count=0
extract_root="$(mktemp -d)"
trap 'rm -rf "$extract_root"' EXIT
while IFS= read -r -d '' artifact_path; do
extract_dir="$extract_root/$(basename "$artifact_path")"
mkdir -p "$extract_dir"
unzip -q "$artifact_path" 'lib/*/*.so' 'base/lib/*/*.so' -d "$extract_dir" 2>/dev/null || true
while IFS= read -r -d '' native_path; do
native_count=$((native_count + 1))
if "$llvm_readelf_path" -S "$native_path" | grep -q '\.debug_'; then
native_entry="${native_path#"$extract_dir"/}"
echo "Native library contains debug sections: $artifact_path:$native_entry"
exit 1
fi
done < <(find "$extract_dir" -name '*.so' -print0)
done < <(find app/build/outputs/apk/mainnet/release app/build/outputs/bundle/mainnetRelease -type f \( -name '*.apk' -o -name '*.aab' \) -print0)
test "$native_count" -gt 0

- name: Verify release signatures
run: |
set -euo pipefail
Expand Down Expand Up @@ -108,16 +145,30 @@ jobs:
set -euo pipefail
artifact_dir="$RUNNER_TEMP/release"
mkdir -p "$artifact_dir"
find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0 |
xargs -0 -I {} cp {} "$artifact_dir/"
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
xargs -0 -I {} cp {} "$artifact_dir/"
(cd "$artifact_dir" && sha256sum *.aab *.apk > SHA256SUMS.txt)
mapfile -d '' bundle_paths < <(find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0)
mapfile -d '' apk_paths < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
test "${#bundle_paths[@]}" -gt 0
test "${#apk_paths[@]}" -gt 0

for bundle_path in "${bundle_paths[@]}"; do
cp "$bundle_path" "$artifact_dir/"
done
for apk_path in "${apk_paths[@]}"; do
cp "$apk_path" "$artifact_dir/"
done

apk_file="$(basename "${apk_paths[0]}")"
build_number="${apk_file#bitkit-mainnet-release-}"
build_number="${build_number%%-*}"
[[ "$build_number" =~ ^[0-9]+$ ]]

(cd "$artifact_dir" && sha256sum -- *.aab *.apk > SHA256SUMS.txt)
echo "artifact_name=bitkit-release-$build_number-$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"

- name: Upload release artifacts
uses: actions/upload-artifact@v6
with:
name: bitkit-release-${{ github.run_number }}
name: ${{ steps.artifacts.outputs.artifact_name }}
path: ${{ steps.artifacts.outputs.artifact_dir }}
retention-days: 30
3 changes: 2 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set dotenv-filename := ".env"
set windows-shell := ["sh", "-cu"]

gradle := "./gradlew"
android_ndk_version := "28.1.13356709"

default:
@just list
Expand Down Expand Up @@ -154,7 +155,7 @@ build task="assembleDevDebug":
{{ gradle }} {{ task }}

release:
{{ gradle }} assembleMainnetRelease bundleMainnetRelease
BITKIT_NDK_VERSION={{ android_ndk_version }} {{ gradle }} assembleMainnetRelease bundleMainnetRelease

install:
{{ gradle }} installDevDebug
Expand Down
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ val e2eBackendEnv = System.getenv("E2E_BACKEND") ?: "local"
val e2eHomegateUrlEnv = System.getenv("E2E_HOMEGATE_URL") ?: "http://127.0.0.1:6288"
val trezorBridgeEnv = System.getenv("TREZOR_BRIDGE")?.toBoolean()?.toString() ?: "false"
val trezorBridgeUrlEnv = System.getenv("TREZOR_BRIDGE_URL") ?: "http://10.0.2.2:21325"
val bitkitNdkVersion = System.getenv("BITKIT_NDK_VERSION")?.takeIf { it.isNotBlank() }
val androidTestAnnotationPackage = "to.bitkit.test.annotations"
val androidTestTaskPrefix = "connectedDevDebug"
val androidTestTaskSuffix = "AndroidTest"
Expand Down Expand Up @@ -145,6 +146,7 @@ val bitkitAndroidTestAnnotation = bitkitAndroidTestAnnotationName?.let {
android {
namespace = "to.bitkit"
compileSdk = 36
bitkitNdkVersion?.let { ndkVersion = it }
defaultConfig {
applicationId = "to.bitkit"
minSdk = 28
Expand Down
Loading