Skip to content

build: AGP 9.4.1 and Gradle 9.6.0 - #2052

Draft
edusperoni wants to merge 9 commits into
mainfrom
feat/agp-9-gradle-9
Draft

edusperoni wants to merge 9 commits into
mainfrom
feat/agp-9-gradle-9

Conversation

@edusperoni

@edusperoni edusperoni commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

Migrates the build to what current Android Studio uses: AGP 9.4.1 + Gradle 9.6.0, with no opt-out flags (android.newDsl, android.builtInKotlin stay at their AGP 9 defaults). Targeted at the 9.2 release. Draft because several items below change what every NativeScript app build sees and need a maintainer decision.

The full device suite passes on an API 36 arm64 emulator (runtestsAndVerifyResults -Pabis=arm64-v8a: 1248 passed, 0 failed), the CI Build job's root ./gradlew builds all four ABIs, runSbgTests passes, and --warning-mode all reports 0 Gradle deprecations from this repo and 0 D8 Kotlin-metadata warnings (AGP 9.4.1 bundles R8 9.4.24, so the R8 pin from #2050 is gone).

Commits

  1. Gradle 9.6.0 wrappers and AGP 9.4.1. test-app and root wrappers regenerated. SDK Build Tools to 36.0.0 (AGP 9.4 minimum); compileSdk stays 35 since it drives the metadata generator and therefore the API surface every app sees. Removed the com.android.tools:r8 pin (NS_DEFAULT_R8_VERSION / -Pr8Version, only ever shipped in today's dev builds) and the groovy-all classpath entry (a Gradle 7 workaround; Gradle 9 bundles Groovy 4). BuildToolTask is now abstract, since Gradle 9 made JavaExec's injected services abstract.
  2. Built-in Kotlin and the androidComponents variant API in app/build.gradle. kotlin-android and kotlinOptions are gone (kotlin-parcelize stays and works). onVariants for the selected build type captures the compile classpath and SingleArtifact.ASSETS; the compile classpath is also an input of cleanupAllJars, because AGP 9 builds the compile-time R.jar in generate<Variant>RFile and the build otherwise fails on an implicit dependency. The 19 per-task findByName blocks on buildMetadata are one lazy dependsOn. SDK directory comes from sdkComponents.sdkDirectory; the platform name is built from compileSdk/compileSdkMinor/compileSdkPreview. Task.project reads at execution time are gone. validateAppIdMatch referenced an undefined appIdentifier, so it threw instead of warning whenever app.gradle overrode applicationId; fixed. In runtime/build.gradle: proguard-android.txtproguard-android-optimize.txt (AGP 9 rejects the old file; minify is off, output unchanged), library targetSdkVersiontestOptions/lint targetSdk, and createPackageConfigFileTask deleted (it hooked bundleDebug/bundleRelease, which don't exist on a library, and used the removed Project.exec).
  3. Gradle 10 deprecations. Gradle 9 deprecates implicit lookup of a parent project's extra properties, which the app script used ~25 times (outLogger, nativescriptDependencies, additional_gradle.properties values, ...) and plugin include.gradle files use too. The root's allprojects.beforeEvaluate now copies the root extra properties into each subproject, the same way userDefinedGradleProperties already was, skipping -P names so they keep precedence. Multi-string dependency notation converted; kotlinOptionscompilerOptions in the metadata generator.
  4. -b is removed in Gradle 9. test-app/runtests.gradle moved to test-app/runtests/ with its own settings.gradle, run with -p runtests; its Exec tasks keep test-app as the working directory. The ccache probe uses providers.exec (under Gradle 9 the old Project.exec call was silently caught and printed "CCache not found!" on every build).
  5. Jetifier no longer enabled. AGP 9 warns it is deprecated, defaults it to false, and removes it in AGP 10. See open question 2.
  6. CI: test jobs enable org.gradle.caching like the build job. The comment that justified disabling it described the old -b nested builds. Verified locally, not yet on CI.
  7. Root getPackageVersion/getCommitVersion read their -P properties at configuration time instead of via Task.project in doLast.
  8. Metadata for Kotlin classes (found by the device tests). With built-in Kotlin, AGP writes Kotlin classes to intermediates/built_in_kotlinc/<buildType>/<task>/classes, not tmp/kotlin-classes. buildMetadata now scans both, otherwise Kotlin classes are missing from the metadata (31 Kotlin specs failed). This affects every app with Kotlin code.

Open questions (shipped-behavior decisions)

  1. android.dependency.useConstraints (most important). AGP 9 defaults it to false, so compile-classpath versions are no longer aligned with runtime-classpath versions. In the test app the compile classpath now resolves core-ktx 1.2.0 / core-runtime 2.1.0 while the runtime ships 1.13.0 / 2.2.0 (AGP 8 aligned both). Metadata and SBG jars are built from the compile classpath, so apps can get metadata for older API versions than they ship. Options: set android.dependency.useConstraints=true in the shipped gradle.properties (AGP 8 behavior, slower configuration) or accept the drift. Left at the AGP default here.
  2. Jetifier. Apps that still depend on support-library artifacts break. Setting it in App_Resources/Android/gradle.properties does not re-enable it (those become extra properties, which AGP never reads); only -P, ~/.gradle/gradle.properties or a CLI change would. Revert commit 5 if that is too aggressive before AGP 10.
  3. Plugin include.gradle / user app.gradle compatibility. Anything using apply plugin: 'kotlin-android', kotlinOptions {}, android.applicationVariants/libraryVariants/variantFilter or android.sdkDirectory now fails. android.newDsl=false / android.builtInKotlin=false restore those until AGP 10, but they are Gradle properties, so an escape hatch would have to be written by the CLI into the generated platforms/android/gradle.properties.
  4. App R class is no longer final (android.enableAppCompileTimeRClass default true). User Java in App_Resources/Android/src that does switch on R.id.* stops compiling. Default kept.
  5. Other AGP 9 defaults kept, none broke the build: uniquePackageNames, defaultTargetSdkToCompileSdkIfUnset (the script always sets targetSdk), onlyEnableUnitTestForTheTestedBuildType, proguard.failOnMissingFiles, r8.strictFullModeForKeepRules, r8.optimizedResourceShrinking (only matter with minify/shrinking on), proguardAndroidTxt.disallowed (the shipped app script references no proguard file).
  6. getMergedAssetsOutputPath now throws a clear error when no variant of the selected build type exists, instead of falling back to an AGP 3.5 path that no longer exists.

Not done

  • dts-generator's own wrapper stays at 8.9 (CI builds it through test-app's wrapper). Its one Gradle 10 deprecation is fixed upstream in android-dts-generator 5fcf611, which the submodule now points at.
  • Configuration 'debugCompileClasspath' was resolved during configuration time is an AGP performance warning from the per-jar extraction tasks being created at configuration time (existing design); fixing it means one extraction task at execution time.
  • The revertVersionFile hook matches task names containing Strip, while AGP's task is stripReleaseDebugSymbols (lowercase), so the root build leaves runtime/src/main/cpp/Version.h modified. Not checked whether main behaves the same.

Android Studio now builds with AGP 9.4, whose minimum (and default) Gradle
is 9.6.0, and apps pick up these wrapper and plugin versions from the
packaged project template.

- test-app and root wrappers move to Gradle 9.6.0 (scripts and wrapper
  jar regenerated by the wrapper task).
- NS_DEFAULT_ANDROID_BUILD_TOOLS_VERSION -> 9.4.1 and the default build
  tools -> 36.0.0, the minimum AGP 9.4 accepts. compileSdk stays at 35
  since it decides which APIs the metadata generator exposes to apps.
- The com.android.tools:r8 buildscript pin (and NS_DEFAULT_R8_VERSION /
  -Pr8Version) is dropped: it only existed because the R8 bundled with
  AGP 8.13 could not read Kotlin 2.4 metadata, and AGP 9.4 bundles a
  newer R8.
- The explicit org.apache.groovy:groovy-all classpath entry dates from
  the Gradle 7 migration; Gradle 9 ships Groovy 4 itself.
- BuildToolTask becomes abstract: Gradle 9 turned JavaExec's injected
  services into abstract getters, so a concrete subclass no longer
  compiles.

The app module still applies kotlin-android, which AGP 9 rejects; that is
addressed in the following commit.
… API

AGP 9 compiles Kotlin itself and fails the build when kotlin-android is
applied, and its new DSL hides the legacy variant API
(android.applicationVariants, android.sdkDirectory, compileSdkVersion)
that the app script relied on. The script is copied into every app, so
it is migrated rather than opting out with android.newDsl=false or
android.builtInKotlin=false.

app/build.gradle:
- Drop `apply plugin: 'kotlin-android'` and `kotlinOptions`; the Kotlin
  JVM target already defaults to compileOptions.targetCompatibility (17).
  kotlin-parcelize stays, it is a compiler plugin that works with
  built-in Kotlin.
- androidComponents.onVariants for the selected build type captures the
  compile classpath (for the jar extraction tasks) and the merged ASSETS
  artifact (the directory copyMetadata writes into). The classpath is
  resolved in afterEvaluate, as before, and is now also an input of
  cleanupAllJars so the producers of generated jars on it (the
  compile-time R class jar that AGP 9 builds in generate<Variant>RFile)
  run first.
- buildMetadata's per-variant task lookups become one lazily resolved
  dependsOn over the same task names for the selected build type.
- The SDK directory comes from androidComponents.sdkComponents and the
  platform name is built from compileSdk/compileSdkMinor/compileSdkPreview.
- cleanupAllJars, buildMetadata and validateAppIdMatch no longer read
  Task.project at execution time. validateAppIdMatch also compared the
  namespace against an undefined `appIdentifier`, which threw instead of
  warning whenever app.gradle changed applicationId.

runtime/build.gradle:
- getDefaultProguardFile('proguard-android.txt') is rejected by AGP 9;
  use proguard-android-optimize.txt (release minification is disabled
  for the runtime, so the output is unchanged).
- Library modules have no targetSdk in the AGP 9 DSL; set it for tests
  and lint instead.
- Remove createPackageConfigFileTask: it only hooked tasks named
  bundleDebug/bundleRelease, which a library module does not have, and
  it relied on APIs Gradle 9 removed (Project.exec, archivesBaseName).
- Gradle 9 deprecates resolving an undeclared property from the parent
  project. The app script, and plugin include.gradle files applied to
  it, read the root's extra properties that way (outLogger,
  nativescriptDependencies, getAppResourcesPath, the
  additional_gradle.properties values, ...). The root now copies its
  extra properties into each subproject before it is evaluated, the same
  way it already does for the App_Resources gradle.properties, skipping
  -P properties so those keep their own value in the subproject.
- Dependencies declared with group/name/version maps become
  single-string coordinates.
- The metadata generator's compileKotlin uses compilerOptions instead of
  kotlinOptions.
Gradle 9 removed the -b/--build-file option and Project.exec.

- runTests/runTestsAndVerifyResults ran `gradlew -b runtests.gradle` in
  test-app. That script is now its own build in test-app/runtests (with
  a settings.gradle) and is run with `-p runtests`. Its Exec tasks keep
  test-app as working directory, which the node tools and the nested
  `gradlew :app:installDebug` depend on.
- The CCache version probe used Project.exec, which no longer exists; the
  surrounding catch turned that into "CCache not found!" on every build.
  It now uses providers.exec.
AGP 9 deprecates android.enableJetifier (its default is now false and the
option goes away in AGP 10) and warns on every build that sets it.
Jetifier rewrites pre-AndroidX support-library references in third-party
binaries; an app that still depends on a support-library artifact needs
android.enableJetifier=true passed as a Gradle property (for example
-Pandroid.enableJetifier=true or ~/.gradle/gradle.properties).
The test jobs left org.gradle.caching off because the SBG AST tests ran
nested `gradlew -b` builds, which failed with "Build cache controller
already set" when the cache was on. Those builds now use `-p` with a
settings file (and -b no longer exists in Gradle 9), so the test jobs
use the same Gradle settings as the build job.
getPackageVersion and getCommitVersion looked up -PpackageVersion,
-PpreReleaseVersion and -PgitCommitVersion through Task.project inside
doLast, which Gradle 9 deprecates and Gradle 10 rejects. They are now
read when the tasks are configured.
…ator

With built-in Kotlin, AGP writes the Kotlin classes to
intermediates/built_in_kotlinc/<buildType>/<compile task>/classes rather
than KGP's tmp/kotlin-classes/<buildType>. buildMetadata only scanned the
latter, so app and plugin Kotlin classes were missing from the metadata
and 31 Kotlin specs in the test app failed. Both locations are scanned,
so a project module still built by KGP keeps working.
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Brings in android-dts-generator 5fcf611 so the submodule no longer trips
Gradle's multi-string dependency deprecation, the last one left in the build.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant