From 3b586a1fa00e37cd0e5a378afde991f0308b4755 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Fri, 14 Aug 2026 20:43:30 -0300 Subject: [PATCH] ci: stop rebuilding the test app during packaging, single-ABI emulator tests, ccache, CMake 3.31 The CI Build job spent ~23 minutes on the npm package because every :runtime:assembleRelease pass (one per AAR flavor) dragged a full test-app build - both variants, including 8 CMake configure+builds - through finalizedBy(":app:buildMetadata") hooks on nearly every runtime task, plus reverse hooks in app/build.gradle attaching metadata tasks onto library tasks. The app already reaches buildMetadata through its merge*Assets dependency, so packaging now builds only the three AARs (12 native builds instead of 24 plus three app builds). Metadata wiring changes: - runtime/build.gradle: drop the finalizedBy(":app:buildMetadata") hooks - app/build.gradle: drop the reverse hooks onto subprojects' library tasks; instead order library classes.jar producers (bundleLibCompileToJar) before cleanupAllJars, which consumes them as declared inputs - app/build.gradle: buildMetadata only depends on the selected build type's variant tasks; the generator only reads that variant's classes, and depending on both variants forced a release app build into assembleDebug Test job: forward -Pabis through the runtests Exec layers and pass -Pabis=x86_64 in CI so the emulator run compiles one ABI instead of eight ABI/variant combinations. Toolchain/caching: - pin CMake 3.31.6 (CI previously installed the vestigial 3.6 fork while AGP auto-installed and used 3.22.1) - re-enable the ccache CMake argument (the -DUSE_CCACHE wiring was commented out, so -PuseCCache never reached CMake), drop -PnoCCache in CI, and persist ~/.ccache via actions/cache - enable org.gradle.parallel/org.gradle.caching in CI only (runner-level gradle.properties), and cancel superseded PR runs via concurrency Verified locally: full package build from scratch (three AARs with all four ABIs, classes.jar and prefab intact), :runtime:assembleRelease and :runtime:testDebugUnitTest schedule zero :app tasks, assembleDebug builds only the debug variant and one ABI with metadata present in the APK, and the full on-device suite passes (879 tests, 0 failures). --- .github/workflows/npm_release.yml | 49 ++++++++++++++++-- .github/workflows/pull_request.yml | 51 +++++++++++++++++-- build.gradle | 4 ++ test-app/app/build.gradle | 34 ++++++++----- test-app/runtests.gradle | 4 ++ test-app/runtime/build.gradle | 82 +++--------------------------- 6 files changed, 130 insertions(+), 94 deletions(-) diff --git a/.github/workflows/npm_release.yml b/.github/workflows/npm_release.yml index 901e66ca9..4be63861d 100644 --- a/.github/workflows/npm_release.yml +++ b/.github/workflows/npm_release.yml @@ -16,6 +16,7 @@ env: NPM_TAG: "next" EMULATOR_NAME: "runtime-emu" NDK_VERSION: r29 + CMAKE_VERSION: "3.31.6" ANDROID_API: 33 ANDROID_ABI: x86_64 NDK_ARCH: linux @@ -53,7 +54,7 @@ jobs: uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - name: Setup NDK run: | - echo "y" | sdkmanager "cmake;3.6.4111459" + echo "y" | sdkmanager "cmake;$CMAKE_VERSION" wget -q https://dl.google.com/android/repository/android-ndk-$NDK_VERSION-$NDK_ARCH.zip chmod +x android-ndk-$NDK_VERSION-$NDK_ARCH.zip unzip -q android-ndk-$NDK_VERSION-$NDK_ARCH.zip @@ -62,6 +63,23 @@ jobs: echo ANDROID_NDK_HOME=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ANDROID_NDK=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ${ANDROID_NDK_HOME} >> $GITHUB_PATH + - name: Setup ccache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.ccache + key: ccache-build-${{ github.sha }} + restore-keys: ccache-build- + - name: Configure build caches + run: | + command -v ccache || sudo apt-get install -y ccache + ccache --version + echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=1G" >> $GITHUB_ENV + # the NDK is unzipped fresh every run, so mtime-based compiler + # identification (the default) would invalidate the whole cache + echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV + mkdir -p ~/.gradle + printf 'org.gradle.parallel=true\norg.gradle.caching=true\n' >> ~/.gradle/gradle.properties - name: Install Dependencies run: | npm install @@ -96,7 +114,9 @@ jobs: - name: Fetch prebuilt V8 run: ./download_v8.sh - name: Build npm package - run: ./gradlew -PgitCommitVersion=${{ github.sha }} -PnoCCache --stacktrace + run: ./gradlew -PgitCommitVersion=${{ github.sha }} --stacktrace + - name: ccache stats + run: ccache -s - name: Upload npm package artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -133,7 +153,7 @@ jobs: uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - name: Setup NDK run: | - echo "y" | sdkmanager "cmake;3.6.4111459" + echo "y" | sdkmanager "cmake;$CMAKE_VERSION" wget -q https://dl.google.com/android/repository/android-ndk-$NDK_VERSION-$NDK_ARCH.zip chmod +x android-ndk-$NDK_VERSION-$NDK_ARCH.zip unzip -q android-ndk-$NDK_VERSION-$NDK_ARCH.zip @@ -142,6 +162,26 @@ jobs: echo ANDROID_NDK_HOME=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ANDROID_NDK=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ${ANDROID_NDK_HOME} >> $GITHUB_PATH + - name: Setup ccache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.ccache + key: ccache-test-${{ github.sha }} + restore-keys: ccache-test- + - name: Configure build caches + # no org.gradle.caching here: the SBG AST tests spawn nested `gradlew -b` + # builds that fail with "Build cache controller already set" when the + # build cache is on + run: | + command -v ccache || sudo apt-get install -y ccache + ccache --version + echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=1G" >> $GITHUB_ENV + # the NDK is unzipped fresh every run, so mtime-based compiler + # identification (the default) would invalidate the whole cache + echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV + mkdir -p ~/.gradle + printf 'org.gradle.parallel=true\n' >> ~/.gradle/gradle.properties - name: Install Dependencies run: | npm install @@ -162,7 +202,8 @@ jobs: # this is needed on API 30+ #target: google_apis arch: ${{env.ANDROID_ABI}} - script: ./gradlew runtestsAndVerifyResults --stacktrace + # only build native code for the ABI the emulator actually runs + script: ./gradlew runtestsAndVerifyResults -Pabis=${{env.ANDROID_ABI}} --stacktrace - name: Upload Test Results if: ${{ !cancelled() }} # run this step even if previous step failed uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 947227f53..c4a2ad4f3 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -6,6 +6,7 @@ env: NPM_TAG: "pr" EMULATOR_NAME: "runtime-emu" NDK_VERSION: r29 + CMAKE_VERSION: "3.31.6" ANDROID_API: 33 ANDROID_ABI: x86_64 NDK_ARCH: linux @@ -13,6 +14,10 @@ env: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: name: Build @@ -38,7 +43,7 @@ jobs: uses: android-actions/setup-android@651bceb6f9ca583f16b8d75b62c36ded2ae6fc9c # v4.0.0 - name: Setup NDK run: | - echo "y" | sdkmanager "cmake;3.6.4111459" + echo "y" | sdkmanager "cmake;$CMAKE_VERSION" wget -q https://dl.google.com/android/repository/android-ndk-$NDK_VERSION-$NDK_ARCH.zip chmod +x android-ndk-$NDK_VERSION-$NDK_ARCH.zip unzip -q android-ndk-$NDK_VERSION-$NDK_ARCH.zip @@ -47,6 +52,23 @@ jobs: echo ANDROID_NDK_HOME=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ANDROID_NDK=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ${ANDROID_NDK_HOME} >> $GITHUB_PATH + - name: Setup ccache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.ccache + key: ccache-build-${{ github.sha }} + restore-keys: ccache-build- + - name: Configure build caches + run: | + command -v ccache || sudo apt-get install -y ccache + ccache --version + echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=1G" >> $GITHUB_ENV + # the NDK is unzipped fresh every run, so mtime-based compiler + # identification (the default) would invalidate the whole cache + echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV + mkdir -p ~/.gradle + printf 'org.gradle.parallel=true\norg.gradle.caching=true\n' >> ~/.gradle/gradle.properties - name: Install Dependencies run: | npm install @@ -70,7 +92,9 @@ jobs: - name: Fetch prebuilt V8 run: ./download_v8.sh - name: Build npm package - run: ./gradlew -PgitCommitVersion=${{ github.sha }} -PnoCCache --stacktrace + run: ./gradlew -PgitCommitVersion=${{ github.sha }} --stacktrace + - name: ccache stats + run: ccache -s - name: Upload npm package artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -110,6 +134,26 @@ jobs: echo ANDROID_NDK_HOME=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ANDROID_NDK=${ANDROID_NDK_HOME} >> $GITHUB_ENV echo ${ANDROID_NDK_HOME} >> $GITHUB_PATH + - name: Setup ccache + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.ccache + key: ccache-test-${{ github.sha }} + restore-keys: ccache-test- + - name: Configure build caches + # no org.gradle.caching here: the SBG AST tests spawn nested `gradlew -b` + # builds that fail with "Build cache controller already set" when the + # build cache is on + run: | + command -v ccache || sudo apt-get install -y ccache + ccache --version + echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV + echo "CCACHE_MAXSIZE=1G" >> $GITHUB_ENV + # the NDK is unzipped fresh every run, so mtime-based compiler + # identification (the default) would invalidate the whole cache + echo "CCACHE_COMPILERCHECK=content" >> $GITHUB_ENV + mkdir -p ~/.gradle + printf 'org.gradle.parallel=true\n' >> ~/.gradle/gradle.properties - name: Install Dependencies run: | npm install @@ -130,7 +174,8 @@ jobs: # this is needed on API 30+ #target: google_apis arch: ${{env.ANDROID_ABI}} - script: ./gradlew runtestsAndVerifyResults --stacktrace + # only build native code for the ABI the emulator actually runs + script: ./gradlew runtestsAndVerifyResults -Pabis=${{env.ANDROID_ABI}} --stacktrace - name: Upload Test Results if: ${{ !cancelled() }} # run this step even if previous step failed uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/build.gradle b/build.gradle index 9c401ae8d..221c47de6 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ import groovy.json.JsonOutput def onlyX86 = project.hasProperty("onlyX86") def useCCache = !project.hasProperty("noCCache") def hasNdkVersion = project.hasProperty("ndkVersion") +def selectedAbis = project.hasProperty("abis") ? project.property("abis") : null if (hasNdkVersion) { println "Using NDK version " + ndkVersion @@ -464,6 +465,9 @@ def getRunTestsBuildArguments = { taskName -> if (useCCache) { arguments.add("-PuseCCache") } + if (selectedAbis != null) { + arguments.add("-Pabis=${selectedAbis}") + } arguments += ["--warning-mode", "all"] return arguments } diff --git a/test-app/app/build.gradle b/test-app/app/build.gradle index 686ed8c4b..19b157a67 100644 --- a/test-app/app/build.gradle +++ b/test-app/app/build.gradle @@ -747,6 +747,13 @@ task buildMetadata(type: BuildToolTask) { android.applicationVariants.all { variant -> + // The generator only reads the selected build type's classes (see doFirst), + // so depending on the other variant's tasks would just force a second full + // app build without affecting the produced metadata. + if (variant.buildType.name != project.ext.selectedBuildType) { + return + } + def buildTypeName = variant.buildType.name.capitalize() def mergeShadersTaskName = "merge${buildTypeName}Shaders" def mergeShadersTask = tasks.findByName(mergeShadersTaskName) @@ -1303,18 +1310,19 @@ tasks.configureEach({ DefaultTask currentTask -> }) -rootProject.subprojects.forEach { - it.tasks.configureEach({ DefaultTask currentTask -> - if (currentTask =~ /.+bundleLibCompileToJar.*/) { - currentTask.finalizedBy(cleanupAllJars) - } - - if (currentTask =~ /bundleLibRuntimeToDir.*/) { - currentTask.finalizedBy(buildMetadata) - } - - if (currentTask =~ /compile.*LibraryResources/) { - currentTask.finalizedBy(buildMetadata) +// Do not attach app metadata tasks onto other projects' library tasks: any build +// that actually packages this app reaches buildMetadata through merge*Assets, and +// hooking library tasks forces a full app build into library-only invocations +// (e.g. :runtime:assembleRelease). +// +// Library subprojects' classes.jar is an input of the jar-extraction pipeline +// (see processJar), so its producer must be ordered before cleanupAllJars — +// only for the selected build type; depending on the whole compile classpath +// instead creates a cycle through this app's own compile tasks. +rootProject.subprojects.forEach { sub -> + if (sub.path != project.path) { + sub.tasks.matching { it.name == "bundleLibCompileToJar${project.selectedBuildType.capitalize()}" }.all { producer -> + cleanupAllJars.dependsOn(producer) } - }) + } } diff --git a/test-app/runtests.gradle b/test-app/runtests.gradle index 6af55d065..9cc19e6ff 100644 --- a/test-app/runtests.gradle +++ b/test-app/runtests.gradle @@ -4,6 +4,7 @@ def runOnDeviceOrEmulator = runOnDevice ? "-d" : "-e" def onlyX86 = project.hasProperty("onlyX86") def useCCache = project.hasProperty("useCCache") +def selectedAbis = project.hasProperty("abis") ? project.property("abis") : null // task deleteDist(type: Delete) { // doFirst { @@ -37,6 +38,9 @@ def getBuildArguments = { -> if (useCCache) { arguments.add("-PuseCCache") } + if (selectedAbis != null) { + arguments.add("-Pabis=${selectedAbis}") + } arguments.add("-PuseKotlin=true") diff --git a/test-app/runtime/build.gradle b/test-app/runtime/build.gradle index 0efc7dde5..57a5173d1 100644 --- a/test-app/runtime/build.gradle +++ b/test-app/runtime/build.gradle @@ -115,12 +115,10 @@ android { if (optimizedWithInspector) { arguments.add("-DOPTIMIZED_WITH_INSPECTOR_BUILD=true") } -// -// if (useCCache) { -// arguments.add("-DUSE_CCACHE=true") -// } -// -// arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_static", "-DANDROID_NDK_ROOT=${NDK_PATH}" + + if (useCCache) { + arguments.add("-DUSE_CCACHE=true") + } cppFlags "-std=c++20" arguments "-DANDROID_STL=c++_static", "-DANDROID_NDK_ROOT=${NDK_PATH}", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" @@ -154,7 +152,7 @@ android { } externalNativeBuild { cmake { - // version "3.18.1" + version "3.31.6" path "CMakeLists.txt" } } @@ -247,73 +245,9 @@ tasks.configureEach { task -> task.finalizedBy createPackageConfigFileTask(taskName) } - if (task =~ /configureCMake.*/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /buildCMake.*/) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("syncReleaseLibJars") || taskName.contains("syncDebugLibJars")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("mergeReleaseJniLibFolders") || taskName.contains("mergeDebugJniLibFolders")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("mergeReleaseShaders") || taskName.contains("mergeDebugShaders")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("packageReleaseAssets") || taskName.contains("packageDebugAssets")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("copyReleaseJniLibsProjectOnly") || taskName.contains("copyDebugJniLibsProjectOnly")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("copyReleaseJniLibsProjectAndLocalJars") || taskName.contains("copyDebugJniLibsProjectAndLocalJars")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("generateReleaseLintVitalModel") || taskName.contains("generateDebugLintVitalModel")) { - task.finalizedBy(":app:buildMetadata") - } - - if (taskName.contains("lintVitalAnalyzeRelease") || taskName.contains("lintVitalAnalyzeDebug")) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /lintAnalyze.+AndroidTest/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /compile.+UnitTestJavaWithJavac/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /generate.+LintModel/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /process.+Manifest/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /merge.+Resources/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /verify.+Resources/) { - task.finalizedBy(":app:buildMetadata") - } - - if (task =~ /test.+UnitTest/) { - task.finalizedBy(":app:buildMetadata") - } + // Do not wire :app:buildMetadata onto runtime tasks: the app declares its own + // dependency on it (merge*Assets), and forcing it here drags a full :app build + // (both variants) into every standalone :runtime:assembleRelease. } task 'setPackageVersion' {