Update CI workflow to use actions/setup-java@v5. Bring in upstream fixes and features for Java setup. Improve compatibility with Java 21 and future updates. - Use actions/setup-java@v5 for Java setup
267 lines
11 KiB
YAML
267 lines
11 KiB
YAML
name: Android Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build-release-apk:
|
|
name: Build signed release APK
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
permissions:
|
|
code: read
|
|
releases: write
|
|
env:
|
|
ANDROID_COMPILE_SDK: "36"
|
|
ANDROID_BUILD_TOOLS: "36.0.0"
|
|
ANDROID_NDK_VERSION: "30.0.15729638"
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
GITEA_RELEASE_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: npm
|
|
|
|
- name: Set up Java
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: "21"
|
|
cache: gradle
|
|
|
|
- name: Set up Rust
|
|
uses: dtolnay/rust-toolchain@1.95.0
|
|
with:
|
|
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
|
|
|
|
- name: Cache Rust build
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: src-tauri -> target
|
|
|
|
- name: Set up Android SDK
|
|
uses: android-actions/setup-android@v3
|
|
|
|
- name: Install Android SDK packages
|
|
run: |
|
|
yes | sdkmanager --licenses >/dev/null || true
|
|
sdkmanager \
|
|
"platforms;android-${ANDROID_COMPILE_SDK}" \
|
|
"build-tools;${ANDROID_BUILD_TOOLS}" \
|
|
"ndk;${ANDROID_NDK_VERSION}"
|
|
echo "NDK_HOME=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" >> "${GITEA_ENV}"
|
|
echo "${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS}" >> "${GITEA_PATH}"
|
|
|
|
- name: Install JavaScript dependencies
|
|
run: npm ci
|
|
|
|
- name: Apply version from release tag
|
|
run: |
|
|
if [ "${GITHUB_REF_TYPE}" != "tag" ]; then
|
|
echo "Release builds must run on a tag such as v0.1.0." >&2
|
|
exit 1
|
|
fi
|
|
|
|
RELEASE_VERSION="${GITHUB_REF_NAME#v}"
|
|
if [[ ! "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "Tag ${GITHUB_REF_NAME} must use the format vMAJOR.MINOR.PATCH." >&2
|
|
exit 1
|
|
fi
|
|
|
|
IFS=. read -r VERSION_MAJOR VERSION_MINOR VERSION_PATCH <<< "${RELEASE_VERSION}"
|
|
if (( 10#${VERSION_MINOR} > 999 || 10#${VERSION_PATCH} > 999 )); then
|
|
echo "Minor and patch versions must not exceed 999." >&2
|
|
exit 1
|
|
fi
|
|
ANDROID_VERSION_CODE=$((10#${VERSION_MAJOR} * 1000000 + 10#${VERSION_MINOR} * 1000 + 10#${VERSION_PATCH}))
|
|
if (( ANDROID_VERSION_CODE < 1 || ANDROID_VERSION_CODE > 2100000000 )); then
|
|
echo "Derived Android versionCode ${ANDROID_VERSION_CODE} is outside the allowed range." >&2
|
|
exit 1
|
|
fi
|
|
|
|
RELEASE_VERSION="${RELEASE_VERSION}" \
|
|
ANDROID_VERSION_CODE="${ANDROID_VERSION_CODE}" \
|
|
GITHUB_REF_NAME="${GITHUB_REF_NAME}" \
|
|
node -e '
|
|
const fs = require("fs");
|
|
const version = process.env.RELEASE_VERSION;
|
|
const versionCode = process.env.ANDROID_VERSION_CODE;
|
|
|
|
function updateJson(path, update) {
|
|
const value = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
update(value);
|
|
fs.writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
}
|
|
|
|
updateJson("package.json", value => { value.version = version; });
|
|
updateJson("package-lock.json", value => {
|
|
value.version = version;
|
|
value.packages[""].version = version;
|
|
});
|
|
updateJson("src-tauri/tauri.conf.json", value => { value.version = version; });
|
|
|
|
const cargoTomlPath = "src-tauri/Cargo.toml";
|
|
const cargoToml = fs.readFileSync(cargoTomlPath, "utf8").replace(
|
|
/(\[package\][\s\S]*?\nversion = ")[^"]+("\n)/,
|
|
`$1${version}$2`
|
|
);
|
|
fs.writeFileSync(cargoTomlPath, cargoToml);
|
|
|
|
const cargoLockPath = "src-tauri/Cargo.lock";
|
|
const cargoLock = fs.readFileSync(cargoLockPath, "utf8").replace(
|
|
/(\[\[package\]\]\nname = "lockscreenwallpaper"\nversion = ")[^"]+("\n)/,
|
|
`$1${version}$2`
|
|
);
|
|
fs.writeFileSync(cargoLockPath, cargoLock);
|
|
|
|
const fdroidPath = ".fdroid.yml";
|
|
let fdroid = fs.readFileSync(fdroidPath, "utf8");
|
|
fdroid = fdroid
|
|
.replace(/^ - versionName: .*$/m, ` - versionName: ${version}`)
|
|
.replace(/^ versionCode: .*$/m, ` versionCode: ${versionCode}`)
|
|
.replace(/^ commit: .*$/m, ` commit: ${process.env.GITHUB_REF_NAME}`)
|
|
.replace(/^CurrentVersion: .*$/m, `CurrentVersion: ${version}`)
|
|
.replace(/^CurrentVersionCode: .*$/m, `CurrentVersionCode: ${versionCode}`);
|
|
fs.writeFileSync(fdroidPath, fdroid);
|
|
'
|
|
|
|
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "${GITEA_ENV}"
|
|
echo "ANDROID_VERSION_CODE=${ANDROID_VERSION_CODE}" >> "${GITEA_ENV}"
|
|
echo "Building ${GITHUB_REF_NAME} with Android versionCode ${ANDROID_VERSION_CODE}."
|
|
|
|
- name: Validate signing secrets
|
|
run: |
|
|
for SECRET_NAME in \
|
|
ANDROID_KEYSTORE_BASE64 \
|
|
ANDROID_KEYSTORE_PASSWORD \
|
|
ANDROID_KEY_ALIAS \
|
|
ANDROID_KEY_PASSWORD \
|
|
GITEA_RELEASE_TOKEN
|
|
do
|
|
if [ -z "${!SECRET_NAME:-}" ]; then
|
|
echo "Missing Gitea Actions secret: ${SECRET_NAME}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: Restore release keystore
|
|
run: |
|
|
SIGNING_DIRECTORY="${RUNNER_TEMP}/wallpaperflow-signing"
|
|
mkdir -p "${SIGNING_DIRECTORY}"
|
|
printf '%s' "${ANDROID_KEYSTORE_BASE64}" | base64 --decode > "${SIGNING_DIRECTORY}/release.jks"
|
|
chmod 600 "${SIGNING_DIRECTORY}/release.jks"
|
|
echo "ANDROID_SIGNING_KEYSTORE=${SIGNING_DIRECTORY}/release.jks" >> "${GITEA_ENV}"
|
|
|
|
- name: Build unsigned release APK
|
|
run: npm run tauri -- android build --apk --ci
|
|
|
|
- name: Verify generated Android version
|
|
run: |
|
|
TAURI_PROPERTIES="src-tauri/gen/android/app/tauri.properties"
|
|
grep -Fx "tauri.android.versionName=${RELEASE_VERSION}" "${TAURI_PROPERTIES}"
|
|
grep -Fx "tauri.android.versionCode=${ANDROID_VERSION_CODE}" "${TAURI_PROPERTIES}"
|
|
|
|
- name: Align and sign APK
|
|
run: |
|
|
UNSIGNED_APK="src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk"
|
|
RELEASE_DIRECTORY="release"
|
|
ALIGNED_APK="${RELEASE_DIRECTORY}/WallpaperFlow-release-aligned.apk"
|
|
SIGNED_APK="${RELEASE_DIRECTORY}/WallpaperFlow-release.apk"
|
|
|
|
test -f "${UNSIGNED_APK}"
|
|
mkdir -p "${RELEASE_DIRECTORY}"
|
|
zipalign -p -f 4 "${UNSIGNED_APK}" "${ALIGNED_APK}"
|
|
apksigner sign \
|
|
--ks "${ANDROID_SIGNING_KEYSTORE}" \
|
|
--ks-key-alias "${ANDROID_KEY_ALIAS}" \
|
|
--ks-pass env:ANDROID_KEYSTORE_PASSWORD \
|
|
--key-pass env:ANDROID_KEY_PASSWORD \
|
|
--out "${SIGNED_APK}" \
|
|
"${ALIGNED_APK}"
|
|
apksigner verify --verbose --print-certs "${SIGNED_APK}"
|
|
rm "${ALIGNED_APK}"
|
|
(cd "${RELEASE_DIRECTORY}" && sha256sum WallpaperFlow-release.apk > WallpaperFlow-release.apk.sha256)
|
|
|
|
- name: Attach APK to Gitea release
|
|
run: |
|
|
API_ROOT="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
|
|
RELEASE_JSON="${RUNNER_TEMP}/wallpaperflow-release.json"
|
|
CREATE_JSON="${RUNNER_TEMP}/wallpaperflow-create-release.json"
|
|
ASSETS_JSON="${RUNNER_TEMP}/wallpaperflow-release-assets.json"
|
|
|
|
STATUS="$(curl --silent --show-error \
|
|
--output "${RELEASE_JSON}" \
|
|
--write-out '%{http_code}' \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
"${API_ROOT}/releases/tags/${GITHUB_REF_NAME}")"
|
|
|
|
if [ "${STATUS}" = "404" ]; then
|
|
node -e '
|
|
process.stdout.write(JSON.stringify({
|
|
tag_name: process.env.GITHUB_REF_NAME,
|
|
name: `WallpaperFlow ${process.env.GITHUB_REF_NAME}`,
|
|
body: "Automatisch erstelltes Android-Release.",
|
|
draft: false,
|
|
prerelease: false
|
|
}))
|
|
' > "${CREATE_JSON}"
|
|
curl --fail --silent --show-error \
|
|
--request POST \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
--header "Content-Type: application/json" \
|
|
--data-binary "@${CREATE_JSON}" \
|
|
--output "${RELEASE_JSON}" \
|
|
"${API_ROOT}/releases"
|
|
elif [ "${STATUS}" != "200" ]; then
|
|
echo "Could not read Gitea release for ${GITHUB_REF_NAME} (HTTP ${STATUS})." >&2
|
|
exit 1
|
|
fi
|
|
|
|
RELEASE_ID="$(node -e '
|
|
const release = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
if (!release.id) process.exit(1);
|
|
process.stdout.write(String(release.id));
|
|
' "${RELEASE_JSON}")"
|
|
|
|
curl --fail --silent --show-error \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
--output "${ASSETS_JSON}" \
|
|
"${API_ROOT}/releases/${RELEASE_ID}/assets"
|
|
|
|
for FILE in \
|
|
release/WallpaperFlow-release.apk \
|
|
release/WallpaperFlow-release.apk.sha256
|
|
do
|
|
ASSET_NAME="$(basename "${FILE}")"
|
|
ASSET_ID="$(ASSET_NAME="${ASSET_NAME}" node -e '
|
|
const assets = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
const asset = assets.find(item => item.name === process.env.ASSET_NAME);
|
|
if (asset) process.stdout.write(String(asset.id));
|
|
' "${ASSETS_JSON}")"
|
|
|
|
if [ -n "${ASSET_ID}" ]; then
|
|
curl --fail --silent --show-error \
|
|
--request DELETE \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
"${API_ROOT}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
|
fi
|
|
|
|
curl --fail --silent --show-error \
|
|
--request POST \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
--form "attachment=@${FILE}" \
|
|
"${API_ROOT}/releases/${RELEASE_ID}/assets" >/dev/null
|
|
done
|