mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
255 lines
11 KiB
YAML
255 lines
11 KiB
YAML
name: Build Android
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
patch:
|
|
description: "Optional patch number, matching the main build workflow."
|
|
required: false
|
|
type: string
|
|
default: "0"
|
|
workflow_call:
|
|
inputs:
|
|
source_tag:
|
|
description: "Existing source tag for a standalone Android release."
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
patch:
|
|
description: "Optional patch number from the main build workflow."
|
|
required: false
|
|
type: string
|
|
default: "0"
|
|
outputs:
|
|
version:
|
|
description: "Android artifact version; combined releases match the desktop tag."
|
|
value: ${{ jobs.build.outputs.version }}
|
|
pull_request:
|
|
paths:
|
|
- ".github/workflows/build-android.yml"
|
|
- ".github/workflows/build.yml"
|
|
- ".github/workflows/release-mobile.yml"
|
|
- ".github/workflows/sign-android.yml"
|
|
- ".gitmodules"
|
|
- "LICENSE"
|
|
- "PRIVACY.md"
|
|
- "apps/android/**"
|
|
- "apps/ios/CrossDeskMobile/Resources/ThirdPartyLicenses.json"
|
|
- "apps/ios/licenses/**"
|
|
- "apps/ios/scripts/generate_third_party_licenses.py"
|
|
- "deps/submodules/minirtc"
|
|
- "libs/wire/**"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build:
|
|
name: Build on Android (arm64)
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 120
|
|
outputs:
|
|
version: ${{ steps.package.outputs.version }}
|
|
env:
|
|
# Exercise the minimum supported version; local builds may use newer Xmake.
|
|
XMAKE_MIN_VERSION: "3.1.1"
|
|
CROSSDESK_SOURCE_TAG: ${{ inputs.source_tag || '' }}
|
|
CROSSDESK_RELEASE_BUILD: ${{ inputs.source_tag && 'YES' || 'NO' }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
ref: ${{ inputs.source_tag || github.ref }}
|
|
submodules: recursive
|
|
persist-credentials: false
|
|
|
|
- name: Set up Java
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: "17"
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v6
|
|
with:
|
|
python-version: "3.13"
|
|
cache: pip
|
|
# Python build dependencies are declared in this workflow. This file
|
|
# also exists when source_tag checks out an older Android release.
|
|
cache-dependency-path: .github/workflows/build-android.yml
|
|
|
|
- name: Install native build tools
|
|
run: |
|
|
crossdesk_missing_packages=()
|
|
for crossdesk_package in build-essential cmake ninja-build pkg-config autoconf automake libtool nasm; do
|
|
if [[ "$(dpkg-query -W -f='${Status}' "${crossdesk_package}" 2>/dev/null || true)" != "install ok installed" ]]; then
|
|
crossdesk_missing_packages+=("${crossdesk_package}")
|
|
fi
|
|
done
|
|
if (( ${#crossdesk_missing_packages[@]} )); then
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends "${crossdesk_missing_packages[@]}"
|
|
fi
|
|
python3 -m pip install setuptools wheel
|
|
|
|
# Use an isolated SDK directory so the cache contains only this toolchain,
|
|
# rather than every SDK/NDK version preinstalled on the runner.
|
|
- name: Select Android SDK directory
|
|
run: |
|
|
echo "ANDROID_HOME=${RUNNER_TEMP}/crossdesk-android-sdk" >> "${GITHUB_ENV}"
|
|
echo "ANDROID_SDK_ROOT=${RUNNER_TEMP}/crossdesk-android-sdk" >> "${GITHUB_ENV}"
|
|
|
|
- name: Restore Android SDK
|
|
id: android-sdk-cache
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: ${{ env.ANDROID_HOME }}
|
|
key: ${{ runner.os }}-${{ runner.arch }}-ubuntu24.04-android-sdk-v1-cli12266719-api36-build36.0.0-ndk28.2.13676358
|
|
|
|
- name: Set up Android SDK
|
|
if: steps.android-sdk-cache.outputs.cache-hit != 'true'
|
|
uses: android-actions/setup-android@v3
|
|
with:
|
|
cmdline-tools-version: "12266719"
|
|
log-accepted-android-sdk-licenses: false
|
|
packages: "platform-tools platforms;android-36 build-tools;36.0.0 ndk;28.2.13676358"
|
|
|
|
# Save immediately after installation, even if a later APK build fails.
|
|
- name: Save Android SDK
|
|
if: steps.android-sdk-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: ${{ env.ANDROID_HOME }}
|
|
key: ${{ steps.android-sdk-cache.outputs.cache-primary-key }}
|
|
|
|
- name: Add Android tools to PATH
|
|
run: |
|
|
echo "${ANDROID_HOME}/cmdline-tools/16.0/bin" >> "${GITHUB_PATH}"
|
|
echo "${ANDROID_HOME}/platform-tools" >> "${GITHUB_PATH}"
|
|
|
|
- name: Install xmake
|
|
uses: xmake-io/github-action-setup-xmake@v1
|
|
with:
|
|
xmake-version: ${{ env.XMAKE_MIN_VERSION }}
|
|
actions-cache-folder: .xmake-toolchain-cache
|
|
actions-cache-key: ${{ runner.os }}-${{ runner.arch }}-ubuntu24.04-android-xmake${{ env.XMAKE_MIN_VERSION }}
|
|
|
|
- name: Cache Xmake package repository
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: apps/android/.native/global/.xmake/repositories/xmake-repo
|
|
key: ${{ runner.os }}-${{ runner.arch }}-android-xmake-repo-v1-${{ hashFiles('apps/android/scripts/build_native.py') }}
|
|
|
|
# Cache installed packages only. build_native.py checks Xmake >= 3.1.1 and pins
|
|
# its own package repository; never reuse another platform's build/config.
|
|
- name: Cache native dependencies
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: apps/android/.native/packages
|
|
key: ${{ runner.os }}-${{ runner.arch }}-android-arm64-native-v1-${{ hashFiles('apps/android/scripts/build_native.py', 'apps/android/native/xmake.lua', 'deps/submodules/minirtc/xmake.lua', 'deps/submodules/minirtc/thirdparty/**') }}
|
|
|
|
- name: Set up Gradle
|
|
uses: gradle/actions/setup-gradle@v6
|
|
with:
|
|
cache-provider: basic
|
|
cache-read-only: ${{ github.event_name == 'pull_request' }}
|
|
|
|
- name: Verify shared license catalog
|
|
run: python3 -B apps/ios/scripts/generate_third_party_licenses.py
|
|
|
|
- name: Resolve Android version
|
|
shell: python
|
|
env:
|
|
PATCH_NUMBER: ${{ inputs.patch || '0' }}
|
|
run: |
|
|
import datetime
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import runpy
|
|
|
|
resolver = Path("apps/android/scripts/release_version.py")
|
|
if resolver.exists():
|
|
runpy.run_path(str(resolver), run_name="__main__")
|
|
else:
|
|
# Older standalone releases predate the resolver and Gradle properties.
|
|
# Keep their original package version when retrying an existing tag.
|
|
tag = os.environ["CROSSDESK_SOURCE_TAG"]
|
|
match = re.fullmatch(r"android-v([0-9]+\.[0-9]+\.[0-9]+)-([0-9]{8})", tag)
|
|
config = Path("apps/android/app/build.gradle").read_text()
|
|
name = re.search(r"versionName\s+'([^']+)'", config)
|
|
code = re.search(r"versionCode\s+([0-9]+)", config)
|
|
if not match or not name or not code or match[1] != name[1]:
|
|
raise ValueError("Legacy Android sources require a matching standalone release tag")
|
|
datetime.datetime.strptime(match[2], "%Y%m%d")
|
|
with open(os.environ["GITHUB_ENV"], "a") as stream:
|
|
stream.write(f"ANDROID_VERSION_NAME={name[1]}\nANDROID_VERSION_CODE={code[1]}\n"
|
|
f"ANDROID_ARTIFACT_VERSION={tag.removeprefix('android-')}\n")
|
|
|
|
- name: Build APKs
|
|
working-directory: apps/android
|
|
run: |
|
|
./gradlew --no-daemon --stacktrace \
|
|
-PcrossdeskVersionName="${ANDROID_VERSION_NAME}" \
|
|
-PcrossdeskVersionCode="${ANDROID_VERSION_CODE}" \
|
|
:app:assembleDebug :app:assembleRelease
|
|
|
|
- name: Verify APKs
|
|
working-directory: apps/android
|
|
run: |
|
|
for APK in app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/release/app-release-unsigned.apk; do
|
|
python3 -B test/verify_apk.py "${APK}" \
|
|
--aapt2 "${ANDROID_HOME}/build-tools/36.0.0/aapt2" \
|
|
--version-name "${ANDROID_VERSION_NAME}" --version-code "${ANDROID_VERSION_CODE}"
|
|
done
|
|
"${ANDROID_HOME}/build-tools/36.0.0/apksigner" verify app/build/outputs/apk/debug/app-debug.apk
|
|
|
|
- name: Package APKs with the resolved Android version
|
|
id: package
|
|
shell: python
|
|
run: |
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
|
|
apk_dir = Path("apps/android/app/build/outputs/apk")
|
|
version = os.environ["ANDROID_ARTIFACT_VERSION"]
|
|
for variant in ("debug", "release"):
|
|
metadata = json.loads((apk_dir / variant / "output-metadata.json").read_text())
|
|
for element in metadata["elements"]:
|
|
if (element["versionName"] != os.environ["ANDROID_VERSION_NAME"] or
|
|
element["versionCode"] != int(os.environ["ANDROID_VERSION_CODE"])):
|
|
raise ValueError(f"Unexpected Android {variant} version: {element}")
|
|
output = Path(os.environ["RUNNER_TEMP"]) / "crossdesk-android"
|
|
for variant, source in (
|
|
("unsigned", "release/app-release-unsigned.apk"),
|
|
("debug", "debug/app-debug.apk"),
|
|
):
|
|
destination = output / variant
|
|
destination.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(apk_dir / source, destination / f"crossdesk-android-arm64-{variant}-{version}.apk")
|
|
with open(os.environ["GITHUB_OUTPUT"], "a") as stream:
|
|
stream.write(f"version={version}\n")
|
|
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as stream:
|
|
stream.write(f"Android {version}: arm64, API 26+, 16 KB aligned.\n\n"
|
|
"Debug APK uses an ephemeral CI debug key; it is for testing and may require uninstalling an existing build. "
|
|
"The Release APK is unsigned and needs your own signing before installation.\n\n"
|
|
"Debug and Release APKs built and packaged; APK checks passed.\n")
|
|
|
|
- name: Upload unsigned Release APK
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: crossdesk-android-arm64-unsigned-${{ steps.package.outputs.version }}
|
|
path: ${{ runner.temp }}/crossdesk-android/unsigned/*.apk
|
|
if-no-files-found: error
|
|
|
|
- name: Upload Debug APK
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: crossdesk-android-arm64-debug-${{ steps.package.outputs.version }}
|
|
path: ${{ runner.temp }}/crossdesk-android/debug/*.apk
|
|
if-no-files-found: error
|
|
retention-days: 14
|