diff --git a/.github/actions/ensure-xmake/action.yml b/.github/actions/ensure-xmake/action.yml index 60bbcfa9..0322d371 100644 --- a/.github/actions/ensure-xmake/action.yml +++ b/.github/actions/ensure-xmake/action.yml @@ -5,6 +5,9 @@ inputs: minimum-version: description: Minimum supported Xmake version (major.minor.patch). required: true + cache-key: + description: Host environment identity, including the image when running in a container. + default: host runs: using: composite @@ -16,11 +19,19 @@ runs: XMAKE_MIN_VERSION: ${{ inputs.minimum-version }} run: bash "$GITHUB_ACTION_PATH/check-version.sh" + - name: Identify Xmake cache environment + if: steps.check.outputs.needs-upgrade == 'true' + id: cache-environment + shell: bash + run: printf 'image=%s-%s\n' "${ImageOS:-unknown}" "${ImageVersion:-unknown}" >> "$GITHUB_OUTPUT" + - name: Install or upgrade Xmake if: steps.check.outputs.needs-upgrade == 'true' uses: xmake-io/github-action-setup-xmake@v1 with: xmake-version: ${{ inputs.minimum-version }} + actions-cache-folder: ${{ runner.temp }}/crossdesk-xmake-toolchain + actions-cache-key: crossdesk-toolchain-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.cache-environment.outputs.image }}-${{ inputs.cache-key }} - name: Verify Xmake meets the minimum version shell: bash diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 3a972ec0..e130af77 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -70,6 +70,7 @@ jobs: java-version: "17" - name: Set up Python + id: python uses: actions/setup-python@v6 with: python-version: "3.13" @@ -90,7 +91,9 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends "${crossdesk_missing_packages[@]}" fi - python3 -m pip install setuptools wheel + if ! python3 -c 'import setuptools, wheel, pip' >/dev/null 2>&1; then + python3 -m pip install setuptools wheel + fi # Use an isolated SDK directory so the cache contains only this toolchain, # rather than every SDK/NDK version preinstalled on the runner. @@ -134,13 +137,40 @@ jobs: actions-cache-folder: .xmake-toolchain-cache actions-cache-key: ${{ runner.os }}-${{ runner.arch }}-ubuntu24.04-android-xmake${{ env.XMAKE_MIN_VERSION }} - # Repository selection follows desktop Xmake behavior. Keep installed - # packages isolated; never reuse another platform's build/config. - - name: Cache native dependencies - uses: actions/cache@v5 + - name: Set native dependency cache key + id: native-cache-key + env: + CACHE_ENVIRONMENT: ${{ runner.os }}-${{ runner.arch }}-android-arm64-native-v2-ndk28.2.13676358-api26-xmake${{ env.XMAKE_MIN_VERSION }}-python${{ steps.python.outputs.python-version }} + RECIPE_HASH: ${{ hashFiles('apps/android/scripts/build_native.py', 'deps/submodules/minirtc/thirdparty/**') }} + DEPENDENCY_HASH: ${{ hashFiles('apps/android/native/xmake.lua', 'deps/submodules/minirtc/xmake.lua') }} + run: | + prefix="${CACHE_ENVIRONMENT}-image${ImageVersion:?Missing GitHub runner image version}-${RECIPE_HASH}-" + printf 'prefix=%s\nkey=%s%s\n' "$prefix" "$prefix" "$DEPENDENCY_HASH" >> "$GITHUB_OUTPUT" + # Historical source_tag checkouts keep exact-only dependency reuse. + if grep -q -- '--configure-only' apps/android/scripts/build_native.py && \ + grep -q 'package.librarydeps.strict_compatibility' apps/android/scripts/build_native.py; then + echo 'incremental=true' >> "$GITHUB_OUTPUT" + fi + + # Cache installed packages separately from native targets and Xmake state. + - name: Restore native dependencies + id: native-cache + uses: actions/cache/restore@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/**') }} + key: ${{ steps.native-cache-key.outputs.key }} + restore-keys: ${{ steps.native-cache-key.outputs.incremental == 'true' && steps.native-cache-key.outputs.prefix || '' }} + + - name: Prepare native dependencies + if: steps.native-cache-key.outputs.incremental == 'true' + run: python3 -B apps/android/scripts/build_native.py --configure-only + + - name: Save native dependencies + if: steps.native-cache-key.outputs.incremental == 'true' && steps.native-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: apps/android/.native/packages + key: ${{ steps.native-cache.outputs.cache-primary-key }} - name: Set up Gradle uses: gradle/actions/setup-gradle@v6 @@ -185,6 +215,13 @@ jobs: -PcrossdeskVersionCode="${ANDROID_VERSION_CODE}" \ :app:assembleDebug :app:assembleRelease + - name: Save legacy native dependencies + if: steps.native-cache-key.outputs.incremental != 'true' && steps.native-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: apps/android/.native/packages + key: ${{ steps.native-cache.outputs.cache-primary-key }} + - name: Verify APKs working-directory: apps/android run: | diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 634acfc7..1b3bbe68 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -155,30 +155,55 @@ jobs: - name: Initialize submodules run: git submodule update --init --recursive - - name: Cache xmake dependencies - uses: actions/cache@v5 - with: - path: apps/ios/.xmake/packages - key: "${{ runner.os }}-xmake${{ env.XMAKE_MIN_VERSION }}-deps-ios-arm64-source-v2-xcode26.6-min16-${{ hashFiles('apps/ios/scripts/build_minirtc_ios.sh', 'xmake.lua', 'deps/submodules/minirtc/xmake.lua', 'deps/submodules/minirtc/thirdparty/**') }}-${{ github.run_id }}" - restore-keys: | - ${{ runner.os }}-xmake${{ env.XMAKE_MIN_VERSION }}-deps-ios-arm64-source-v2-xcode26.6-min16-${{ hashFiles('apps/ios/scripts/build_minirtc_ios.sh', 'xmake.lua', 'deps/submodules/minirtc/xmake.lua', 'deps/submodules/minirtc/thirdparty/**') }}- - - name: Set up Python + id: python uses: actions/setup-python@v6 with: python-version: "3.13" + cache: pip + cache-dependency-path: .github/workflows/build-ios.yml - name: Install Python build tools shell: bash run: | # Xmake only accepts the host Python when all three modules exist; # otherwise it falls back to compiling another Python from source. - python3 -m pip install --upgrade pip setuptools wheel + if ! python3 -c 'import setuptools, wheel, pip' >/dev/null 2>&1; then + python3 -m pip install setuptools wheel + fi + + - name: Set native dependency cache key + id: native-cache-key + shell: bash + env: + CACHE_ENVIRONMENT: ${{ runner.os }}-${{ runner.arch }}-ios-arm64-source-v3-xcode26.6-min16-xmake${{ env.XMAKE_MIN_VERSION }}-python${{ steps.python.outputs.python-version }} + RECIPE_HASH: ${{ hashFiles('apps/ios/scripts/build_minirtc_ios.sh', 'deps/submodules/minirtc/thirdparty/**') }} + DEPENDENCY_HASH: ${{ hashFiles('xmake.lua', 'deps/submodules/minirtc/xmake.lua') }} + run: | + image="${ImageVersion:?Missing GitHub runner image version}" + prefix="${CACHE_ENVIRONMENT}-image${image}-${RECIPE_HASH}-" + printf 'image=%s\nprefix=%s\nkey=%s%s\n' "$image" "$prefix" "$prefix" "$DEPENDENCY_HASH" >> "$GITHUB_OUTPUT" + # Historical source_tag checkouts keep exact-only dependency reuse. + if grep -q -- '--configure-only' apps/ios/scripts/build_minirtc_ios.sh && \ + grep -q 'package.librarydeps.strict_compatibility' apps/ios/scripts/build_minirtc_ios.sh; then + echo 'incremental=true' >> "$GITHUB_OUTPUT" + fi - name: Install xmake uses: xmake-io/github-action-setup-xmake@v1 with: xmake-version: "${{ env.XMAKE_MIN_VERSION }}" + actions-cache-folder: ${{ runner.temp }}/crossdesk-ios-xmake-toolchain + actions-cache-key: ios-toolchain-v1-${{ runner.os }}-${{ runner.arch }}-image${{ steps.native-cache-key.outputs.image }}-xcode26.6 + + # Keep recipe/patch and toolchain changes outside the fallback boundary. + - name: Restore native dependencies + id: native-cache + uses: actions/cache/restore@v5 + with: + path: apps/ios/.xmake/packages + key: ${{ steps.native-cache-key.outputs.key }} + restore-keys: ${{ steps.native-cache-key.outputs.incremental == 'true' && steps.native-cache-key.outputs.prefix || '' }} - name: Verify minimum xmake version shell: bash @@ -187,6 +212,24 @@ jobs: python3 -B apps/ios/scripts/verify_native_build.py \ --xmake-version "${RUNNER_TEMP}/xmake-version.txt" + - name: Prepare native dependencies + if: steps.native-cache-key.outputs.incremental == 'true' + timeout-minutes: 60 + shell: bash + env: + CONFIGURATION: Release + PLATFORM_NAME: iphoneos + CURRENT_ARCH: arm64 + CROSSDESK_NATIVE_VERBOSE: "1" + run: apps/ios/scripts/build_minirtc_ios.sh --configure-only + + - name: Save native dependencies + if: steps.native-cache-key.outputs.incremental == 'true' && steps.native-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: apps/ios/.xmake/packages + key: ${{ steps.native-cache.outputs.cache-primary-key }} + # Xcode buffers Run Script output until the phase finishes. Build the # native archives here so dependency downloads and compiler commands are # streamed to Actions; the Xcode phase then reuses the same build products. @@ -216,6 +259,13 @@ jobs: apps/ios/scripts/build_minirtc_ios.sh 2>&1 | \ tee "${RUNNER_TEMP}/ios-native-build.log" + - name: Save legacy native dependencies + if: steps.native-cache-key.outputs.incremental != 'true' && steps.native-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: apps/ios/.xmake/packages + key: ${{ steps.native-cache.outputs.cache-primary-key }} + - name: Build CrossDesk Mobile timeout-minutes: 30 shell: bash diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99d0e60b..3ef126d1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,6 +132,7 @@ jobs: uses: ./.github/actions/ensure-xmake with: minimum-version: ${{ env.XMAKE_MIN_VERSION }} + cache-key: ${{ matrix.image }} - name: Verify Linux build image shell: bash diff --git a/apps/android/scripts/build_native.py b/apps/android/scripts/build_native.py index 25d333d7..6551b1dc 100644 --- a/apps/android/scripts/build_native.py +++ b/apps/android/scripts/build_native.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Build the native controller in isolation from desktop and iOS caches.""" +import argparse import fcntl import os from pathlib import Path @@ -26,7 +27,7 @@ def find_xmake(): return xmake -def main(): +def main(configure_only=False): sdk = Path(os.environ.get('ANDROID_HOME') or os.environ.get('ANDROID_SDK_ROOT') or Path.home() / 'Library/Android/sdk') ndk = sdk / 'ndk/28.2.13676358' @@ -45,13 +46,20 @@ def main(): # and updates. Only installed Android packages/build products are isolated. subprocess.run([xmake, 'f', '-P', str(work), '-y', '-p', 'android', '-a', 'arm64-v8a', '-m', 'release', '--ndk=' + str(ndk), '--ndk_sdkver=26', - '--runtimes=c++_static', '--policies=package.precompiled:n', + '--runtimes=c++_static', + '--policies=package.precompiled:n,package.librarydeps.strict_compatibility', '-o', str(CACHE / 'build')], cwd=work, env=env, check=True) + if configure_only: + return subprocess.run([xmake, '-P', str(work), '-y', 'crossdesk_android'], cwd=work, env=env, check=True) if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--configure-only', action='store_true', + help='Install native dependencies before compiling application targets.') + args = parser.parse_args() CACHE.mkdir(parents=True, exist_ok=True) with (CACHE / 'build.lock').open('w') as lock: fcntl.flock(lock, fcntl.LOCK_EX) - main() + main(configure_only=args.configure_only) diff --git a/apps/ios/scripts/build_minirtc_ios.sh b/apps/ios/scripts/build_minirtc_ios.sh index 8613b1db..7ee85b41 100755 --- a/apps/ios/scripts/build_minirtc_ios.sh +++ b/apps/ios/scripts/build_minirtc_ios.sh @@ -2,6 +2,14 @@ set -euo pipefail +CONFIGURE_ONLY=false +if [[ $# -eq 1 && "$1" == "--configure-only" ]]; then + CONFIGURE_ONLY=true +elif [[ $# -ne 0 ]]; then + print -u2 "Usage: $0 [--configure-only]" + exit 64 +fi + SCRIPT_DIR="${0:A:h}" IOS_DIR="${SCRIPT_DIR:h}" REPO_DIR="${IOS_DIR:h:h}" @@ -169,7 +177,7 @@ configure_xmake() { print "Configuring iOS native project: ${project_dir}" run_xmake f -P "${project_dir}" "${clean_flags[@]}" "${XMAKE_LOG_FLAGS[@]}" -o "${build_dir}" \ -p iphoneos -a arm64 --appledev="${APPLE_DEVICE}" -m "${MODE}" --target_minver=16.0 \ - --policies=package.precompiled:n -y "$@" + --policies=package.precompiled:n,package.librarydeps.strict_compatibility -y "$@" mkdir -p "${build_dir}" print -r -- "${CONFIG_SIGNATURE}" > "${stamp}" } @@ -182,6 +190,24 @@ configure_xmake() { configure_xmake "${MINIRTC_DIR}" "${MINIRTC_BUILD_DIR}" \ --as="${XMAKE_TOOLCHAIN_BIN}/clang" --USE_CUDA=false \ --MINIRTC_ENABLE_AOM="${MINIRTC_ENABLE_AOM}" +) + +# Build the platform-independent wire archive from the root project's target. +# The copied manifest only isolates Xmake's generated configuration; there is +# no second project definition to maintain. +( + cd "${WIRE_WORK_DIR}" + configure_xmake "${WIRE_WORK_DIR}" "${WIRE_BUILD_DIR}" \ + --CROSSDESK_BUILD_DESKTOP=false --CROSSDESK_BUILD_CLI=false +) + +# CI can persist the installed packages before compiling either archive. +if [[ "${CONFIGURE_ONLY}" == true ]]; then + exit 0 +fi + +( + cd "${MINIRTC_DIR}" print "Building MiniRTC for ${SDK_NAME} (${MODE})" run_xmake b -P "${MINIRTC_DIR}" "${XMAKE_LOG_FLAGS[@]}" minirtc ) @@ -191,13 +217,8 @@ if [[ ! -f "${MINIRTC_LIBRARY}" ]]; then exit 66 fi -# Build the platform-independent wire archive from the root project's target. -# The copied manifest only isolates Xmake's generated configuration; there is -# no second project definition to maintain. ( cd "${WIRE_WORK_DIR}" - configure_xmake "${WIRE_WORK_DIR}" "${WIRE_BUILD_DIR}" \ - --CROSSDESK_BUILD_DESKTOP=false --CROSSDESK_BUILD_CLI=false print "Building CrossDesk wire for ${SDK_NAME} (${MODE})" run_xmake b -P "${WIRE_WORK_DIR}" "${XMAKE_LOG_FLAGS[@]}" crossdesk_wire )