-
Notifications
You must be signed in to change notification settings - Fork 248
369 lines (330 loc) · 14.2 KB
/
Copy pathgradle.yml
File metadata and controls
369 lines (330 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
name: CI
on:
push:
branches:
- master
- classic
tags:
- v*.*.*
pull_request:
branches:
- master
- classic
paths-ignore:
- '**.md'
- '**.txt'
- 'renovate.json'
- '.editorconfig'
- '.gitignore'
- '.github/**'
- '.idea/**'
- '!.github/workflows/**'
workflow_dispatch:
env:
ORIGINAL_PROJECT: "CeuiLiSA/Pixiv-Shaft"
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build apk
runs-on: ubuntu-24.04
steps:
- name: Clone repo
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4.2.2
with:
java-version: '21'
distribution: 'adopt'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Grant execute permission for gradlew
run: chmod +x gradlew
# 签名材料从 Secrets 注入(keystore 与密码已从仓库移除)。
# keystore 解码到 $RUNNER_TEMP(仓库外)——历史 tag 的提交里还跟踪着
# keystore.jks,写进仓库根目录会让后面的 git checkout 撞 untracked 冲突。
# keystore.properties 从未被任何提交跟踪,放根目录安全。
# fork 的 PR 拿不到 Secrets → 跳过,build.gradle 降级为 debug 默认签名。
- name: Set up signing material
env:
KEYSTORE_B64: ${{ secrets.SHAFT_KEYSTORE_B64 }}
KEYSTORE_PASSWORD: ${{ secrets.SHAFT_KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.SHAFT_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.SHAFT_KEY_PASSWORD }}
run: |
if [ -n "$KEYSTORE_B64" ]; then
echo "$KEYSTORE_B64" | base64 -d > "$RUNNER_TEMP/keystore.jks"
{
echo "storeFile=$RUNNER_TEMP/keystore.jks"
echo "storePassword=$KEYSTORE_PASSWORD"
echo "keyAlias=$KEY_ALIAS"
echo "keyPassword=$KEY_PASSWORD"
} > keystore.properties
echo "Signing material ready"
else
echo "No signing secrets (fork build), falling back to default debug signing"
fi
- name: Detect branch
if: github.ref_type == 'branch'
run: |
BRANCH_NAME=${GITHUB_REF#refs/heads/}
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
if [ "$BRANCH_NAME" = "master" ]; then
echo "APK_SUFFIX=master" >> $GITHUB_ENV
elif [ "$BRANCH_NAME" = "classic" ]; then
echo "APK_SUFFIX=classic" >> $GITHUB_ENV
fi
# 官方仓库的包必须带正确的 HMAC 密钥,否则广场/聊天会被 shaft-api-v2 401,
# 而 build.gradle 对空密钥是静默降级的(fork 友好),CI 不会自己红。
# 这里只比对 sha256 指纹:密钥是 256 bit 随机值,公开指纹无法反推。
# 指纹来源:服务器 /etc/shaft-api-v2/events-hmac-secret,`tr -d '\n' | sha256sum`。
# 轮换密钥时同步更新这里和 GitHub Secret SHAFT_EVENTS_HMAC。
# fork 发来的 PR 同样拿不到 Secrets,跳过。
- name: Verify SHAFT_EVENTS_HMAC secret
if: github.repository == env.ORIGINAL_PROJECT && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
env:
SHAFT_EVENTS_HMAC: ${{ secrets.SHAFT_EVENTS_HMAC }}
EXPECTED_FINGERPRINT: e0f22bd504573b61863577d1369fc6c4c2e6dceab92cd5baddc3b9fe7fcfde6e
run: |
if [ -z "$SHAFT_EVENTS_HMAC" ]; then
echo "::error::SHAFT_EVENTS_HMAC secret is empty; the APK would sign with an empty key"
exit 1
fi
ACTUAL=$(printf '%s' "$SHAFT_EVENTS_HMAC" | sha256sum | awk '{print $1}')
if [ "$ACTUAL" != "$EXPECTED_FINGERPRINT" ]; then
echo "::error::SHAFT_EVENTS_HMAC fingerprint mismatch: got $ACTUAL"
exit 1
fi
echo "SHAFT_EVENTS_HMAC fingerprint OK"
# 单测跑在打包之前,红了就不出产物。只跑 debug 变体:release 变体的
# 测试代码相同,多跑一遍只是白费 R8 前的编译时间。
- name: Run unit tests
env:
SHAFT_EVENTS_HMAC: ${{ secrets.SHAFT_EVENTS_HMAC }}
run: >-
./gradlew
:app:testGithubDebugUnitTest
:actionqueue:testDebugUnitTest
:feeds:testDebugUnitTest
:websocket:testDebugUnitTest
--continue
# Room 会在编译时自动写出当前 schema;如果忘记提交,迁移测试会消费刚生成的
# 文件并照样通过,最终却丢失可供未来迁移使用的版本历史。
- name: Verify Room schemas are committed
run: |
changes="$(git status --porcelain --untracked-files=all -- app/schemas)"
if [ -n "$changes" ]; then
echo "::error::Room schemas changed during compilation; regenerate and commit app/schemas"
printf '%s\n' "$changes"
exit 1
fi
# app lint 只分析宿主自身;各 library 必须显式列出,才能阻止模块内新问题绕过门禁。
- name: Run Android lint
run: >-
./gradlew
:app:lintGithubDebug
:actionqueue:lintDebug
:feeds:lintDebug
:websocket:lintDebug
:models:lintDebug
:flowlayout-lib:lintDebug
:progressmanager:lintDebug
:witstudio:lintDebug
--continue
# progressmanager 历史配置了 abortOnError=false;统一检查 XML,避免该模块的
# lint Error 被 Gradle 任务吞掉后让 CI 误报成功。
- name: Verify lint reports are error-free
run: |
failed=0
reports=(
app/build/reports/lint-results-githubDebug.xml
actionqueue/build/reports/lint-results-debug.xml
feeds/build/reports/lint-results-debug.xml
websocket/build/reports/lint-results-debug.xml
models/build/reports/lint-results-debug.xml
flowlayout-lib/build/reports/lint-results-debug.xml
progressmanager/build/reports/lint-results-debug.xml
witstudio/build/reports/lint-results-debug.xml
)
for report in "${reports[@]}"; do
if [ ! -f "$report" ]; then
echo "::error file=$report::Expected lint XML report was not generated"
failed=1
elif grep -Eq 'severity="(Error|Fatal)"' "$report"; then
echo "::error file=$report::Android lint reported an error"
failed=1
fi
done
exit "$failed"
- name: Upload lint reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: lint-reports-${{ github.run_id }}
path: '**/build/reports/lint-results*'
if-no-files-found: ignore
- name: Upload unit test reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: unit-test-reports-${{ github.run_id }}
path: |
**/build/reports/tests/
**/build/test-results/
if-no-files-found: ignore
- name: Build debug apk
if: github.ref_type == 'branch'
env:
SHAFT_EVENTS_HMAC: ${{ secrets.SHAFT_EVENTS_HMAC }}
run: ./gradlew assembleGithubDebug
- name: Upload debug artifact
if: github.ref_type == 'branch'
uses: actions/upload-artifact@v4
with:
name: apk-debug-${{ env.APK_SUFFIX }}
path: app/build/outputs/apk/github/debug
- name: Get tag name
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
run: |
set -x
version_tag=${GITHUB_REF/refs\/tags\//}
version_tag_short=`echo $version_tag | awk 'NR==1,/v/{sub(/v/, "");print}'`
echo "VERSION_TAG=$version_tag" >> $GITHUB_ENV
echo "VERSION_TAG_SHORT=$version_tag_short" >> $GITHUB_ENV
- name: Extract tag message before checkout
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
run: |
TAG_MESSAGE=$(git cat-file tag "$VERSION_TAG" 2>/dev/null | sed '1,/^$/d' || echo "")
if [ -z "$TAG_MESSAGE" ]; then
TAG_MESSAGE="Release $VERSION_TAG"
fi
# Persist to file since env vars lose multiline
echo "$TAG_MESSAGE" > /tmp/tag_message.txt
- name: Detect and checkout correct branch
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
run: |
# Auto-detect which branch this tagged commit belongs to
TAG_COMMIT=$(git rev-parse HEAD)
CLASSIC_CONTAINS=$(git merge-base --is-ancestor "$TAG_COMMIT" origin/classic && echo yes || echo no)
MASTER_CONTAINS=$(git merge-base --is-ancestor "$TAG_COMMIT" origin/master && echo yes || echo no)
if [ "$CLASSIC_CONTAINS" = "yes" ] && [ "$MASTER_CONTAINS" = "no" ]; then
TAG_BRANCH=classic
elif [ "$MASTER_CONTAINS" = "yes" ] && [ "$CLASSIC_CONTAINS" = "no" ]; then
TAG_BRANCH=master
elif [ "$CLASSIC_CONTAINS" = "yes" ] && [ "$MASTER_CONTAINS" = "yes" ]; then
# Commit exists on both branches, pick the one whose tip is closer
CLASSIC_DIST=$(git rev-list --count "$TAG_COMMIT"..origin/classic)
MASTER_DIST=$(git rev-list --count "$TAG_COMMIT"..origin/master)
if [ "$CLASSIC_DIST" -le "$MASTER_DIST" ]; then
TAG_BRANCH=classic
else
TAG_BRANCH=master
fi
else
TAG_BRANCH=master
fi
echo "TAG_BRANCH=$TAG_BRANCH" >> $GITHUB_ENV
echo "Detected branch: $TAG_BRANCH"
git checkout "origin/$TAG_BRANCH"
git submodule update --init --recursive
- name: Build release apk
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
env:
SHAFT_EVENTS_HMAC: ${{ secrets.SHAFT_EVENTS_HMAC }}
run: ./gradlew assembleGithubRelease --stacktrace
- name: Deal with release apk
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
run: |
cp app/build/outputs/apk/github/release/app-github-release.apk app-release.apk
if [ "$TAG_BRANCH" = "classic" ]; then
APK_NAME="PixShaft_${VERSION_TAG_SHORT}_classic.apk"
else
APK_NAME="PixShaft_${VERSION_TAG_SHORT}.apk"
fi
cp app-release.apk "$APK_NAME"
echo "APK_NAME=$APK_NAME" >> $GITHUB_ENV
sha1=`sha1sum app-release.apk | awk '{ print toupper($1) }'`
sha256=`sha256sum app-release.apk | awk '{ print toupper($1) }'`
echo "APK_SHA1=$sha1" >> $GITHUB_ENV
echo "APK_SHA256=$sha256" >> $GITHUB_ENV
- name: Prepare release notes
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
run: |
TAG_MESSAGE=$(cat /tmp/tag_message.txt)
# Write release notes with SHA checksums
{
echo "$TAG_MESSAGE"
echo ""
echo '```'
echo "SHA-1: $APK_SHA1"
echo "SHA-256: $APK_SHA256"
echo '```'
} > release_notes.md
- name: Draft release
if: startsWith(github.ref, 'refs/tags/') && github.repository == env.ORIGINAL_PROJECT
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.VERSION_TAG }}
name: ${{ env.VERSION_TAG }}
body_path: release_notes.md
files: |
${{ env.APK_NAME }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
instrumentation:
name: Critical androidTest (API 35)
runs-on: ubuntu-24.04
needs: build
timeout-minutes: 45
steps:
- name: Clone repo
uses: actions/checkout@v4
with:
submodules: 'true'
- name: Set up JDK 21
uses: actions/setup-java@v4.2.2
with:
java-version: '21'
distribution: 'adopt'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# MigrationTestHelper 必须对未混淆的目标 APK 跑,避免测试夹具跨 APK 调用被 R8
# 重命名的 Kotlin stdlib;真正依赖混淆结果的契约测试则单独对 Release 跑。
# x86_64 只对本次测试构建生效,正式 APK 仍保持 arm64-v8a。
- name: Run critical instrumented tests
uses: reactivecircus/android-emulator-runner@v2
env:
# 一次性测试键,仅用于证明 native signer 能加载;不是生产凭证。
SHAFT_EVENTS_HMAC: ci-instrumentation-only-not-a-secret
with:
api-level: 35
target: google_apis
arch: x86_64
profile: pixel_7_pro
script: |
./gradlew :app:connectedGithubDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=ceui.lisa.database.AppDatabaseMigrationTest -PSHAFT_TEST_ABI=x86_64 --stacktrace
./gradlew :app:connectedGithubReleaseAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=ceui.lisa.ExampleInstrumentedTest,ceui.lisa.model.AppApiGsonR8InstrumentedTest,ceui.pixiv.shaftapi.ShaftHmacInstrumentedTest,ceui.pixiv.ui.search.SearchRiskPolicyInstrumentedTest -PSHAFT_TEST_BUILD_TYPE=release -PSHAFT_TEST_ABI=x86_64 --stacktrace
./gradlew :actionqueue:connectedDebugAndroidTest --stacktrace
- name: Upload instrumented test reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: android-test-reports-${{ github.run_id }}
path: |
**/build/reports/androidTests/
**/build/outputs/androidTest-results/
if-no-files-found: ignore