Skip to content

Commit 92a01a7

Browse files
committed
Merge remote-tracking branch 'origin/master' into feat/hw-wallet-activity
2 parents 6572e15 + 335038c commit 92a01a7

12 files changed

Lines changed: 334 additions & 35 deletions

File tree

.agents/commands/release.md

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: "Create a new release: bump version, create PR, build mainnet, tag, draft release"
2+
description: "Create a new release: bump version, create PR, run release workflow, tag, draft release"
33
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
44
---
55

@@ -77,6 +77,8 @@ Cherry-pick the commits you need onto this branch now, then continue.
7777
```
7878
Wait for the user to confirm they are done cherry-picking before proceeding.
7979

80+
If the base is a tag that predates the release workflow changes, port the current release workflow support onto the release branch before proceeding. At minimum, the release branch/tag must contain the updated artifact naming in `.github/workflows/release.yml`, otherwise Step 7 will dispatch an old workflow and then look for an artifact name it cannot produce.
81+
8082
Finalize changelog after the release branch contains all release commits:
8183

8284
```bash
@@ -202,22 +204,64 @@ gh release edit v{newVersionName} --notes-file /tmp/release-notes.md
202204

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

205-
### 7. Build Mainnet Release
207+
### 7. Run Store Release Workflow
206208

207209
```bash
208-
just release
210+
release_ref="v{newVersionName}"
211+
release_artifact_dir=".ai/release-artifacts-{newVersionName}"
212+
if ! git show "$release_ref:.github/workflows/release.yml" | grep -q 'bitkit-release-$build_number-$GITHUB_RUN_NUMBER'; then
213+
echo "Release ref $release_ref does not contain the current release artifact naming." >&2
214+
echo "Port the release workflow changes onto the release branch, retag, then rerun /release." >&2
215+
exit 1
216+
fi
217+
dispatch_started_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
218+
gh workflow run release.yml --ref "$release_ref"
219+
run_id=""
220+
for attempt in {1..30}; do
221+
run_id="$(gh run list \
222+
--workflow release.yml \
223+
--branch "$release_ref" \
224+
--event workflow_dispatch \
225+
--created ">=$dispatch_started_at" \
226+
--limit 1 \
227+
--json databaseId \
228+
--jq '.[0].databaseId // empty')"
229+
if [ -n "$run_id" ]; then
230+
break
231+
fi
232+
sleep 5
233+
done
234+
if [ -z "$run_id" ]; then
235+
echo "Failed to find release workflow run for $release_ref" >&2
236+
exit 1
237+
fi
238+
gh run watch "$run_id" --exit-status
239+
workflow_run_url="$(gh run view "$run_id" --json url --jq .url)"
240+
run_number="$(gh run view "$run_id" --json number --jq .number)"
241+
rm -rf "$release_artifact_dir"
242+
mkdir -p "$release_artifact_dir"
243+
gh run download "$run_id" \
244+
--name "bitkit-release-{newVersionCode}-${run_number}" \
245+
--dir "$release_artifact_dir"
246+
if command -v sha256sum >/dev/null; then
247+
(cd "$release_artifact_dir" && sha256sum -c SHA256SUMS.txt)
248+
else
249+
(cd "$release_artifact_dir" && shasum -a 256 -c SHA256SUMS.txt)
250+
fi
209251
```
210252

211-
Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk`
212-
Expected AAB path: `app/build/outputs/bundle/mainnetRelease/bitkit-mainnet-release-{newVersionCode}.aab`
253+
Expected APK path: `.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}-universal.apk`
254+
Expected AAB path: `.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}.aab`
255+
256+
Verify both files exist. If the workflow fails or the artifact checksum verification fails, stop and report the error to the user.
213257

214-
Verify both files exist. If the build fails, stop and report the error to the user.
258+
Store `workflow_run_url` for the summary.
215259

216-
### 8. Upload APK to Draft Release
260+
### 8. Upload Workflow APK to Draft Release
217261

218262
```bash
219263
gh release upload v{newVersionName} \
220-
app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk
264+
.ai/release-artifacts-{newVersionName}/bitkit-mainnet-release-{newVersionCode}-universal.apk
221265
```
222266

223267
### 9. Return to Master
@@ -235,14 +279,16 @@ Version bump PR: {PR URL}
235279
Release branch: release-{newVersionName}
236280
Tag: v{newVersionName}
237281
Draft release: {release URL}
282+
Release workflow: {workflow run URL}
283+
Artifacts: .ai/release-artifacts-{newVersionName}
238284
APK uploaded: bitkit-mainnet-release-{newVersionCode}-universal.apk
239285
Store release notes: .ai/release-notes-{newVersionName}.md
240286
241287
Next steps:
242288
- Share release notes with Jacobo for review
243-
- QA the APK
244-
- If patching the release branch: increment only versionCode, re-tag, rebuild, and re-upload
245-
- Submit to Play Store when QA passes
289+
- QA the workflow-built APK
290+
- Submit the workflow-built AAB to Play Store when QA passes
291+
- If patching the release branch: increment only versionCode, re-tag, rerun the release workflow, and re-upload
246292
- Publish the draft release on GitHub after store release
247293
- Merge release branch PR into master
248294
```

.github/workflows/release-internal.yml

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ concurrency:
1010
env:
1111
TERM: xterm-256color
1212
FORCE_COLOR: 1
13+
NDK_VERSION: 28.1.13356709
1314

1415
jobs:
1516
build-internal:
16-
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')
1717
runs-on: ubuntu-latest
1818
timeout-minutes: 45
1919
environment: release-internal
@@ -35,6 +35,16 @@ jobs:
3535
- name: Setup Gradle
3636
uses: gradle/actions/setup-gradle@v5
3737

38+
- name: Setup Android NDK
39+
run: |
40+
set -euo pipefail
41+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
42+
test -n "$android_sdk_root"
43+
sdkmanager_path="$android_sdk_root/cmdline-tools/latest/bin/sdkmanager"
44+
test -x "$sdkmanager_path"
45+
yes | "$sdkmanager_path" --licenses >/dev/null || true
46+
"$sdkmanager_path" --install "ndk;$NDK_VERSION"
47+
3848
- name: Decode mainnet release google-services.json
3949
env:
4050
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
@@ -65,6 +75,33 @@ jobs:
6575
KEY_PASSWORD: ${{ secrets.INTERNAL_KEY_PASSWORD }}
6676
run: ./gradlew assembleMainnetRelease --no-daemon --stacktrace
6777

78+
- name: Verify native libraries are stripped
79+
run: |
80+
set -euo pipefail
81+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
82+
test -n "$android_sdk_root"
83+
llvm_readelf_candidates=("$android_sdk_root/ndk/$NDK_VERSION"/toolchains/llvm/prebuilt/*/bin/llvm-readelf)
84+
llvm_readelf_path="${llvm_readelf_candidates[0]}"
85+
test -x "$llvm_readelf_path"
86+
87+
native_count=0
88+
extract_root="$(mktemp -d)"
89+
trap 'rm -rf "$extract_root"' EXIT
90+
while IFS= read -r -d '' artifact_path; do
91+
extract_dir="$extract_root/$(basename "$artifact_path")"
92+
mkdir -p "$extract_dir"
93+
unzip -q "$artifact_path" 'lib/*/*.so' -d "$extract_dir" 2>/dev/null || true
94+
while IFS= read -r -d '' native_path; do
95+
native_count=$((native_count + 1))
96+
if "$llvm_readelf_path" -S "$native_path" | grep -q '\.debug_'; then
97+
native_entry="${native_path#"$extract_dir"/}"
98+
echo "Native library contains debug sections: $artifact_path:$native_entry"
99+
exit 1
100+
fi
101+
done < <(find "$extract_dir" -name '*.so' -print0)
102+
done < <(find app/build/outputs/apk/mainnet/release -type f -name '*.apk' -print0)
103+
test "$native_count" -gt 0
104+
68105
- name: Verify internal release signature
69106
run: |
70107
set -euo pipefail
@@ -86,14 +123,25 @@ jobs:
86123
set -euo pipefail
87124
artifact_dir="$RUNNER_TEMP/internal-release"
88125
mkdir -p "$artifact_dir"
89-
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
90-
xargs -0 -I {} cp {} "$artifact_dir/"
91-
(cd "$artifact_dir" && sha256sum *.apk > SHA256SUMS.txt)
126+
mapfile -d '' apk_paths < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
127+
test "${#apk_paths[@]}" -gt 0
128+
129+
for apk_path in "${apk_paths[@]}"; do
130+
cp "$apk_path" "$artifact_dir/"
131+
done
132+
133+
apk_file="$(basename "${apk_paths[0]}")"
134+
build_number="${apk_file#bitkit-mainnet-release-}"
135+
build_number="${build_number%%-*}"
136+
[[ "$build_number" =~ ^[0-9]+$ ]]
137+
138+
(cd "$artifact_dir" && sha256sum -- *.apk > SHA256SUMS.txt)
139+
echo "artifact_name=bitkit-release-internal-$build_number-$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
92140
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"
93141
94142
- name: Upload internal artifacts
95143
uses: actions/upload-artifact@v6
96144
with:
97-
name: bitkit-internal-release-${{ github.run_number }}
145+
name: ${{ steps.artifacts.outputs.artifact_name }}
98146
path: ${{ steps.artifacts.outputs.artifact_dir }}
99147
retention-days: 30

.github/workflows/release.yml

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ concurrency:
1010
env:
1111
TERM: xterm-256color
1212
FORCE_COLOR: 1
13+
NDK_VERSION: 28.1.13356709
1314

1415
jobs:
1516
build-release:
16-
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')
17+
if: startsWith(github.ref, 'refs/tags/v')
1718
runs-on: ubuntu-latest
1819
timeout-minutes: 45
1920
environment: release
@@ -35,6 +36,16 @@ jobs:
3536
- name: Setup Gradle
3637
uses: gradle/actions/setup-gradle@v5
3738

39+
- name: Setup Android NDK
40+
run: |
41+
set -euo pipefail
42+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
43+
test -n "$android_sdk_root"
44+
sdkmanager_path="$android_sdk_root/cmdline-tools/latest/bin/sdkmanager"
45+
test -x "$sdkmanager_path"
46+
yes | "$sdkmanager_path" --licenses >/dev/null || true
47+
"$sdkmanager_path" --install "ndk;$NDK_VERSION"
48+
3849
- name: Decode mainnet release google-services.json
3950
env:
4051
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
@@ -65,6 +76,33 @@ jobs:
6576
KEY_PASSWORD: ${{ secrets.BITKIT_KEY_PASSWORD }}
6677
run: ./gradlew assembleMainnetRelease bundleMainnetRelease --no-daemon --stacktrace
6778

79+
- name: Verify native libraries are stripped
80+
run: |
81+
set -euo pipefail
82+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
83+
test -n "$android_sdk_root"
84+
llvm_readelf_candidates=("$android_sdk_root/ndk/$NDK_VERSION"/toolchains/llvm/prebuilt/*/bin/llvm-readelf)
85+
llvm_readelf_path="${llvm_readelf_candidates[0]}"
86+
test -x "$llvm_readelf_path"
87+
88+
native_count=0
89+
extract_root="$(mktemp -d)"
90+
trap 'rm -rf "$extract_root"' EXIT
91+
while IFS= read -r -d '' artifact_path; do
92+
extract_dir="$extract_root/$(basename "$artifact_path")"
93+
mkdir -p "$extract_dir"
94+
unzip -q "$artifact_path" 'lib/*/*.so' 'base/lib/*/*.so' -d "$extract_dir" 2>/dev/null || true
95+
while IFS= read -r -d '' native_path; do
96+
native_count=$((native_count + 1))
97+
if "$llvm_readelf_path" -S "$native_path" | grep -q '\.debug_'; then
98+
native_entry="${native_path#"$extract_dir"/}"
99+
echo "Native library contains debug sections: $artifact_path:$native_entry"
100+
exit 1
101+
fi
102+
done < <(find "$extract_dir" -name '*.so' -print0)
103+
done < <(find app/build/outputs/apk/mainnet/release app/build/outputs/bundle/mainnetRelease -type f \( -name '*.apk' -o -name '*.aab' \) -print0)
104+
test "$native_count" -gt 0
105+
68106
- name: Verify release signatures
69107
run: |
70108
set -euo pipefail
@@ -108,16 +146,30 @@ jobs:
108146
set -euo pipefail
109147
artifact_dir="$RUNNER_TEMP/release"
110148
mkdir -p "$artifact_dir"
111-
find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0 |
112-
xargs -0 -I {} cp {} "$artifact_dir/"
113-
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
114-
xargs -0 -I {} cp {} "$artifact_dir/"
115-
(cd "$artifact_dir" && sha256sum *.aab *.apk > SHA256SUMS.txt)
149+
mapfile -d '' bundle_paths < <(find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0)
150+
mapfile -d '' apk_paths < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
151+
test "${#bundle_paths[@]}" -gt 0
152+
test "${#apk_paths[@]}" -gt 0
153+
154+
for bundle_path in "${bundle_paths[@]}"; do
155+
cp "$bundle_path" "$artifact_dir/"
156+
done
157+
for apk_path in "${apk_paths[@]}"; do
158+
cp "$apk_path" "$artifact_dir/"
159+
done
160+
161+
apk_file="$(basename "${apk_paths[0]}")"
162+
build_number="${apk_file#bitkit-mainnet-release-}"
163+
build_number="${build_number%%-*}"
164+
[[ "$build_number" =~ ^[0-9]+$ ]]
165+
166+
(cd "$artifact_dir" && sha256sum -- *.aab *.apk > SHA256SUMS.txt)
167+
echo "artifact_name=bitkit-release-$build_number-$GITHUB_RUN_NUMBER" >> "$GITHUB_OUTPUT"
116168
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"
117169
118170
- name: Upload release artifacts
119171
uses: actions/upload-artifact@v6
120172
with:
121-
name: bitkit-release-${{ github.run_number }}
173+
name: ${{ steps.artifacts.outputs.artifact_name }}
122174
path: ${{ steps.artifacts.outputs.artifact_dir }}
123175
retention-days: 30

Justfile

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ set dotenv-filename := ".env"
33
set windows-shell := ["sh", "-cu"]
44

55
gradle := "./gradlew"
6+
ndk_ver := "28.1.13356709"
67

78
default:
89
@just list
@@ -45,16 +46,29 @@ init:
4546
compile:
4647
{{ gradle }} compileDevDebugKotlin
4748

48-
run mode="":
49+
run mode="" logs="":
4950
#!/usr/bin/env sh
5051
set -eu
5152
5253
app_id="to.bitkit.dev"
5354
app_dir="app/build/outputs/apk/dev/debug"
5455
mode="{{ mode }}"
56+
logs="{{ logs }}"
57+
attach_logs=false
5558
59+
if [ "$mode" = "logs" ]; then
60+
attach_logs=true
61+
mode=""
62+
fi
63+
if [ -n "$logs" ]; then
64+
if [ "$logs" != "logs" ]; then
65+
echo "usage: just run [docker] [logs]" >&2
66+
exit 1
67+
fi
68+
attach_logs=true
69+
fi
5670
if [ -n "$mode" ] && [ "$mode" != "docker" ]; then
57-
echo "usage: just run [docker]" >&2
71+
echo "usage: just run [docker] [logs]" >&2
5872
exit 1
5973
fi
6074
@@ -136,6 +150,11 @@ run mode="":
136150
adb -s "$device_id" shell am force-stop "$app_id"
137151
adb -s "$device_id" shell monkey -p "$app_id" -c android.intent.category.LAUNCHER 1 >/dev/null
138152
153+
if [ "$attach_logs" != "true" ]; then
154+
echo "Launched $app_id"
155+
exit 0
156+
fi
157+
139158
pid="$(
140159
adb -s "$device_id" shell pidof -s "$app_id" 2>/dev/null \
141160
| tr -d '\r' \
@@ -154,7 +173,7 @@ build task="assembleDevDebug":
154173
{{ gradle }} {{ task }}
155174

156175
release:
157-
{{ gradle }} assembleMainnetRelease bundleMainnetRelease
176+
NDK_VERSION={{ ndk_ver }} {{ gradle }} assembleMainnetRelease bundleMainnetRelease
158177

159178
install:
160179
{{ gradle }} installDevDebug

app/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ val e2eBackendEnv = System.getenv("E2E_BACKEND") ?: "local"
5050
val e2eHomegateUrlEnv = System.getenv("E2E_HOMEGATE_URL") ?: "http://127.0.0.1:6288"
5151
val trezorBridgeEnv = System.getenv("TREZOR_BRIDGE")?.toBoolean()?.toString() ?: "false"
5252
val trezorBridgeUrlEnv = System.getenv("TREZOR_BRIDGE_URL") ?: "http://10.0.2.2:21325"
53+
val requestedNdkVersion = System.getenv("NDK_VERSION")?.takeIf { it.isNotBlank() }
5354
val androidTestAnnotationPackage = "to.bitkit.test.annotations"
5455
val androidTestTaskPrefix = "connectedDevDebug"
5556
val androidTestTaskSuffix = "AndroidTest"
@@ -145,6 +146,7 @@ val bitkitAndroidTestAnnotation = bitkitAndroidTestAnnotationName?.let {
145146
android {
146147
namespace = "to.bitkit"
147148
compileSdk = 36
149+
requestedNdkVersion?.let { ndkVersion = it }
148150
defaultConfig {
149151
applicationId = "to.bitkit"
150152
minSdk = 28
@@ -230,6 +232,7 @@ android {
230232
)
231233
signingConfig = signingConfigs.getByName("release")
232234
ndk {
235+
debugSymbolLevel = "SYMBOL_TABLE"
233236
// noinspection ChromeOsAbiSupport
234237
abiFilters += listOf("armeabi-v7a", "arm64-v8a")
235238
}

0 commit comments

Comments
 (0)