Add APIs to resolve multiplatform resources in variant publications

^KT-65540
This commit is contained in:
Timofey Solonin
2024-02-19 02:31:43 +01:00
committed by Space Team
parent 4c8febf10d
commit 2fda16f526
6 changed files with 240 additions and 4 deletions
@@ -912,8 +912,10 @@ public abstract interface class org/jetbrains/kotlin/gradle/plugin/mpp/resources
public static final field Companion Lorg/jetbrains/kotlin/gradle/plugin/mpp/resources/KotlinTargetResourcesPublication$Companion;
public static final field EXTENSION_NAME Ljava/lang/String;
public abstract fun canPublishResources (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;)Z
public abstract fun canResolveResources (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;)Z
public abstract fun publishInAndroidAssets (Lorg/jetbrains/kotlin/gradle/plugin/mpp/KotlinAndroidTarget;Lkotlin/jvm/functions/Function1;Lorg/gradle/api/provider/Provider;)V
public abstract fun publishResourcesAsKotlinComponent (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;Lkotlin/jvm/functions/Function1;Lorg/gradle/api/provider/Provider;)V
public abstract fun resolveResources (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTarget;)Lorg/gradle/api/provider/Provider;
}
public abstract class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier {
@@ -34,6 +34,10 @@ interface KotlinTargetResourcesPublication {
resourcePathForSourceSet: (KotlinSourceSet) -> (ResourceRoot),
relativeResourcePlacement: Provider<File>,
)
fun canResolveResources(target: KotlinTarget): Boolean
fun resolveResources(target: KotlinTarget): Provider<File>
companion object {
const val EXTENSION_NAME = "multiplatformResourcesPublication"
@@ -6,19 +6,21 @@
package org.jetbrains.kotlin.gradle.plugin.mpp.resources
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.kotlinPropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
import org.jetbrains.kotlin.gradle.plugin.launchInStage
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.publication.KotlinAndroidTargetResourcesPublication
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.resolve.AggregateResourcesTask
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.resolve.KotlinTargetResourcesResolutionStrategy
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.resolve.ResolveResourcesFromDependenciesTask
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget
import org.jetbrains.kotlin.gradle.tasks.locateTask
@@ -42,6 +44,11 @@ internal abstract class KotlinTargetResourcesPublicationImpl @Inject constructor
KotlinAndroidTarget::class,
)
private val targetsThatSupportResolution = listOf(
KotlinJsIrTarget::class,
KotlinNativeTarget::class,
)
private val targetToResourcesMap: MutableMap<KotlinTarget, TargetResources> = mutableMapOf()
private val androidTargetAssetsMap: MutableMap<KotlinAndroidTarget, TargetResources> = mutableMapOf()
@@ -117,6 +124,86 @@ internal abstract class KotlinTargetResourcesPublicationImpl @Inject constructor
}
}
override fun canResolveResources(target: KotlinTarget): Boolean {
return targetsThatSupportResolution.any { it.isInstance(target) }
}
override fun resolveResources(target: KotlinTarget): Provider<File> {
if (!canResolveResources(target)) {
target.project.reportDiagnostic(KotlinToolingDiagnostics.ResourceMayNotBeResolvedForTarget(target.name))
}
val aggregateResourcesTaskName = target.disambiguateName("AggregateResources")
project.locateTask<AggregateResourcesTask>(aggregateResourcesTaskName)?.let {
return it.flatMap { it.outputDirectory.asFile }
}
val resolveResourcesFromDependenciesTask = project.registerTask<ResolveResourcesFromDependenciesTask>(
target.disambiguateName("ResolveResourcesFromDependencies")
)
val aggregateResourcesTask = project.registerTask<AggregateResourcesTask>(aggregateResourcesTaskName) { aggregate ->
aggregate.resourcesFromDependenciesDirectory.set(resolveResourcesFromDependenciesTask.flatMap { it.outputDirectory })
aggregate.outputDirectory.set(
project.layout.buildDirectory.dir("$MULTIPLATFORM_RESOURCES_DIRECTORY/aggregated-resources/${target.targetName}")
)
}
project.launchInStage(KotlinPluginLifecycle.Stage.AfterFinaliseCompilations) {
val mainCompilation = target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
resolveResourcesFromDependencies(
compilation = mainCompilation,
resolveResourcesFromDependenciesTask = resolveResourcesFromDependenciesTask,
targetName = target.targetName,
)
resolveResourcesFromSelf(
compilation = mainCompilation,
target = target,
aggregateResourcesTask = aggregateResourcesTask,
)
}
return aggregateResourcesTask.flatMap { it.outputDirectory.asFile }
}
private fun resolveResourcesFromDependencies(
compilation: KotlinCompilation<*>,
resolveResourcesFromDependenciesTask: TaskProvider<ResolveResourcesFromDependenciesTask>,
targetName: String,
) {
resolveResourcesFromDependenciesTask.configure {
it.filterResourcesByExtension.set(
project.kotlinPropertiesProvider
.mppFilterResourcesByExtension
.map { explicitlyEnabled ->
// Always filter resources configuration because it resolves klibs for dependency graph inheritance
explicitlyEnabled || project.kotlinPropertiesProvider.mppResourcesResolutionStrategy == KotlinTargetResourcesResolutionStrategy.ResourcesConfiguration
}
)
it.archivesFromDependencies.from(
project.kotlinPropertiesProvider.mppResourcesResolutionStrategy.resourceArchives(compilation)
)
it.outputDirectory.set(
project.layout.buildDirectory.dir("$MULTIPLATFORM_RESOURCES_DIRECTORY/resources-from-dependencies/${targetName}")
)
}
}
private fun resolveResourcesFromSelf(
compilation: KotlinCompilation<*>,
target: KotlinTarget,
aggregateResourcesTask: TaskProvider<AggregateResourcesTask>,
) {
subscribeOnPublishResources(target) { resources ->
val copyResourcesTask = compilation.assembleHierarchicalResources(
target.disambiguateName("ResolveSelfResources"),
resources,
)
aggregateResourcesTask.configure { aggregate ->
aggregate.resourcesFromSelfDirectory.set(copyResourcesTask)
}
}
}
internal companion object {
const val MULTIPLATFORM_RESOURCES_DIRECTORY = "kotlin-multiplatform-resources"
const val RESOURCES_CLASSIFIER = "kotlin_resources"
@@ -0,0 +1,47 @@
/*
* 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.plugin.mpp.resources.resolve
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.tasks.*
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.incremental.deleteDirectoryContents
import javax.inject.Inject
@DisableCachingByDefault
internal abstract class AggregateResourcesTask : DefaultTask() {
@get:Inject
abstract val fileSystem: FileSystemOperations
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputDirectory
abstract val resourcesFromDependenciesDirectory: DirectoryProperty
@get:Optional
@get:PathSensitive(PathSensitivity.RELATIVE)
@get:InputDirectory
abstract val resourcesFromSelfDirectory: DirectoryProperty
@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty
@TaskAction
fun copyResources() {
outputDirectory.get().asFile.deleteDirectoryContents()
fileSystem.copy { copy ->
resourcesFromDependenciesDirectory.orNull?.let { copy.from(it) }
resourcesFromSelfDirectory.orNull?.let { copy.from(it) }
copy.into(outputDirectory)
copy.duplicatesStrategy = DuplicatesStrategy.FAIL
}
}
}
@@ -0,0 +1,51 @@
/*
* 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.plugin.mpp.resources.resolve
import org.gradle.api.DefaultTask
import org.gradle.api.file.*
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.work.DisableCachingByDefault
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublicationImpl
import org.jetbrains.kotlin.incremental.deleteDirectoryContents
import javax.inject.Inject
@DisableCachingByDefault
internal abstract class ResolveResourcesFromDependenciesTask : DefaultTask() {
@get:Inject
abstract val fileSystem: FileSystemOperations
@get:Inject
abstract val archiveOperations: ArchiveOperations
@get:Input
abstract val filterResourcesByExtension: Property<Boolean>
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFiles
abstract val archivesFromDependencies: ConfigurableFileCollection
@get:OutputDirectory
abstract val outputDirectory: DirectoryProperty
@TaskAction
fun copyResources() {
outputDirectory.get().asFile.deleteDirectoryContents()
fileSystem.copy { copy ->
archivesFromDependencies
.filter { it.isFile }
.filter { if (filterResourcesByExtension.get()) it.name.endsWith(KotlinTargetResourcesPublicationImpl.RESOURCES_ZIP_EXTENSION) else true }
.forEach {
copy.from(archiveOperations.zipTree(it))
}
copy.into(outputDirectory)
copy.duplicatesStrategy = DuplicatesStrategy.FAIL
}
}
}
@@ -3,6 +3,8 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:OptIn(ExperimentalWasmDsl::class)
package org.jetbrains.kotlin.gradle.unitTests
import com.android.build.gradle.LibraryExtension
@@ -15,10 +17,10 @@ import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnosticFactory
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.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.gradle.util.assertContainsDiagnostic
import org.jetbrains.kotlin.gradle.util.assertNoDiagnostics
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
import org.jetbrains.kotlin.gradle.util.kotlin
import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
@@ -119,6 +121,49 @@ class KotlinTargetResourcesPublicationImplTests {
)
}
@Test
fun `test targets that can publish resources`() {
buildProjectWithMPP {
plugins.apply("com.android.library")
enableMppResourcesPublication(true)
kotlin {
listOf(
androidTarget(),
jvm(),
wasmJs(),
wasmWasi(),
linuxArm64(),
iosArm64(),
).forEach { target ->
assert(
resourcesPublicationExtension!!.canPublishResources(target),
{ target }
)
}
}
}
}
@Test
fun `test targets that can resolve resources`() {
buildProjectWithMPP {
enableMppResourcesPublication(true)
kotlin {
listOf(
wasmJs(),
wasmWasi(),
linuxArm64(),
iosArm64(),
).forEach { target ->
assert(
resourcesPublicationExtension!!.canResolveResources(target),
{ target }
)
}
}
}
}
private fun testCallbacksAfterApiCall(
callback: ((Unit) -> Unit) -> Unit,
apiCall: (Unit) -> Unit,