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.compilationImpl.*
|
||||
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.RegisterMultiplatformResourcesPublicationExtensionAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinMultiplatformSourceSetSetupAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.LanguageSettingsSetupAction
|
||||
import org.jetbrains.kotlin.gradle.plugin.statistics.MultiplatformBuildStatsReportSetupAction
|
||||
@@ -84,6 +85,7 @@ internal fun Project.registerKotlinPluginExtensions() {
|
||||
register(project, SetupConsistentMetadataDependenciesResolution)
|
||||
register(project, RegisterMultiplatformResourcesPublicationExtensionAction)
|
||||
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