chore: 重构 OpenCode 命令和技能文档体系
- 新增:统一的 git 命令文档(add/commit/push/pull 等) - 新增:整合的 Gitea 技能文档(API、运行器、工作流等) - 新增:工作流模板(Android、Go、Node.js 等) - 移除:已弃用的旧命令脚本和发布脚本 - 改进:.gitignore 添加敏感文件保护规则 - 改进:AGENTS.md 完善了开发规范和示例 此次重组统一了命令和技能的文档结构,便于后续维护和扩展。
This commit is contained in:
399
skill/gitea/workflow-templates/android-app.md
Normal file
399
skill/gitea/workflow-templates/android-app.md
Normal file
@@ -0,0 +1,399 @@
|
||||
# Android 应用 Workflow 模板
|
||||
|
||||
适用于 Android 应用的 CI/CD workflow,支持 APK 构建、签名和发布。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- Kotlin / Java Android 应用
|
||||
- Jetpack Compose 项目
|
||||
- 需要签名发布的 APK
|
||||
- 需要创建 Release 的项目
|
||||
|
||||
## 环境要求
|
||||
|
||||
| 依赖 | Runner 要求 |
|
||||
|------|------------|
|
||||
| JDK 17+ | Runner 主机已安装 |
|
||||
| Android SDK | Runner 主机已安装 |
|
||||
| Gradle | 由 wrapper 管理 |
|
||||
|
||||
**重要**:必须使用 `darwin-arm64` 标签的 macOS Runner,因为 Google 不提供 Linux ARM64 版本的 Android SDK。
|
||||
|
||||
## Workflow 骨架模板
|
||||
|
||||
```yaml
|
||||
name: Android - Build & Release APK
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'android/**' # 修改为实际目录
|
||||
- '.gitea/workflows/android.yml'
|
||||
tags:
|
||||
- 'android-*' # 修改为实际 tag 前缀
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
SERVICE_PREFIX: android # 修改为实际服务名
|
||||
SERVICE_DIR: android # 修改为实际目录名
|
||||
APP_NAME: myapp-android # 修改为实际应用名
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Release APK
|
||||
runs-on: darwin-arm64 # macOS ARM64(必须)
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
outputs:
|
||||
version_name: ${{ steps.vars.outputs.version_name }}
|
||||
version_code: ${{ steps.vars.outputs.version_code }}
|
||||
apk_name: ${{ steps.vars.outputs.apk_name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Verify Java environment
|
||||
run: |
|
||||
java -version
|
||||
echo "JAVA_HOME=$JAVA_HOME"
|
||||
|
||||
- name: Setup Android SDK
|
||||
run: |
|
||||
if [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME" ]; then
|
||||
echo "Using ANDROID_HOME: $ANDROID_HOME"
|
||||
else
|
||||
for SDK_PATH in ~/Library/Android/sdk ~/android-sdk /opt/homebrew/share/android-commandlinetools; do
|
||||
if [ -d "$SDK_PATH" ]; then
|
||||
export ANDROID_HOME=$SDK_PATH
|
||||
echo "Found Android SDK: $SDK_PATH"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$ANDROID_HOME" ] || [ ! -d "$ANDROID_HOME" ]; then
|
||||
echo "ERROR: Android SDK not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
|
||||
echo "$ANDROID_HOME/cmdline-tools/latest/bin" >> $GITHUB_PATH
|
||||
echo "$ANDROID_HOME/platform-tools" >> $GITHUB_PATH
|
||||
|
||||
- name: Get gradle-hashfiles
|
||||
id: hash-gradle
|
||||
working-directory: ${{ env.SERVICE_DIR }}
|
||||
run: |
|
||||
HASH=$(sha256sum gradle/libs.versions.toml app/build.gradle.kts build.gradle.kts settings.gradle.kts 2>/dev/null | sha256sum | awk '{print $1}' | head -c 16)
|
||||
echo "hash=${HASH}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Gradle
|
||||
uses: https://github.com/actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ env.SERVICE_PREFIX }}-${{ steps.hash-gradle.outputs.hash }}
|
||||
restore-keys: gradle-${{ env.SERVICE_PREFIX }}-
|
||||
|
||||
- name: Set variables
|
||||
id: vars
|
||||
run: |
|
||||
# 从 build.gradle.kts 提取版本信息(按需修改路径和正则)
|
||||
version_name=$(grep 'versionName' ${{ env.SERVICE_DIR }}/app/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
||||
version_code=$(grep 'versionCode' ${{ env.SERVICE_DIR }}/app/build.gradle.kts | head -1 | sed 's/[^0-9]*//g')
|
||||
git_tag=$(git describe --tags --abbrev=0 --always)
|
||||
apk_name="${{ env.APP_NAME }}-${version_name}"
|
||||
|
||||
{
|
||||
echo "version_name=${version_name}"
|
||||
echo "version_code=${version_code}"
|
||||
echo "git_tag=${git_tag}"
|
||||
echo "apk_name=${apk_name}"
|
||||
} >> $GITHUB_ENV
|
||||
|
||||
echo "version_name=${version_name}" >> $GITHUB_OUTPUT
|
||||
echo "version_code=${version_code}" >> $GITHUB_OUTPUT
|
||||
echo "apk_name=${apk_name}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup signing
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
# 创建 local.properties
|
||||
echo "sdk.dir=$ANDROID_HOME" > ${{ env.SERVICE_DIR }}/local.properties
|
||||
|
||||
# 配置签名(如果提供了 keystore)
|
||||
if [ -n "$KEYSTORE_BASE64" ]; then
|
||||
echo "$KEYSTORE_BASE64" | base64 -d > ${{ env.SERVICE_DIR }}/release.keystore
|
||||
{
|
||||
echo "KEYSTORE_FILE=../release.keystore"
|
||||
echo "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD"
|
||||
echo "KEY_ALIAS=$KEY_ALIAS"
|
||||
echo "KEY_PASSWORD=$KEY_PASSWORD"
|
||||
} >> ${{ env.SERVICE_DIR }}/local.properties
|
||||
echo "Signing configured"
|
||||
else
|
||||
echo "WARNING: No signing key provided, building unsigned APK"
|
||||
fi
|
||||
|
||||
- name: Build Release APK
|
||||
working-directory: ${{ env.SERVICE_DIR }}
|
||||
run: |
|
||||
chmod +x gradlew
|
||||
|
||||
# ============================================
|
||||
# 用户自定义构建步骤(按项目需求修改)
|
||||
# ============================================
|
||||
|
||||
# 清理(按需启用)
|
||||
# ./gradlew clean
|
||||
|
||||
# 单元测试(按需启用)
|
||||
# ./gradlew test
|
||||
|
||||
# 构建 Release APK(必须)
|
||||
./gradlew assembleRelease --no-daemon
|
||||
|
||||
- name: Rename APK
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp ${{ env.SERVICE_DIR }}/app/build/outputs/apk/release/app-release.apk dist/${{ env.apk_name }}.apk
|
||||
cd dist
|
||||
sha256sum ${{ env.apk_name }}.apk > ${{ env.apk_name }}.apk.sha256
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.SERVICE_PREFIX }}-apk
|
||||
path: dist/
|
||||
|
||||
- name: Notify
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
env:
|
||||
WEBHOOK_URL: ${{ vars.WEBHOOK_URL }}
|
||||
run: |
|
||||
status="${{ job.status }}"
|
||||
[ "$status" = "success" ] && status_text="Build Success" || status_text="Build Failed"
|
||||
|
||||
curl -s -H "Content-Type: application/json" -X POST -d \
|
||||
"{\"msg_type\":\"text\",\"content\":{\"text\":\"${{ env.APP_NAME }} ${status_text}\\nVersion: ${{ env.version_name }}\"}}" \
|
||||
"$WEBHOOK_URL" || true
|
||||
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: darwin-arm64
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ env.SERVICE_PREFIX }}-apk
|
||||
path: dist
|
||||
|
||||
- name: Create Release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
VERSION_NAME: ${{ needs.build.outputs.version_name }}
|
||||
APK_NAME: ${{ needs.build.outputs.apk_name }}
|
||||
run: |
|
||||
git_tag=$(git describe --tags --abbrev=0)
|
||||
api_url="${{ github.server_url }}/api/v1"
|
||||
repo="${{ github.repository }}"
|
||||
|
||||
# 创建 Release
|
||||
release_id=$(curl -s -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag} (v${VERSION_NAME})\"}" \
|
||||
"${api_url}/repos/${repo}/releases" | jq -r '.id')
|
||||
|
||||
# 上传 APK
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-F "attachment=@dist/${APK_NAME}.apk" \
|
||||
"${api_url}/repos/${repo}/releases/${release_id}/assets"
|
||||
|
||||
# 上传校验和
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-F "attachment=@dist/${APK_NAME}.apk.sha256" \
|
||||
"${api_url}/repos/${repo}/releases/${release_id}/assets"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 签名配置
|
||||
|
||||
### 1. 生成签名密钥
|
||||
|
||||
```bash
|
||||
keytool -genkey -v -keystore release.keystore \
|
||||
-alias myapp \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity 10000
|
||||
```
|
||||
|
||||
### 2. Base64 编码
|
||||
|
||||
```bash
|
||||
base64 -i release.keystore -o keystore.base64
|
||||
# 将 keystore.base64 内容复制到 ANDROID_KEYSTORE_BASE64 secret
|
||||
```
|
||||
|
||||
### 3. build.gradle.kts 签名配置
|
||||
|
||||
```kotlin
|
||||
android {
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val props = Properties()
|
||||
val propsFile = rootProject.file("local.properties")
|
||||
if (propsFile.exists()) {
|
||||
props.load(propsFile.inputStream())
|
||||
val keystoreFile = props.getProperty("KEYSTORE_FILE", "")
|
||||
if (keystoreFile.isNotEmpty()) {
|
||||
storeFile = file(keystoreFile)
|
||||
storePassword = props.getProperty("KEYSTORE_PASSWORD", "")
|
||||
keyAlias = props.getProperty("KEY_ALIAS", "")
|
||||
keyPassword = props.getProperty("KEY_PASSWORD", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 缓存配置
|
||||
|
||||
### 缓存路径
|
||||
|
||||
```yaml
|
||||
path: |
|
||||
~/.gradle/caches # Gradle 依赖缓存
|
||||
~/.gradle/wrapper # Gradle wrapper
|
||||
```
|
||||
|
||||
### Key 计算
|
||||
|
||||
```bash
|
||||
HASH=$(sha256sum gradle/libs.versions.toml app/build.gradle.kts build.gradle.kts settings.gradle.kts | sha256sum | awk '{print $1}' | head -c 16)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Secrets 配置
|
||||
|
||||
| Secret | 用途 |
|
||||
|--------|------|
|
||||
| `ANDROID_KEYSTORE_BASE64` | Base64 编码的 keystore 文件 |
|
||||
| `ANDROID_KEYSTORE_PASSWORD` | keystore 密码 |
|
||||
| `ANDROID_KEY_ALIAS` | 密钥别名 |
|
||||
| `ANDROID_KEY_PASSWORD` | 密钥密码 |
|
||||
| `RELEASE_TOKEN` | Gitea API 令牌(创建 Release) |
|
||||
|
||||
---
|
||||
|
||||
## Gradle 优化
|
||||
|
||||
### gradle.properties
|
||||
|
||||
```properties
|
||||
# CI 环境优化
|
||||
org.gradle.daemon=false
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.configureondemand=true
|
||||
|
||||
# Android 配置
|
||||
android.useAndroidX=true
|
||||
android.nonTransitiveRClass=true
|
||||
```
|
||||
|
||||
### 国内镜像(可选)
|
||||
|
||||
```properties
|
||||
# gradle-wrapper.properties
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本提取
|
||||
|
||||
从 `build.gradle.kts` 提取版本信息:
|
||||
|
||||
```bash
|
||||
# versionName
|
||||
version_name=$(grep 'versionName' app/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
||||
|
||||
# versionCode
|
||||
version_code=$(grep 'versionCode' app/build.gradle.kts | head -1 | sed 's/[^0-9]*//g')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见 Gradle 任务
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| `./gradlew assembleRelease` | 构建 Release APK |
|
||||
| `./gradlew assembleDebug` | 构建 Debug APK |
|
||||
| `./gradlew bundleRelease` | 构建 AAB(App Bundle)|
|
||||
| `./gradlew test` | 运行单元测试 |
|
||||
| `./gradlew lint` | 运行 Lint 检查 |
|
||||
| `./gradlew clean` | 清理构建产物 |
|
||||
|
||||
---
|
||||
|
||||
## 使用步骤
|
||||
|
||||
1. 复制上方 workflow 模板到 `.gitea/workflows/android.yml`
|
||||
2. 修改 `SERVICE_PREFIX`、`SERVICE_DIR`、`APP_NAME` 为实际值
|
||||
3. 修改 `paths` 和 `tags` 触发条件
|
||||
4. 配置 `build.gradle.kts` 签名(参考上方示例)
|
||||
5. 配置 Secrets(keystore、密码等)
|
||||
6. 推送代码触发 workflow
|
||||
|
||||
---
|
||||
|
||||
## 版本发布
|
||||
|
||||
```bash
|
||||
# 创建并推送 tag
|
||||
git tag android-1.0.0
|
||||
git push origin android-1.0.0
|
||||
```
|
||||
Reference in New Issue
Block a user