Publish multiplatform resources and assets for Android target
^KT-65540
This commit is contained in:
committed by
Space Team
parent
43a4fd5ddf
commit
a2989a3711
+189
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.gradle.plugin.mpp.resources.publication
|
||||||
|
|
||||||
|
import com.android.build.api.variant.AndroidComponentsExtension
|
||||||
|
import com.android.build.api.variant.Variant
|
||||||
|
import org.gradle.api.DefaultTask
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.file.DirectoryProperty
|
||||||
|
import org.gradle.api.file.FileSystemOperations
|
||||||
|
import org.gradle.api.tasks.*
|
||||||
|
import org.gradle.work.DisableCachingByDefault
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.*
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.AndroidGradlePluginVersion
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupAction
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.launch
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublicationImpl
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.assembleHierarchicalResources
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.resourcesPublicationExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.sources.android.androidSourceSetInfo
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.registerTask
|
||||||
|
import org.jetbrains.kotlin.gradle.utils.androidExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.utils.androidPluginIds
|
||||||
|
import org.jetbrains.kotlin.gradle.utils.findByType
|
||||||
|
import org.jetbrains.kotlin.gradle.utils.getOrPut
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private val Project.kotlinMultiplatformAndroidResourcesPublication: KotlinAndroidTargetResourcesPublication
|
||||||
|
get() {
|
||||||
|
return extraProperties.getOrPut("kotlinMultiplatformAndroidResourcesPublication") {
|
||||||
|
KotlinAndroidTargetResourcesPublication()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val SetUpMultiplatformAndroidAssetsAndResourcesPublicationAction = KotlinProjectSetupAction {
|
||||||
|
if (!kotlinPropertiesProvider.mppResourcesPublication) return@KotlinProjectSetupAction
|
||||||
|
|
||||||
|
project.launchInStage(KotlinPluginLifecycle.Stage.AfterFinaliseCompilations) {
|
||||||
|
multiplatformExtension.awaitTargets()
|
||||||
|
.filterIsInstance<KotlinAndroidTarget>()
|
||||||
|
.forEach { target ->
|
||||||
|
target.compilations.forEach { compilation ->
|
||||||
|
target.setUpMultiplatformResourcesAndAssets(compilation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isConfigured = false
|
||||||
|
androidPluginIds.forEach { pluginId ->
|
||||||
|
pluginManager.withPlugin(pluginId) {
|
||||||
|
if (isConfigured) {
|
||||||
|
return@withPlugin
|
||||||
|
}
|
||||||
|
isConfigured = true
|
||||||
|
|
||||||
|
// Bundle assets and resources only if AGP is 7.3.0+ due to "addGeneratedSourceDirectory" API availability. And the resources
|
||||||
|
// API is only supported in CMP since 7.3.1
|
||||||
|
if (AndroidGradlePluginVersion.current >= KotlinAndroidTargetResourcesPublication.MIN_AGP_VERSION) {
|
||||||
|
project.extensions.findByType<AndroidComponentsExtension<*, *, *>>()?.onVariants { newAgpVariant ->
|
||||||
|
kotlinMultiplatformAndroidResourcesPublication.setUpCopyAssetsTasksForNewAgpVariants(
|
||||||
|
project,
|
||||||
|
newAgpVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class KotlinAndroidTargetResourcesPublication {
|
||||||
|
|
||||||
|
@DisableCachingByDefault
|
||||||
|
internal abstract class AssetsCopyForAGPTask : DefaultTask() {
|
||||||
|
@get:Inject
|
||||||
|
abstract val fileSystem: FileSystemOperations
|
||||||
|
|
||||||
|
@get:SkipWhenEmpty
|
||||||
|
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||||
|
@get:InputDirectory
|
||||||
|
abstract val inputDirectory: DirectoryProperty
|
||||||
|
|
||||||
|
@get:OutputDirectory
|
||||||
|
abstract val outputDirectory: DirectoryProperty
|
||||||
|
|
||||||
|
@TaskAction
|
||||||
|
fun copy() {
|
||||||
|
fileSystem.copy {
|
||||||
|
it.from(inputDirectory)
|
||||||
|
it.into(outputDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val copyTaskFromVariantName: MutableMap<String, TaskProvider<AssetsCopyForAGPTask>> = mutableMapOf()
|
||||||
|
private val subscribers: MutableMap<String, MutableList<(TaskProvider<AssetsCopyForAGPTask>) -> (Unit)>> = mutableMapOf()
|
||||||
|
|
||||||
|
internal fun setUpCopyAssetsTasksForNewAgpVariants(
|
||||||
|
project: Project,
|
||||||
|
newAgpVariant: Variant,
|
||||||
|
) {
|
||||||
|
val variantName = newAgpVariant.name
|
||||||
|
if (copyTaskFromVariantName[variantName] != null) {
|
||||||
|
error("Setting up multiple copy assets tasks for variant $variantName")
|
||||||
|
}
|
||||||
|
// Prepare these copy tasks in advance because onVariants API has a specific lifecycle requirement and only set up the input for
|
||||||
|
// this task if assets publication is requested
|
||||||
|
val copyTask = project.registerTask<AssetsCopyForAGPTask>("${variantName}AssetsCopyForAGP") {
|
||||||
|
// Specify output just in case
|
||||||
|
it.outputDirectory.convention(
|
||||||
|
project.layout.buildDirectory.dir(
|
||||||
|
"${KotlinTargetResourcesPublicationImpl.MULTIPLATFORM_RESOURCES_DIRECTORY}/assemble-hierarchically/${variantName}-tmp-for-agp"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
newAgpVariant.sources.assets?.addGeneratedSourceDirectory(
|
||||||
|
taskProvider = copyTask,
|
||||||
|
wiredWith = { task -> task.outputDirectory }
|
||||||
|
)
|
||||||
|
copyTaskFromVariantName[variantName] = copyTask
|
||||||
|
subscribers[variantName].orEmpty().forEach { notify ->
|
||||||
|
notify(copyTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun subscribeOnCopyAssetsTaskForVariant(
|
||||||
|
variantName: String,
|
||||||
|
notify: (TaskProvider<AssetsCopyForAGPTask>) -> (Unit),
|
||||||
|
) {
|
||||||
|
// Associate between BaseVariant and Variant by name
|
||||||
|
copyTaskFromVariantName[variantName]?.let(notify)
|
||||||
|
subscribers.getOrPut(variantName, { mutableListOf() }).add(notify)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MIN_AGP_VERSION = "7.3.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun KotlinAndroidTarget.setUpMultiplatformResourcesAndAssets(
|
||||||
|
compilation: KotlinJvmAndroidCompilation,
|
||||||
|
) {
|
||||||
|
if (project.multiplatformExtensionOrNull == null) return
|
||||||
|
|
||||||
|
val legacyAgpVariant = compilation.androidVariant
|
||||||
|
val legacyAgpVariantName = getVariantName(legacyAgpVariant)
|
||||||
|
project.multiplatformExtension.resourcesPublicationExtension?.subscribeOnPublishResources(this) { resources ->
|
||||||
|
val copyResourcesTask = compilation.assembleHierarchicalResources(
|
||||||
|
disambiguateName("${legacyAgpVariantName}Resources"),
|
||||||
|
resources,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (AndroidGradlePluginVersion.current < "8.0.0") {
|
||||||
|
// AGP [7.3-8) requires explicit dependsOn
|
||||||
|
legacyAgpVariant.processJavaResourcesProvider.configure {
|
||||||
|
it.dependsOn(copyResourcesTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.androidExtension.sourceSets.getByName(
|
||||||
|
compilation.defaultSourceSet.androidSourceSetInfo.androidSourceSetName
|
||||||
|
).resources.srcDir(copyResourcesTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
project.multiplatformExtension.resourcesPublicationExtension?.subscribeOnAndroidPublishAssets(this) { assets ->
|
||||||
|
project.kotlinMultiplatformAndroidResourcesPublication.subscribeOnCopyAssetsTaskForVariant(legacyAgpVariantName) { copyTaskForAgp ->
|
||||||
|
val copyAssetsTask = compilation.assembleHierarchicalResources(
|
||||||
|
disambiguateName("${legacyAgpVariantName}Assets"),
|
||||||
|
assets,
|
||||||
|
)
|
||||||
|
copyTaskForAgp.configure {
|
||||||
|
it.inputDirectory.set(copyAssetsTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -20,8 +20,9 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AddBuildListenerForXCodeSetu
|
|||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionSetupAction
|
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XcodeVersionSetupAction
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.*
|
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.*
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.internal.DeprecatedMppGradlePropertiesMigrationSetupAction
|
import org.jetbrains.kotlin.gradle.plugin.mpp.internal.DeprecatedMppGradlePropertiesMigrationSetupAction
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.RegisterMultiplatformResourcesPublicationExtensionAction
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.publication.SetUpMultiplatformAndroidAssetsAndResourcesPublicationAction
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.publication.SetUpMultiplatformJvmResourcesPublicationAction
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.publication.SetUpMultiplatformJvmResourcesPublicationAction
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.RegisterMultiplatformResourcesPublicationExtensionAction
|
||||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinMultiplatformSourceSetSetupAction
|
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinMultiplatformSourceSetSetupAction
|
||||||
import org.jetbrains.kotlin.gradle.plugin.sources.LanguageSettingsSetupAction
|
import org.jetbrains.kotlin.gradle.plugin.sources.LanguageSettingsSetupAction
|
||||||
import org.jetbrains.kotlin.gradle.plugin.statistics.MultiplatformBuildStatsReportSetupAction
|
import org.jetbrains.kotlin.gradle.plugin.statistics.MultiplatformBuildStatsReportSetupAction
|
||||||
@@ -84,6 +85,7 @@ internal fun Project.registerKotlinPluginExtensions() {
|
|||||||
register(project, SetupConsistentMetadataDependenciesResolution)
|
register(project, SetupConsistentMetadataDependenciesResolution)
|
||||||
register(project, RegisterMultiplatformResourcesPublicationExtensionAction)
|
register(project, RegisterMultiplatformResourcesPublicationExtensionAction)
|
||||||
register(project, SetUpMultiplatformJvmResourcesPublicationAction)
|
register(project, SetUpMultiplatformJvmResourcesPublicationAction)
|
||||||
|
register(project, SetUpMultiplatformAndroidAssetsAndResourcesPublicationAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+159
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.gradle.unitTests
|
||||||
|
|
||||||
|
import com.android.build.gradle.LibraryExtension
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.api.internal.project.ProjectInternal
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.resourcesPublicationExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
|
||||||
|
import org.jetbrains.kotlin.gradle.util.kotlin
|
||||||
|
import org.jetbrains.kotlin.gradle.utils.androidExtension
|
||||||
|
import org.junit.Test
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KotlinAndroidTargetResourcesPublicationTests {
|
||||||
|
|
||||||
|
private val Project.expectedReleaseResourcePath
|
||||||
|
get() = layout.buildDirectory.dir(
|
||||||
|
"kotlin-multiplatform-resources/assemble-hierarchically/androidReleaseResources"
|
||||||
|
).get().asFile
|
||||||
|
|
||||||
|
private val Project.expectedDebugResourcePath
|
||||||
|
get() = layout.buildDirectory.dir(
|
||||||
|
"kotlin-multiplatform-resources/assemble-hierarchically/androidDebugResources"
|
||||||
|
).get().asFile
|
||||||
|
|
||||||
|
private val Project.expectedDemoReleaseResourcePath
|
||||||
|
get() = layout.buildDirectory.dir(
|
||||||
|
"kotlin-multiplatform-resources/assemble-hierarchically/androidDemoReleaseResources"
|
||||||
|
).get().asFile
|
||||||
|
|
||||||
|
private val Project.expectedFullReleaseResourcePath
|
||||||
|
get() = layout.buildDirectory.dir(
|
||||||
|
"kotlin-multiplatform-resources/assemble-hierarchically/androidFullReleaseResources"
|
||||||
|
).get().asFile
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test publishing android resources - reflects in android resource directories`() {
|
||||||
|
val project = mppProjectWithAndroidTarget()
|
||||||
|
|
||||||
|
project.evaluate()
|
||||||
|
project.publishFakeResources(project.multiplatformExtension.androidTarget())
|
||||||
|
project.publishFakeAssets(project.multiplatformExtension.androidTarget())
|
||||||
|
|
||||||
|
val androidVariantSourceSets = project.multiplatformExtension.androidTarget()
|
||||||
|
.compilations.getByName("release")
|
||||||
|
.androidVariant.sourceSets
|
||||||
|
val actualResourcesDirectories = androidVariantSourceSets.flatMapTo(linkedSetOf()) { it.resourcesDirectories }
|
||||||
|
|
||||||
|
assert(
|
||||||
|
actualResourcesDirectories.contains(project.expectedReleaseResourcePath)
|
||||||
|
) { actualResourcesDirectories.dumpPaths() }
|
||||||
|
assert(
|
||||||
|
!actualResourcesDirectories.contains(project.expectedDebugResourcePath)
|
||||||
|
) { actualResourcesDirectories.dumpPaths() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test publishing android resources with flavors - reflects in android resource directories`() {
|
||||||
|
val project = mppProjectWithAndroidTarget {
|
||||||
|
val dimension = "version"
|
||||||
|
flavorDimensions += dimension
|
||||||
|
productFlavors {
|
||||||
|
with(create("demo")) { this.dimension = dimension }
|
||||||
|
with(create("full")) { this.dimension = dimension }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.evaluate()
|
||||||
|
project.publishFakeResources(project.multiplatformExtension.androidTarget())
|
||||||
|
|
||||||
|
val androidVariantSourceSets = project.multiplatformExtension.androidTarget()
|
||||||
|
.compilations.getByName("demoRelease")
|
||||||
|
.androidVariant.sourceSets
|
||||||
|
val actualResourcesDirectories = androidVariantSourceSets.flatMapTo(linkedSetOf()) {
|
||||||
|
it.resourcesDirectories
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(
|
||||||
|
actualResourcesDirectories.contains(project.expectedDemoReleaseResourcePath)
|
||||||
|
) { actualResourcesDirectories.dumpPaths() }
|
||||||
|
assert(
|
||||||
|
!actualResourcesDirectories.contains(project.expectedFullReleaseResourcePath)
|
||||||
|
) { actualResourcesDirectories.dumpPaths() }
|
||||||
|
assert(
|
||||||
|
!actualResourcesDirectories.contains(project.expectedReleaseResourcePath)
|
||||||
|
) { actualResourcesDirectories.dumpPaths() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test publishing android resources - doesn't show up in resources - when there is no publication`() {
|
||||||
|
val project = mppProjectWithAndroidTarget()
|
||||||
|
|
||||||
|
project.evaluate()
|
||||||
|
|
||||||
|
val actualSourcesDirectories = project.multiplatformExtension.androidTarget()
|
||||||
|
.compilations.getByName("release")
|
||||||
|
.androidVariant.sourceSets.flatMapTo(linkedSetOf()) { it.resourcesDirectories }
|
||||||
|
|
||||||
|
assert(
|
||||||
|
!actualSourcesDirectories.contains(project.expectedReleaseResourcePath)
|
||||||
|
) { actualSourcesDirectories.dumpPaths() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mppProjectWithAndroidTarget(
|
||||||
|
configure: LibraryExtension.(Unit) -> (Unit) = { },
|
||||||
|
): ProjectInternal {
|
||||||
|
val project = buildProjectWithMPP {
|
||||||
|
plugins.apply("com.android.library")
|
||||||
|
kotlin {
|
||||||
|
androidTarget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val libraryExt = (project.extensions.getByName("android") as LibraryExtension)
|
||||||
|
libraryExt.compileSdk = 30
|
||||||
|
libraryExt.configure(Unit)
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Project.publishFakeResources(target: KotlinTarget) {
|
||||||
|
project.multiplatformExtension.resourcesPublicationExtension?.publishResourcesAsKotlinComponent(
|
||||||
|
target,
|
||||||
|
resourcePathForSourceSet = {
|
||||||
|
KotlinTargetResourcesPublication.ResourceRoot(
|
||||||
|
project.provider { File(it.name) },
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
relativeResourcePlacement = project.provider { File("test") },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Project.publishFakeAssets(target: KotlinAndroidTarget) {
|
||||||
|
project.multiplatformExtension.resourcesPublicationExtension?.publishInAndroidAssets(
|
||||||
|
target,
|
||||||
|
resourcePathForSourceSet = {
|
||||||
|
KotlinTargetResourcesPublication.ResourceRoot(
|
||||||
|
project.provider { File(it.name) },
|
||||||
|
emptyList(),
|
||||||
|
emptyList(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
relativeResourcePlacement = project.provider { File("test") },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Collection<File>.dumpPaths(): String {
|
||||||
|
return "Actual paths:" + if (isEmpty()) " <Empty>" else joinToString("") { "\n${it}" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user