Browse Source

[gradle] Support new AGP with androidLibrary target (#5157)

Since AGP `8.8.0-alpha08` there was added support generated assets in
the new `androidLibrary` target.
We have to support a new target configuration and work with compose
multiplatform resources

Fixes https://youtrack.jetbrains.com/issue/CMP-6982

## Testing
- Added gradle tests

## Release Notes
### Features - Gradle Plugin
- Support compose resources in `androidLibrary` target
pull/5158/head v1.8.0-dev1899
Konstantin 1 month ago committed by GitHub
parent
commit
5e6021212d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      .github/workflows/gradle-plugin.yml
  2. 19
      gradle-plugins/build.gradle.kts
  3. 134
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt
  4. 15
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt
  5. 4
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/MultimoduleResources.kt
  6. 4
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/SinglemoduleResources.kt
  7. 4
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt
  8. 22
      gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt
  9. 2
      gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/settings.gradle
  10. 37
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/build.gradle.kts
  11. 4
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/src/androidMain/AndroidManifest.xml
  12. 3
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/src/commonMain/composeResources/values/strings.xml
  13. 17
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/src/commonMain/kotlin/me/sample/app/App.kt
  14. 7
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/build.gradle.kts
  15. 26
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/featureModule/build.gradle.kts
  16. 3
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/featureModule/src/commonMain/composeResources/values/strings.xml
  17. 12
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/featureModule/src/commonMain/kotlin/me/sample/app/Feature.kt
  18. 3
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/gradle.properties
  19. 29
      gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/settings.gradle.kts
  20. 4
      gradle-plugins/gradle.properties
  21. 2
      gradle-plugins/gradle/libs.versions.toml

4
.github/workflows/gradle-plugin.yml

@ -17,8 +17,8 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-14, windows-2022]
gradle: [7.4, 8.8]
agp: [8.1.0, 8.5.0]
gradle: [7.4, 8.10.2]
agp: [8.1.0, 8.5.0, 8.8.0-alpha08]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3

19
gradle-plugins/build.gradle.kts

@ -1,4 +1,5 @@
import com.gradle.publish.PluginBundleExtension
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
@ -20,8 +21,8 @@ subprojects {
plugins.withId("java") {
configureIfExists<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
withJavadocJar()
withSourcesJar()
@ -30,11 +31,13 @@ subprojects {
plugins.withId("org.jetbrains.kotlin.jvm") {
tasks.withType(KotlinJvmCompile::class).configureEach {
// must be set to a language version of the kotlin compiler & runtime,
// which is bundled to the oldest supported Gradle
kotlinOptions.languageVersion = "1.5"
kotlinOptions.apiVersion = "1.5"
kotlinOptions.jvmTarget = "1.8"
compilerOptions {
// must be set to a language version of the kotlin compiler & runtime,
// which is bundled to the oldest supported Gradle
languageVersion.set(KotlinVersion.KOTLIN_1_5)
apiVersion.set(KotlinVersion.KOTLIN_1_5)
jvmTarget.set(JvmTarget.JVM_11)
}
}
}

134
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/AndroidResources.kt

@ -1,8 +1,10 @@
package org.jetbrains.compose.resources
import com.android.build.api.dsl.KotlinMultiplatformAndroidTarget
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.Component
import com.android.build.api.variant.HasAndroidTest
import com.android.build.api.variant.KotlinMultiplatformAndroidComponentsExtension
import com.android.build.api.variant.Sources
import com.android.build.gradle.internal.lint.AndroidLintAnalysisTask
import com.android.build.gradle.internal.lint.LintModelWriterTask
import org.gradle.api.DefaultTask
@ -25,29 +27,122 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import java.io.File
import javax.inject.Inject
internal fun Project.configureAndroidComposeResources(moduleResourceDir: Provider<File>? = null) {
//copy all compose resources to android assets
val androidComponents = project.extensions.findByType(AndroidComponentsExtension::class.java) ?: return
//copy all compose resources to android assets
internal fun Project.configureAndroidComposeResources(
agpPluginId: String,
moduleResourceDir: Provider<File>? = null
) {
val kotlinExtension = extensions.findByType(KotlinMultiplatformExtension::class.java) ?: return
if (agpPluginId != AGP_KMP_LIB_ID) {
extensions.findByType(AndroidComponentsExtension::class.java)?.let { androidComponents ->
configureAndroidComposeResources(kotlinExtension, androidComponents, moduleResourceDir)
}
} else {
@Suppress("UnstableApiUsage")
extensions.findByType(KotlinMultiplatformAndroidComponentsExtension::class.java)?.let { androidComponents ->
configureAndroidComposeResources(kotlinExtension, androidComponents, moduleResourceDir)
}
}
}
private fun Project.configureAndroidComposeResources(
kotlinExtension: KotlinMultiplatformExtension,
androidComponents: AndroidComponentsExtension<*, *, *>,
moduleResourceDir: Provider<File>?
) {
logger.info("Configure compose resources with AndroidComponentsExtension")
androidComponents.onVariants { variant ->
configureGeneratedAndroidComponentAssets(variant, moduleResourceDir)
val componentAssets = getAndroidComponentComposeResources(kotlinExtension, variant.name)
configureGeneratedAndroidComponentAssets(
variant.name,
variant.sources,
componentAssets,
moduleResourceDir
)
if (variant is HasAndroidTest) {
variant.androidTest?.let { androidTest ->
configureGeneratedAndroidComponentAssets(androidTest, moduleResourceDir)
val androidTestAssets = getAndroidComponentComposeResources(kotlinExtension, androidTest.name)
configureGeneratedAndroidComponentAssets(
androidTest.name,
androidTest.sources,
androidTestAssets,
moduleResourceDir
)
}
}
}
}
private fun Project.getAndroidComponentComposeResources(
kotlinExtension: KotlinMultiplatformExtension,
componentName: String
): FileCollection = project.files({
kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).flatMap { androidTarget ->
androidTarget.compilations.flatMap { compilation ->
if (compilation.androidVariant.name == componentName) {
compilation.allKotlinSourceSets.map { kotlinSourceSet ->
getPreparedComposeResourcesDir(kotlinSourceSet)
}
} else emptyList()
}
}
})
@Suppress("UnstableApiUsage")
private fun Project.configureAndroidComposeResources(
kotlinExtension: KotlinMultiplatformExtension,
androidComponents: KotlinMultiplatformAndroidComponentsExtension,
moduleResourceDir: Provider<File>?
) {
logger.info("Configure compose resources with KotlinMultiplatformAndroidComponentsExtension")
androidComponents.onVariant { variant ->
val variantAssets = getAndroidKmpComponentComposeResources(kotlinExtension, variant.name)
configureGeneratedAndroidComponentAssets(
variant.name,
variant.sources,
variantAssets,
moduleResourceDir
)
variant.androidTest?.let { androidTest ->
val androidTestAssets = getAndroidKmpComponentComposeResources(kotlinExtension, androidTest.name)
configureGeneratedAndroidComponentAssets(
androidTest.name,
androidTest.sources,
androidTestAssets,
moduleResourceDir
)
}
}
}
@Suppress("UnstableApiUsage")
private fun Project.getAndroidKmpComponentComposeResources(
kotlinExtension: KotlinMultiplatformExtension,
componentName: String
): FileCollection = project.files({
kotlinExtension.targets.withType(KotlinMultiplatformAndroidTarget::class.java).flatMap { androidTarget ->
androidTarget.compilations.flatMap { compilation ->
if (compilation.componentName == componentName) {
compilation.allKotlinSourceSets.map { kotlinSourceSet ->
getPreparedComposeResourcesDir(kotlinSourceSet)
}
} else emptyList()
}
}
})
private fun Project.configureGeneratedAndroidComponentAssets(
component: Component,
componentName: String,
componentSources: Sources,
componentAssets: FileCollection,
moduleResourceDir: Provider<File>?
) {
val kotlinExtension = project.extensions.findByType(KotlinMultiplatformExtension::class.java) ?: return
val camelComponentName = component.name.uppercaseFirstChar()
val componentAssets = getAndroidComponentComposeResources(kotlinExtension, component.name)
logger.info("Configure ${component.name} resources for 'android' target")
logger.info("Configure $componentName resources for 'android' target")
val camelComponentName = componentName.uppercaseFirstChar()
val copyComponentAssets = registerTask<CopyResourcesToAndroidAssetsTask>(
"copy${camelComponentName}ComposeResourcesToAndroidAssets"
) {
@ -55,7 +150,7 @@ private fun Project.configureGeneratedAndroidComponentAssets(
moduleResourceDir?.let { relativeResourcePlacement.set(it) }
}
component.sources.assets?.addGeneratedSourceDirectory(
componentSources.assets?.addGeneratedSourceDirectory(
copyComponentAssets,
CopyResourcesToAndroidAssetsTask::outputDirectory
)
@ -71,21 +166,6 @@ private fun Project.configureGeneratedAndroidComponentAssets(
}
}
private fun Project.getAndroidComponentComposeResources(
kotlinExtension: KotlinMultiplatformExtension,
componentName: String
): FileCollection = project.files({
kotlinExtension.targets.withType(KotlinAndroidTarget::class.java).flatMap { androidTarget ->
androidTarget.compilations.flatMap { compilation ->
if (compilation.androidVariant.name == componentName) {
compilation.allKotlinSourceSets.map { kotlinSourceSet ->
getPreparedComposeResourcesDir(kotlinSourceSet)
}
} else emptyList()
}
}
})
//Copy task doesn't work with 'variant.sources?.assets?.addGeneratedSourceDirectory' API
internal abstract class CopyResourcesToAndroidAssetsTask : DefaultTask() {
@get:Inject

15
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/ComposeResources.kt

@ -15,10 +15,9 @@ internal const val COMPOSE_RESOURCES_DIR = "composeResources"
internal const val RES_GEN_DIR = "generated/compose/resourceGenerator"
internal const val KMP_RES_EXT = "multiplatformResourcesPublication"
private const val MIN_GRADLE_VERSION_FOR_KMP_RESOURCES = "7.6"
private val androidPluginIds = listOf(
"com.android.application",
"com.android.library"
)
private const val AGP_APP_ID = "com.android.application"
private const val AGP_LIB_ID = "com.android.library"
internal const val AGP_KMP_LIB_ID = "com.android.kotlin.multiplatform.library"
internal fun Project.configureComposeResources(extension: ResourcesExtension) {
val config = provider { extension }
@ -64,10 +63,8 @@ internal fun Project.onKotlinJvmApplied(config: Provider<ResourcesExtension>) {
configureJvmOnlyResources(kotlinExtension, config)
}
internal fun Project.onAgpApplied(block: () -> Unit) {
androidPluginIds.forEach { pluginId ->
plugins.withId(pluginId) {
block()
}
internal fun Project.onAgpApplied(block: (pluginId: String) -> Unit) {
listOf(AGP_APP_ID, AGP_LIB_ID, AGP_KMP_LIB_ID).forEach { pluginId ->
plugins.withId(pluginId) { block(pluginId) }
}
}

4
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/MultimoduleResources.kt

@ -37,8 +37,8 @@ internal fun Project.configureMultimoduleResources(
.all { target -> configureTargetResources(target, moduleIsolationDirectory) }
//configure ANDROID resources
onAgpApplied {
configureAndroidComposeResources(moduleIsolationDirectory)
onAgpApplied { agpId ->
configureAndroidComposeResources(agpId, moduleIsolationDirectory)
fixAndroidLintTaskDependencies()
}
}

4
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/resources/SinglemoduleResources.kt

@ -27,8 +27,8 @@ internal fun Project.configureSinglemoduleResources(
}
}
onAgpApplied {
configureAndroidComposeResources()
onAgpApplied { agpId ->
configureAndroidComposeResources(agpId)
fixAndroidLintTaskDependencies()
}
}

4
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/GradlePluginTest.kt

@ -11,6 +11,7 @@ import org.gradle.util.GradleVersion
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.PreviewLogger
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.RemoteConnection
import org.jetbrains.compose.desktop.ui.tooling.preview.rpc.receiveConfigFromGradle
import org.jetbrains.compose.internal.Version
import org.jetbrains.compose.test.utils.GradlePluginTestBase
import org.jetbrains.compose.test.utils.checkExists
import org.jetbrains.compose.test.utils.checks
@ -77,7 +78,8 @@ class GradlePluginTest : GradlePluginTestBase() {
@Test
fun newAndroidTarget() {
Assumptions.assumeTrue(defaultTestEnvironment.parsedGradleVersion >= GradleVersion.version("8.0.0"))
Assumptions.assumeTrue(defaultTestEnvironment.parsedGradleVersion >= GradleVersion.version("8.10.2"))
Assumptions.assumeTrue(Version.fromString(defaultTestEnvironment.agpVersion) >= Version.fromString("8.8.0-alpha08"))
with(testProject("application/newAndroidTarget")) {
gradle("build", "--dry-run").checks {
}

22
gradle-plugins/compose/src/test/kotlin/org/jetbrains/compose/test/tests/integration/ResourcesTest.kt

@ -2,6 +2,7 @@ package org.jetbrains.compose.test.tests.integration
import org.gradle.util.GradleVersion
import org.jetbrains.compose.desktop.application.internal.ComposeProperties
import org.jetbrains.compose.internal.Version
import org.jetbrains.compose.internal.utils.Arch
import org.jetbrains.compose.internal.utils.OS
import org.jetbrains.compose.internal.utils.currentArch
@ -315,6 +316,27 @@ class ResourcesTest : GradlePluginTestBase() {
}
}
@Test
fun testNewAgpResources() {
Assumptions.assumeTrue(defaultTestEnvironment.parsedGradleVersion >= GradleVersion.version("8.10.2"))
Assumptions.assumeTrue(Version.fromString(defaultTestEnvironment.agpVersion) >= Version.fromString("8.8.0-alpha08"))
with(testProject("misc/newAgpResources", defaultTestEnvironment)) {
gradle(":appModule:assembleDebug").checks {
check.logContains("Configure compose resources with KotlinMultiplatformAndroidComponentsExtension")
val resourcesFiles = sequenceOf(
"assets/composeResources/newagpresources.appmodule.generated.resources/values/strings.commonMain.cvr",
"assets/composeResources/newagpresources.featuremodule.generated.resources/values/strings.commonMain.cvr"
)
val apk = file("appModule/build/outputs/apk/debug/appModule-debug.apk")
//isAndroid = false, because the new AGP has an issue with duplicate resources for now
checkResourcesZip(apk, resourcesFiles, false)
}
}
}
@Test
fun testDisableMultimoduleResourcesWithNewKotlin() {
with(testProject("misc/kmpResourcePublication")) {

2
gradle-plugins/compose/src/test/test-projects/application/newAndroidTarget/settings.gradle

@ -3,7 +3,7 @@ pluginManagement {
id 'org.jetbrains.kotlin.multiplatform' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.kotlin.plugin.compose' version 'KOTLIN_VERSION_PLACEHOLDER'
id 'org.jetbrains.compose' version 'COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER'
id 'com.android.kotlin.multiplatform.library' version '8.2.0-alpha13'
id 'com.android.kotlin.multiplatform.library' version 'AGP_VERSION_PLACEHOLDER'
}
repositories {
mavenLocal()

37
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/build.gradle.kts

@ -0,0 +1,37 @@
plugins {
id("org.jetbrains.compose")
kotlin("multiplatform")
kotlin("plugin.compose")
id("com.android.application")
}
kotlin {
jvmToolchain(11)
androidTarget()
jvm()
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.material3)
implementation(compose.components.resources)
implementation(project(":featureModule"))
}
jvmMain.dependencies {
implementation(compose.desktop.currentOs)
}
}
}
android {
namespace = "me.sample.app"
compileSdk = 35
defaultConfig {
applicationId = "org.example.project"
minSdk = 24
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
}

4
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/src/androidMain/AndroidManifest.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application/>
</manifest>

3
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/src/commonMain/composeResources/values/strings.xml

@ -0,0 +1,3 @@
<resources>
<string name="str_1">App text str_1</string>
</resources>

17
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/appModule/src/commonMain/kotlin/me/sample/app/App.kt

@ -0,0 +1,17 @@
package me.sample.app
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import newagpresources.appmodule.generated.resources.*
import org.jetbrains.compose.resources.stringResource
@Composable
fun App() {
Column {
val txt = "text: "
Text(txt + stringResource(Res.string.str_1))
MyFeatureText(txt = txt)
}
}

7
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/build.gradle.kts

@ -0,0 +1,7 @@
plugins {
id("org.jetbrains.compose").apply(false)
kotlin("multiplatform").apply(false)
kotlin("plugin.compose").apply(false)
id("com.android.kotlin.multiplatform.library").apply(false)
id("com.android.application").apply(false)
}

26
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/featureModule/build.gradle.kts

@ -0,0 +1,26 @@
plugins {
id("org.jetbrains.compose")
kotlin("multiplatform")
kotlin("plugin.compose")
id("com.android.kotlin.multiplatform.library")
}
kotlin {
jvmToolchain(11)
jvm()
androidLibrary {
experimentalProperties["android.experimental.kmp.enableAndroidResources"] = true
namespace = "me.sample.feature"
compileSdk = 35
minSdk = 24
}
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.material3)
implementation(compose.components.resources)
}
}
}

3
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/featureModule/src/commonMain/composeResources/values/strings.xml

@ -0,0 +1,3 @@
<resources>
<string name="str_1">Feature text str_1</string>
</resources>

12
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/featureModule/src/commonMain/kotlin/me/sample/app/Feature.kt

@ -0,0 +1,12 @@
package me.sample.app
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import org.jetbrains.compose.resources.stringResource
import newagpresources.featuremodule.generated.resources.*
@Composable
fun MyFeatureText(modifier: Modifier = Modifier, txt: String) {
Text(txt + stringResource(Res.string.str_1), modifier)
}

3
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/gradle.properties

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
kotlin.code.style=official
android.useAndroidX=true

29
gradle-plugins/compose/src/test/test-projects/misc/newAgpResources/settings.gradle.kts

@ -0,0 +1,29 @@
rootProject.name = "newAgpResources"
include(":featureModule")
include(":appModule")
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/")
}
plugins {
id("org.jetbrains.kotlin.multiplatform").version("KOTLIN_VERSION_PLACEHOLDER")
id("org.jetbrains.kotlin.plugin.compose").version("KOTLIN_VERSION_PLACEHOLDER")
id("org.jetbrains.compose").version("COMPOSE_GRADLE_PLUGIN_VERSION_PLACEHOLDER")
id("com.android.kotlin.multiplatform.library").version("AGP_VERSION_PLACEHOLDER")
id("com.android.application").version("AGP_VERSION_PLACEHOLDER")
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev/")
mavenLocal()
}
}

4
gradle-plugins/gradle.properties

@ -15,8 +15,8 @@ compose.tests.kotlin.version=2.0.0
# Don't forget to edit versions in .github/workflows/gradle-plugin.yml as well
# and Publish.Subtasks.buildTypes.gradle.GradlePluginTestKt#gradleVersions in the TC config
# minimal and current gradle version
compose.tests.gradle.versions=7.4, 8.8
compose.tests.agp.versions=8.1.0, 8.5.0
compose.tests.gradle.versions=7.4, 8.10.2
compose.tests.agp.versions=8.1.0, 8.5.0, 8.8.0-alpha08
# A version of Gradle plugin, that will be published,
# unless overridden by COMPOSE_GRADLE_PLUGIN_VERSION env var.

2
gradle-plugins/gradle/libs.versions.toml

@ -2,7 +2,7 @@
kotlin = "2.0.0"
gradle-download-plugin = "5.5.0"
kotlin-poet = "1.16.0"
plugin-android = "7.3.0"
plugin-android = "8.8.0-alpha08"
shadow-jar = "8.1.1"
publish-plugin = "1.2.1"

Loading…
Cancel
Save