Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3240d4a
Add AGP public-API migration docs (contributor doc + breaking-change …
reidbaker Jul 22, 2026
4aefffe
Remove AGP-internal imports from VersionFetcher and migrate compileSd…
reidbaker Jul 22, 2026
12de6b5
Address review feedback: Add ApplicationExtension mock helper and fix…
reidbaker Aug 11, 2026
b55957e
Remove PR 1 markdown files from PR 2
reidbaker Aug 11, 2026
c0d77d3
Address adversarial review feedback: fix missing findByName test mock…
reidbaker Aug 11, 2026
6c3dd8b
Fix isOffline mock in test
reidbaker Aug 11, 2026
4d11eb1
Merge branch 'master' into agp-internal-utils-v3
reidbaker Aug 11, 2026
fdfccbd
Address PR feedback: refactor isHigherThan, extract test helper, remo…
reidbaker-agent Aug 13, 2026
08ece37
Add explanatory comment for unknown version fallback
reidbaker-agent Aug 13, 2026
615bd93
Revert explanatory comment
reidbaker-agent Aug 13, 2026
92ddbf9
Fix ktlint errors in tests
reidbaker-agent Aug 13, 2026
eeedccf
Remove duplicate mock configuration
reidbaker-agent Aug 13, 2026
3d11b6c
Restore ApplicationExtension import in FlutterPluginUtilsTest
reidbaker-agent Aug 14, 2026
5405b90
Clean up redundant fully qualified names
reidbaker-agent Aug 14, 2026
f9e55f7
Add public buildModeFor overloads and wire Flutter dependencies throu…
reidbaker-agent Aug 17, 2026
14b6a37
Merge remote-tracking branch 'origin/master' into agp-buildmode-deps
reidbaker-agent Aug 17, 2026
ee87670
Address adversarial review feedback for PR 3 (agp-buildmode-deps)
reidbaker-agent Aug 17, 2026
dd1223a
Merge remote-tracking branch 'origin/master' into agp-buildmode-deps
reidbaker-agent Aug 17, 2026
441340b
Address review and adversarial feedback: robust CompileSdkVersion fac…
reidbaker-agent Aug 17, 2026
0b4b9e7
Address Round 3 review feedback: namespaced AGP property, CompileSdkV…
reidbaker-agent Aug 17, 2026
8d446dc
Simplify agpVersion comment in build.gradle.kts
reidbaker-agent Aug 17, 2026
7d0ddd1
Document execution safety and purpose of validateNoCommonExtensionInB…
reidbaker-agent Aug 17, 2026
72c595e
Clarify why root gradlew test/check commands do not cross composite b…
reidbaker-agent Aug 17, 2026
4d34a0e
Rename setupLegacyBuildTypeContainers to setupBaseExtensionBuildTypeC…
reidbaker-agent Aug 17, 2026
1d3849f
Address Round 4 review: CompileSdkVersion dual protection, BytecodeVa…
reidbaker-agent Aug 17, 2026
365544e
Add KDocs to BytecodeValidatorTest helper methods
reidbaker-agent Aug 17, 2026
b140c63
Address review: dynamic KotlinCompile destination directory and remov…
reidbaker-agent Aug 20, 2026
cf603bf
Migrate bytecode validation to JUnit test in BytecodeValidatorTest
reidbaker-agent Aug 21, 2026
d23ae5f
Merge remote-tracking branch 'origin/master' into agp-buildmode-deps
reidbaker-agent Aug 21, 2026
31905be
Clean up mock boilerplate in FlutterPluginTest and add unmockkAll tea…
reidbaker-agent Aug 21, 2026
3570a7c
Remove unused imports in PluginHandler.kt
reidbaker-agent Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/flutter_tools/gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
}
}

// The AGP version to compile and test against. Namespaced with `flutter.internal.` to
// avoid collision with user app properties when evaluated as an included build.
val agpVersion: String = providers.gradleProperty("flutter.internal.agpVersion").getOrElse("8.11.1")

dependencies {
// Versions available https://mvnrepository.com/artifact/androidx.annotation/annotation-jvm.
// Version release notes https://developer.android.com/jetpack/androidx/releases/annotation
Expand All @@ -61,13 +65,13 @@ dependencies {
// All kotlinx implementation dependencies must work with the oldest kotlin supported versions.
// Defined in packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0")
// When bumping, also update:
// When bumping the default agpVersion above, also update:
// * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart
// * ndkVersion constant in packages/flutter_tools/lib/src/android/gradle_utils.dart
// * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt
compileOnly("com.android.tools.build:gradle:8.11.1")
compileOnly("com.android.tools.build:gradle:$agpVersion")

testImplementation(kotlin("test"))
testImplementation("com.android.tools.build:gradle:8.11.1")
testImplementation("com.android.tools.build:gradle:$agpVersion")
testImplementation("io.mockk:mockk:1.13.16")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably happened in #190957 but as I was reviewing this code I decided it should live in its own file.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package com.flutter.gradle

/**
* The compile SDK configured on a project's Android extension: either a numeric API level
* (`compileSdk = 36`) or a preview codename (`compileSdkPreview = "Baklava"`). Both are null
* when the DSL has not been configured (yet).
*/
internal data class CompileSdkVersion(
val apiLevel: Int?,
val previewCodename: String?
) {
init {
require(apiLevel == null || previewCodename == null) {
"CompileSdkVersion cannot have both apiLevel ($apiLevel) and previewCodename ($previewCodename) set."
}
}

/**
* Whether this compile SDK is known to be higher than [other].
*
* - numeric vs numeric: numeric comparison.
* - preview vs numeric: a preview codename targets an unreleased SDK, so it is
* considered higher than any numeric API level.
* - preview vs preview: codenames stopped being alphabetically ordered when the
* alphabet reset at "Baklava", so distinct codenames are incomparable and this
* returns false rather than guessing.
* - if either side is unset, returns false.
*/
fun isHigherThan(other: CompileSdkVersion): Boolean {
if (other.previewCodename != null) {
return false
}
if (previewCodename != null && other.apiLevel != null) {
return true
}
if (apiLevel != null && other.apiLevel != null) {
return apiLevel > other.apiLevel
}
return false
}

/** The human-readable form used in log messages, e.g. "35" or "Baklava". */
override fun toString(): String = previewCodename ?: apiLevel?.toString() ?: "unknown"
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,12 @@ object DependencyVersionChecker {
val kgpVersion: Version? = VersionFetcher.getKGPVersion(project)
if (kgpVersion != null) {
checkKGPVersion(kgpVersion, project)
} else {
project.logger.debug(

@reidbaker reidbaker Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is helpful for debugging but also I think it violates our style guidelines. Looking for second opinions.

Argument for is that because this uses the debug level you have to pass -d to get these logs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know mechanically what they would need to pass -d to? I.e. the flutter tool would accept that and print this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would have to run the assembleDebug task manually with the -d flag from the flutter tool the way to get these logs would be to set org.gradle.logging.level=debug in gradle.properties then run the flutter tool (probably with -v)

"Unable to detect project Kotlin Gradle Plugin version. Skipping Kotlin min-version enforcement. " +
"This is expected if the project uses AGP built-in Kotlin (android.builtInKotlin) or does not apply KGP."
)
}
// KGP is not required, so don't log any warning if we can't find the version.
}

private fun configureMinSdkCheck(project: Project) {
Expand Down
10 changes: 5 additions & 5 deletions packages/flutter_tools/gradle/src/main/kotlin/FlutterPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package com.flutter.gradle

import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.BuildType
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.AbstractAppExtension
import com.android.build.gradle.LibraryExtension
Expand All @@ -32,6 +31,7 @@ import java.io.File
import java.nio.charset.StandardCharsets
import java.nio.file.Paths
import java.util.Properties
import com.android.build.api.dsl.BuildType as DslBuildType

class FlutterPlugin : Plugin<Project> {
private var project: Project? = null
Expand Down Expand Up @@ -203,7 +203,7 @@ class FlutterPlugin : Plugin<Project> {
"flutter_proguard_rules.pro"
).toFile()
// TODO(gmackall): reconsider getting the android extension every time
val debugBuildType: BuildType = FlutterPluginUtils.getAndroidExtension(project).buildTypes.getByName("debug")
val debugBuildType: DslBuildType = FlutterPluginUtils.getAndroidExtension(project).buildTypes.getByName("debug")
FlutterPluginUtils.getAndroidExtension(project).buildTypes.create(
"profile",
{
Expand All @@ -214,7 +214,7 @@ class FlutterPlugin : Plugin<Project> {
}
)
if (FlutterPluginUtils.shouldShrinkResources(project)) {
val releaseBuildType: BuildType = FlutterPluginUtils.getAndroidExtension(project).buildTypes.getByName("release")
val releaseBuildType: DslBuildType = FlutterPluginUtils.getAndroidExtension(project).buildTypes.getByName("release")
releaseBuildType.isMinifyEnabled = true
releaseBuildType.isShrinkResources = FlutterPluginUtils.isBuiltAsApp(project)
releaseBuildType.proguardFiles.add(
Expand Down Expand Up @@ -246,12 +246,12 @@ class FlutterPlugin : Plugin<Project> {
}
localEngineHost = engineHostOut.name
}
FlutterPluginUtils.getLegacyAndroidExtension(project).buildTypes.all {
FlutterPluginUtils.getAndroidExtension(project).buildTypes.all {
addFlutterDependencies(this)
}
}

private fun addFlutterDependencies(buildType: com.android.builder.model.BuildType) {
private fun addFlutterDependencies(buildType: DslBuildType) {
FlutterPluginUtils.addFlutterDependencies(
project!!,
buildType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ package com.flutter.gradle

import com.android.build.api.AndroidPluginVersion
import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.ApplicationBuildType
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.DynamicFeatureBuildType
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import com.android.builder.model.BuildType
import com.flutter.gradle.plugins.PluginHandler
import com.flutter.gradle.tasks.DeepLinkJsonFromManifestTask
import com.flutter.gradle.tasks.EnableHcppManifestTask
Expand All @@ -30,6 +31,8 @@ import java.io.File
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.util.Properties
import com.android.build.api.dsl.BuildType as DslBuildType
import com.android.builder.model.BuildType as ModelBuildType

/**
* A collection of static utility functions used by the Flutter Gradle Plugin.
Expand Down Expand Up @@ -473,15 +476,47 @@ object FlutterPluginUtils {
*/
@JvmStatic
@JvmName("buildModeFor")
internal fun buildModeFor(buildType: BuildType): String {
if (buildType.name == "profile") {
internal fun buildModeFor(buildType: ModelBuildType): String = buildModeFor(buildType.name, buildType.isDebuggable)

/**
* Returns a Flutter build mode for a build type identified by [buildTypeName] and its
* [isDebuggable] flag.
*
* @return "debug", "profile", or "release" (fall-back).
*/
@JvmStatic
@JvmName("buildModeFor")
internal fun buildModeFor(
buildTypeName: String,
isDebuggable: Boolean
): String {
if (buildTypeName == "profile") {
return "profile"
} else if (buildType.isDebuggable) {
} else if (isDebuggable) {
return "debug"
}
return "release"
}

/**
* Returns a Flutter build mode for an AGP public DSL [buildType].
*
* Application and dynamic-feature build types expose a public `isDebuggable` flag.
* Library build types do not, so for them the conventional "debug" name is the only
* public signal available at DSL scope.
*/
@JvmStatic
@JvmName("buildModeFor")
internal fun buildModeFor(buildType: DslBuildType): String {
val isDebuggable =
when (buildType) {
is ApplicationBuildType -> buildType.isDebuggable
is DynamicFeatureBuildType -> buildType.isDebuggable
else -> buildType.name == "debug"
}
return buildModeFor(buildType.name, isDebuggable)
}

/**
* Returns true if the build mode is supported by the current call to Gradle.
* This only relevant when using a local engine. Because the engine
Expand Down Expand Up @@ -542,9 +577,10 @@ object FlutterPluginUtils {
@JvmName("getCompileSdkFromProject")
internal fun getCompileSdkFromProject(project: Project): CompileSdkVersion {
val androidExtension = getAndroidExtension(project)
val preview = androidExtension.compileSdkPreview
return CompileSdkVersion(
apiLevel = androidExtension.compileSdk,
previewCodename = androidExtension.compileSdkPreview
apiLevel = if (preview != null) null else androidExtension.compileSdk,
previewCodename = preview
)
}

Expand Down Expand Up @@ -990,7 +1026,7 @@ object FlutterPluginUtils {
@JvmName("addFlutterDependencies")
internal fun addFlutterDependencies(
project: Project,
buildType: BuildType,
buildType: DslBuildType,
pluginHandler: PluginHandler,
engineVersion: String
) {
Expand Down
37 changes: 0 additions & 37 deletions packages/flutter_tools/gradle/src/main/kotlin/VersionFetcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -123,40 +123,3 @@ internal class Version(

override fun toString(): String = "$major.$minor.$patch"
}

/**
* The compile SDK configured on a project's Android extension: either a numeric API level
* (`compileSdk = 36`) or a preview codename (`compileSdkPreview = "Baklava"`). Both are null
* when the DSL has not been configured (yet).
*/
internal data class CompileSdkVersion(
val apiLevel: Int?,
val previewCodename: String?
) {
/**
* Whether this compile SDK is known to be higher than [other].
*
* - numeric vs numeric: numeric comparison.
* - preview vs numeric: a preview codename targets an unreleased SDK, so it is
* considered higher than any numeric API level.
* - preview vs preview: codenames stopped being alphabetically ordered when the
* alphabet reset at "Baklava", so distinct codenames are incomparable and this
* returns false rather than guessing.
* - if either side is unset, returns false.
*/
fun isHigherThan(other: CompileSdkVersion): Boolean {
if (other.previewCodename != null) {
return false
}
if (previewCodename != null && other.apiLevel != null) {
return true
}
if (apiLevel != null && other.apiLevel != null) {
return apiLevel > other.apiLevel
}
return false
}

/** The human-readable form used in log messages, e.g. "35" or "Baklava". */
override fun toString(): String = previewCodename ?: apiLevel?.toString() ?: "unknown"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@

package com.flutter.gradle.plugins

import com.android.builder.model.BuildType
import com.flutter.gradle.CompileSdkVersion
import com.flutter.gradle.FlutterExtension
import com.flutter.gradle.FlutterPluginUtils
import com.flutter.gradle.FlutterPluginUtils.addApiDependencies
import com.flutter.gradle.FlutterPluginUtils.buildModeFor
import com.flutter.gradle.FlutterPluginUtils.getAndroidExtension
import com.flutter.gradle.FlutterPluginUtils.getCompileSdkFromProject
import com.flutter.gradle.FlutterPluginUtils.getLegacyAndroidExtension
import com.flutter.gradle.FlutterPluginUtils.isBuiltAsApp
import com.flutter.gradle.FlutterPluginUtils.supportsBuildMode
import com.flutter.gradle.NativePluginLoaderReflectionBridge
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import java.io.File
import com.android.build.gradle.internal.dsl.BuildType as dslBuildType
import com.android.build.api.dsl.BuildType as DslBuildType

/**
* Handles interactions with the flutter plugins (not Gradle plugins) used by the Flutter project,
Expand Down Expand Up @@ -112,7 +111,7 @@ class PluginHandler(
// Add plugin dependency to the app project. We only want to add dependency
// for dev dependencies in non-release builds.
project.afterEvaluate {
getLegacyAndroidExtension(project).buildTypes.forEach { buildType ->
getAndroidExtension(project).buildTypes.forEach { buildType ->
if (!(pluginObject["dev_dependency"] as Boolean) || buildType.name != "release") {
project.dependencies.add("${buildType.name}Api", pluginProject)
}
Expand All @@ -133,7 +132,7 @@ class PluginHandler(
)
}

getLegacyAndroidExtension(project).buildTypes.forEach { buildType ->
getAndroidExtension(project).buildTypes.forEach { buildType ->
addEmbeddingDependencyToPlugin(project, pluginProject, buildType, engineVersion)
}
}
Expand All @@ -142,7 +141,7 @@ class PluginHandler(
private fun addEmbeddingDependencyToPlugin(
project: Project,
pluginProject: Project,
buildType: BuildType,
buildType: DslBuildType,
engineVersion: String
) {
val flutterBuildMode: String = buildModeFor(buildType)
Expand All @@ -163,8 +162,14 @@ class PluginHandler(
// However, only copy if the plugin is also an app project, since library projects
// cannot have applicationIdSuffix and other app-specific properties.
if (isBuiltAsApp(pluginProject)) {
(getLegacyAndroidExtension(pluginProject).buildTypes as NamedDomainObjectContainer<dslBuildType>)
.addAll(getLegacyAndroidExtension(project).buildTypes as NamedDomainObjectContainer<dslBuildType>)
getLegacyAndroidExtension(project).buildTypes.forEach { appBuildType ->
val pluginBuildTypes = getLegacyAndroidExtension(pluginProject).buildTypes
if (pluginBuildTypes.findByName(appBuildType.name) == null) {
pluginBuildTypes.create(appBuildType.name) {
initWith(appBuildType)
}
}
}
} else {
// For library projects, create compatible build types without app-specific properties
getLegacyAndroidExtension(project).buildTypes.forEach { appBuildType ->
Expand Down Expand Up @@ -213,7 +218,7 @@ class PluginHandler(
}
val pluginProject: Project = project.rootProject.findProject(":$pluginName") ?: return

getLegacyAndroidExtension(project).buildTypes.forEach { buildType ->
getAndroidExtension(project).buildTypes.forEach { buildType ->
val flutterBuildMode: String = buildModeFor(buildType)
if (flutterBuildMode == "release" && (pluginObject["dev_dependency"] as? Boolean == true)) {
// This plugin is a dev dependency will not be included in the
Expand Down
Loading