diff --git a/.circleci/agp9-smoke/.gitignore b/.circleci/agp9-smoke/.gitignore new file mode 100644 index 000000000..67bcc2f72 --- /dev/null +++ b/.circleci/agp9-smoke/.gitignore @@ -0,0 +1,2 @@ +.gradle/ +build/ diff --git a/.circleci/agp9-smoke/.sdkmanrc b/.circleci/agp9-smoke/.sdkmanrc new file mode 100644 index 000000000..06021bdfb --- /dev/null +++ b/.circleci/agp9-smoke/.sdkmanrc @@ -0,0 +1,6 @@ +# JDK 21 and Gradle 9.1 are required by AGP 9. +# This .sdkmanrc is consumed by `sdk env install` in the agp9-smoke-test +# CircleCI job. The project root pins JDK 17 (used by every other job); +# only this smoke test needs JDK 21 + a newer Gradle. +java=21.0.7-librca +gradle=9.1.0 diff --git a/.circleci/agp9-smoke/README.md b/.circleci/agp9-smoke/README.md new file mode 100644 index 000000000..dbc980b23 --- /dev/null +++ b/.circleci/agp9-smoke/README.md @@ -0,0 +1,50 @@ +# AGP 9 smoke test for `purchases_ui_flutter` + +A minimal Gradle-only fixture that exercises the AGP-9 branch of +`purchases_ui_flutter/android/build.gradle`, without needing Flutter +or a full app build. + +## What it tests + +`purchases_ui_flutter/android/build.gradle` switches behaviour based on the +detected Android Gradle Plugin major version: + +- `agpMajor < 9` — applies the `kotlin-android` plugin and uses the + legacy `android.kotlinOptions { … }` DSL. +- `agpMajor >= 9` — relies on AGP's built-in Kotlin and uses the + top-level `kotlin { compilerOptions { … } }` DSL. + +The first branch is exercised by every regular CI build (the +`purchase_tester` example pins AGP 8 / KGP 1.x). + +The AGP 9 branch only runs when a downstream user is on a recent enough +Flutter (3.44+) and AGP 9. This fixture lets CI catch regressions on +that branch at configuration time. + +## How it works + +`settings.gradle` pins AGP `9.0.1` and KGP `2.2.10` via +`pluginManagement`, then includes the plugin's `android/` directory as +a subproject. The plugin's own `buildscript { }` declares a lower AGP +version; Gradle resolves the classpath to the highest declared version, +so the plugin's `build.gradle` is evaluated under AGP 9 here. + +`run.sh` asserts: + +1. `agpMajor == 9` after the plugin's `buildscript { }` runs — proves + the AGP-9 branches of the plugin's `build.gradle` are reached. +2. The `compileDebugKotlin` task graph resolves under `--dry-run` — + proves AGP applied cleanly and the new top-level + `kotlin.compilerOptions` DSL is valid. + +This is a configuration-time smoke test, not a full build. It will not +catch issues that only manifest during actual compilation (e.g. Kotlin +source incompatibilities with KGP 2.x). + +## Running locally + +Requires JDK 21, Gradle 9.1+, and an Android SDK installed. + +```bash +bash .circleci/agp9-smoke/run.sh +``` diff --git a/.circleci/agp9-smoke/run.sh b/.circleci/agp9-smoke/run.sh new file mode 100755 index 000000000..bf5d98663 --- /dev/null +++ b/.circleci/agp9-smoke/run.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Smoke-test purchases_ui_flutter/android/build.gradle under AGP 9. +# +# Uses the sibling settings.gradle, which pins AGP 9.0.1 + KGP 2.2.10 +# via pluginManagement. The plugin's own buildscript declares a lower +# AGP version, but Gradle resolves the classpath to the highest, so +# the plugin's build.gradle is evaluated with AGP 9 here. +# +# Assertions: +# 1. agpMajor == 9 (so the AGP 9 else-branch in build.gradle ran). +# 2. compileDebugKotlin task graph resolves (built-in Kotlin works). +# +# This is a configuration-time smoke test, not a full build. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +GRADLE_ARGS=(--no-daemon -q) + +echo "==> Asserting agpMajor == 9" +agp_major="$(gradle "${GRADLE_ARGS[@]}" :purchases_ui_flutter:properties | awk '/^agpMajor: / {print $2}')" +if [[ "$agp_major" != "9" ]]; then + echo "FAIL: expected agpMajor=9 but got '${agp_major}'." >&2 + echo " The AGP 9 branch of purchases_ui_flutter/android/build.gradle was not exercised." >&2 + exit 1 +fi +echo "OK: agpMajor=${agp_major}" + +echo "==> Asserting compileDebugKotlin task graph resolves under AGP 9" +gradle "${GRADLE_ARGS[@]}" :purchases_ui_flutter:compileDebugKotlin --dry-run +echo "OK: AGP 9 + built-in Kotlin wired up correctly" + +echo "==> AGP 9 smoke test passed" diff --git a/.circleci/agp9-smoke/settings.gradle b/.circleci/agp9-smoke/settings.gradle new file mode 100644 index 000000000..97c87d348 --- /dev/null +++ b/.circleci/agp9-smoke/settings.gradle @@ -0,0 +1,30 @@ +// Settings file for the AGP 9 smoke test of purchases_ui_flutter. +// See README.md in this directory for context. + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +// Pin the AGP and Kotlin Gradle Plugin versions used during this smoke +// test. The plugin's own buildscript declares a lower AGP version; Gradle +// resolves the buildscript classpath to the highest declared version, so +// the plugin gets evaluated with AGP 9 here. +plugins { + id "com.android.application" version "9.0.1" apply false + id "org.jetbrains.kotlin.android" version "2.2.10" apply false +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "agp9-smoke" +include ":purchases_ui_flutter" +project(":purchases_ui_flutter").projectDir = file("../../purchases_ui_flutter/android") diff --git a/.circleci/config.yml b/.circleci/config.yml index fc6071cde..e0e7bb668 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -324,6 +324,27 @@ jobs: ./gradlew app:assembleAndroidTest ./gradlew app:assembleDebug -Ptarget=`pwd`/../integration_test/app_test.dart + agp9-smoke-test: + description: "Smoke-test purchases_ui_flutter under AGP 9 (configuration-time only)" + docker: + - image: ghcr.io/cirruslabs/flutter:stable + resource_class: medium + steps: + - checkout + - revenuecat/install-sdkman + - run: + name: Install JDK 21 and Gradle 9.1 (required by AGP 9) + working_directory: .circleci/agp9-smoke + command: sdk env install + - run: + name: Print versions + command: | + java -version + gradle --version + - run: + name: Run AGP 9 smoke test + command: bash .circleci/agp9-smoke/run.sh + android-integration-test: description: "Run Android integration tests for flutter" <<: *android-machine-emulator @@ -589,6 +610,7 @@ workflows: - ios-integration-test-spm - macos-integration-test-spm - android-integration-test-build + - agp9-smoke-test - run-maestro-e2e-tests-ios: context: - e2e-tests diff --git a/android/build.gradle b/android/build.gradle index 33c0374e9..ec580446e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,6 @@ group 'com.revenuecat.purchases_flutter' version '10.2.0' buildscript { - ext.kotlin_version = '1.8.22' ext.common_version = '18.9.1' repositories { google() @@ -11,7 +10,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.13.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -23,7 +21,6 @@ rootProject.allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' android { compileSdk 34 @@ -38,10 +35,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - defaultConfig { minSdkVersion 21 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -64,6 +57,5 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "com.revenuecat.purchases:purchases-hybrid-common:$common_version" } diff --git a/purchases_ui_flutter/android/build.gradle b/purchases_ui_flutter/android/build.gradle index e8f3fc99d..f36ba3bc0 100644 --- a/purchases_ui_flutter/android/build.gradle +++ b/purchases_ui_flutter/android/build.gradle @@ -1,9 +1,15 @@ group 'com.revenuecat.purchases_ui_flutter' version '10.2.0' +// Apply the legacy kotlin-android plugin only when AGP isn't providing +// Kotlin itself — i.e. AGP < 9, or AGP 9+ with `android.builtInKotlin=false`. See: +// https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors buildscript { ext.kotlin_version = '1.9.20' ext.common_version = '18.9.1' + ext.agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0] as int + ext.builtInKotlinEnabled = (findProperty('android.builtInKotlin') ?: 'true') == 'true' + ext.applyLegacyKgp = agpMajor < 9 || !builtInKotlinEnabled repositories { google() mavenCentral() @@ -11,7 +17,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.13.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + if (applyLegacyKgp) { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } } @@ -23,7 +31,10 @@ allprojects { } apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' + +if (applyLegacyKgp) { + apply plugin: 'kotlin-android' +} android { if (project.android.hasProperty("namespace")) { @@ -35,10 +46,6 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } - sourceSets { main.java.srcDirs += 'src/main/kotlin' } @@ -56,3 +63,19 @@ android { implementation 'androidx.compose.ui:ui-android:1.5.4' } } + +// jvmTarget DSL matches the Kotlin plugin in use: `android.kotlinOptions` +// for kotlin-android, top-level `kotlin {}` for AGP's built-in Kotlin. +if (applyLegacyKgp) { + android { + kotlinOptions { + jvmTarget = '1.8' + } + } +} else { + kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8 + } + } +}