Add a task to hierarchically assemble multiplatform resources
^KT-65540
This commit is contained in:
committed by
Space Team
parent
74628c0394
commit
5ddc7b47ef
+11
@@ -765,6 +765,17 @@ object KotlinToolingDiagnostics {
|
||||
)
|
||||
}
|
||||
|
||||
private val resourcesBugReportRequest get() = "This is likely a bug in Kotlin Gradle Plugin configuration. Please report this issue to https://kotl.in/issue."
|
||||
object ResourcePublishedMoreThanOncePerTarget : ToolingDiagnosticFactory(ERROR) {
|
||||
operator fun invoke(targetName: String) = build(
|
||||
"""
|
||||
Only one resources publication per target $targetName is allowed.
|
||||
|
||||
$resourcesBugReportRequest
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
object DependencyDoesNotPhysicallyExist : ToolingDiagnosticFactory(WARNING) {
|
||||
operator fun invoke(dependency: File) = build(
|
||||
"""
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
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.file.FileTree
|
||||
import org.gradle.api.file.ProjectLayout
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.Provider
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.work.DisableCachingByDefault
|
||||
import org.jetbrains.kotlin.incremental.deleteDirectoryContents
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
@DisableCachingByDefault
|
||||
internal abstract class AssembleHierarchicalResourcesTask : DefaultTask() {
|
||||
|
||||
internal class Resource(
|
||||
@get:Internal
|
||||
val resourcesBaseDirectory: Provider<File>,
|
||||
|
||||
includes: List<String>,
|
||||
excludes: List<String>,
|
||||
layout: ProjectLayout,
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
val resourcesFileTree: Provider<FileTree> = layout.dir(resourcesBaseDirectory).map { baseDirectory ->
|
||||
baseDirectory.asFileTree.matching { patternFilter ->
|
||||
includes.forEach { patternFilter.include(it) }
|
||||
excludes.forEach { patternFilter.exclude(it) }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@get:Inject
|
||||
abstract val fileSystem: FileSystemOperations
|
||||
|
||||
@get:Inject
|
||||
abstract val projectLayout: ProjectLayout
|
||||
|
||||
@get:Nested
|
||||
abstract val resourceDirectoriesByLevel: ListProperty<List<Resource>>
|
||||
|
||||
/**
|
||||
* Relative resource placement is part of this task to prepare the output directory as is for consumption by the .zip publication or the
|
||||
* self-resources resolution tasks
|
||||
*/
|
||||
@get:Input
|
||||
abstract val relativeResourcePlacement: Property<File>
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val outputDirectory: DirectoryProperty
|
||||
|
||||
@TaskAction
|
||||
fun action() {
|
||||
val directoriesToCopy = resourcesSourceSetWalk.directoriesToCopy(
|
||||
resourceDirectoriesByLevel.get()
|
||||
).unwrapOrThrow()
|
||||
|
||||
val outputDirectoryFile = outputDirectory.get().asFile
|
||||
outputDirectoryFile.deleteDirectoryContents()
|
||||
|
||||
fileSystem.copy { copy ->
|
||||
directoriesToCopy.forEach { dir ->
|
||||
copy.from(dir)
|
||||
}
|
||||
copy.into(outputDirectoryFile.resolve(relativeResourcePlacement.get()))
|
||||
copy.duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
if (outputDirectoryFile.listFiles()?.isEmpty() != false) {
|
||||
// Output an empty directory for the zip task
|
||||
outputDirectoryFile.resolve(relativeResourcePlacement.get()).mkdirs()
|
||||
}
|
||||
}
|
||||
|
||||
private val resourcesSourceSetWalk = SourceSetWalk(
|
||||
fileSystem = object : FileSystem<Resource> {
|
||||
override fun walk(root: Resource): Sequence<File> = root.resourcesFileTree.get().asSequence()
|
||||
},
|
||||
basePathFromResource = { it.resourcesBaseDirectory.get() },
|
||||
fileTreeToCopyFromResource = { it.resourcesFileTree.get() },
|
||||
)
|
||||
|
||||
internal interface FileSystem<RootEntity> {
|
||||
fun walk(root: RootEntity): Sequence<File>
|
||||
fun exists(file: File): Boolean = file.exists()
|
||||
fun isDirectory(file: File): Boolean = file.isDirectory
|
||||
}
|
||||
|
||||
internal class SourceSetWalk<Resource, FileTreeToCopy>(
|
||||
val fileSystem: FileSystem<Resource>,
|
||||
val basePathFromResource: (Resource) -> (File),
|
||||
val fileTreeToCopyFromResource: (Resource) -> (FileTreeToCopy),
|
||||
) {
|
||||
sealed class Result<T> {
|
||||
data class ToCopy<T>(val value: T) : Result<T>()
|
||||
data class Collision<T>(val left: File, val right: File) : Result<T>()
|
||||
data class NotDirectory<T>(val path: File) : Result<T>()
|
||||
|
||||
fun unwrapOrThrow(): T {
|
||||
when (this) {
|
||||
is Collision -> error("There is a duplicate resource in a source set level:\n${left.canonicalPath}\n${right.canonicalPath}")
|
||||
is NotDirectory -> error("Path $path is not a directory")
|
||||
is ToCopy -> return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun directoriesToCopy(
|
||||
leveledResources: List<List<Resource>>
|
||||
): Result<List<FileTreeToCopy>> {
|
||||
// 1. Is case of a collision between levels, files seen at next levels overwrite files from previous levels. E.g. iosMain/res/foo
|
||||
// resources overwrites commonMain/res/foo
|
||||
val directoriesToCopy = mutableListOf<FileTreeToCopy>()
|
||||
leveledResources.onEach { level ->
|
||||
val relativePathsSeenAtThisLevel: MutableMap<String, File> = mutableMapOf()
|
||||
level.onEach { resource ->
|
||||
when (
|
||||
val result = discoverAndAppendResourceDirectory(
|
||||
resource = resource,
|
||||
relativePathsSeenAtThisLevel = relativePathsSeenAtThisLevel,
|
||||
)
|
||||
) {
|
||||
is Result.Collision -> return Result.Collision(result.left, result.right)
|
||||
is Result.NotDirectory -> return Result.NotDirectory(result.path)
|
||||
is Result.ToCopy -> directoriesToCopy.add(result.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ToCopy(directoriesToCopy)
|
||||
}
|
||||
|
||||
private fun discoverAndAppendResourceDirectory(
|
||||
resource: Resource,
|
||||
relativePathsSeenAtThisLevel: MutableMap<String, File>,
|
||||
): Result<FileTreeToCopy>? {
|
||||
val resourcesDirectory = basePathFromResource(resource)
|
||||
if (!fileSystem.exists(resourcesDirectory)) return null
|
||||
if (!fileSystem.isDirectory(resourcesDirectory)) return Result.NotDirectory(resourcesDirectory)
|
||||
|
||||
val collisions: List<Result.Collision<FileTreeToCopy>> =
|
||||
fileSystem.walk(resource).map<File, Result.Collision<FileTreeToCopy>?>{ child ->
|
||||
if (fileSystem.isDirectory(child)) {
|
||||
return@map null
|
||||
}
|
||||
val relativePath = child.toRelativeString(resourcesDirectory)
|
||||
relativePathsSeenAtThisLevel[relativePath]?.let { alreadySeenFile ->
|
||||
return@map Result.Collision(
|
||||
child,
|
||||
alreadySeenFile,
|
||||
)
|
||||
}
|
||||
relativePathsSeenAtThisLevel[relativePath] = child
|
||||
return@map null
|
||||
}.mapNotNull { it }.toList()
|
||||
|
||||
if (collisions.isNotEmpty()) {
|
||||
return collisions.first()
|
||||
}
|
||||
|
||||
return Result.ToCopy(fileTreeToCopyFromResource(resource))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import org.gradle.api.file.Directory
|
||||
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.diagnostics.KotlinToolingDiagnostics
|
||||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.reportDiagnostic
|
||||
import org.jetbrains.kotlin.gradle.tasks.locateTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.registerTask
|
||||
import org.jetbrains.kotlin.tooling.core.withClosureGroupingByDistance
|
||||
|
||||
internal fun KotlinCompilation<*>.assembleHierarchicalResources(
|
||||
targetNamePrefix: String,
|
||||
resources: KotlinTargetResourcesPublicationImpl.TargetResources,
|
||||
): Provider<Directory> = registerAssembleHierarchicalResourcesTaskProvider(
|
||||
targetNamePrefix,
|
||||
resources,
|
||||
).flatMap { it.outputDirectory }
|
||||
|
||||
internal fun KotlinCompilation<*>.registerAssembleHierarchicalResourcesTaskProvider(
|
||||
targetNamePrefix: String,
|
||||
resources: KotlinTargetResourcesPublicationImpl.TargetResources,
|
||||
): TaskProvider<AssembleHierarchicalResourcesTask> {
|
||||
val taskName = "${targetNamePrefix}CopyHierarchicalMultiplatformResources"
|
||||
val existingTask = project.tasks.locateTask<AssembleHierarchicalResourcesTask>(taskName)
|
||||
if (existingTask != null) {
|
||||
project.reportDiagnostic(KotlinToolingDiagnostics.ResourcePublishedMoreThanOncePerTarget(targetNamePrefix))
|
||||
return existingTask
|
||||
}
|
||||
|
||||
return project.registerTask<AssembleHierarchicalResourcesTask>(taskName) { assembleResources ->
|
||||
project.launchInStage(KotlinPluginLifecycle.Stage.AfterFinaliseRefinesEdges) {
|
||||
val resourceDirectoriesByLevel = splitResourceDirectoriesBySourceSetLevel(
|
||||
resources = resources,
|
||||
rootSourceSets = kotlinSourceSets.sortedBy { it.name },
|
||||
)
|
||||
val outputDirectory = project.layout.buildDirectory.dir(
|
||||
"${KotlinTargetResourcesPublicationImpl.MULTIPLATFORM_RESOURCES_DIRECTORY}/assemble-hierarchically/${targetNamePrefix}"
|
||||
)
|
||||
|
||||
resourceDirectoriesByLevel.forEach { level ->
|
||||
assembleResources.resourceDirectoriesByLevel.add(
|
||||
level.map {
|
||||
AssembleHierarchicalResourcesTask.Resource(
|
||||
it.resourcesBaseDirectory,
|
||||
it.includes,
|
||||
it.excludes,
|
||||
project.layout,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
assembleResources.relativeResourcePlacement.set(resources.relativeResourcePlacement)
|
||||
assembleResources.outputDirectory.set(outputDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitResourceDirectoriesBySourceSetLevel(
|
||||
resources: KotlinTargetResourcesPublicationImpl.TargetResources,
|
||||
rootSourceSets: List<KotlinSourceSet>,
|
||||
): List<List<KotlinTargetResourcesPublication.ResourceRoot>> {
|
||||
return rootSourceSets.withClosureGroupingByDistance { it.dependsOn }.map { sourceSets ->
|
||||
sourceSets.map { sourceSet ->
|
||||
resources.resourcePathForSourceSet(sourceSet)
|
||||
}
|
||||
}.reversed()
|
||||
}
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* 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("UNCHECKED_CAST")
|
||||
|
||||
package org.jetbrains.kotlin.gradle.unitTests
|
||||
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.AssembleHierarchicalResourcesTask
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.popLast
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AssembleHierarchicalResourcesTaskSourceSetWalkTests {
|
||||
|
||||
@Test
|
||||
fun `test simple resource root`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.ToCopy(
|
||||
listOf(
|
||||
absolutePath("commonMain")
|
||||
)
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"foo" to null,
|
||||
)
|
||||
)
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("commonMain")
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test collision on the top level`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.Collision(
|
||||
absolutePath("commonMain2", "res", "collision").toFile(),
|
||||
absolutePath("commonMain", "res", "collision").toFile(),
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
),
|
||||
"commonMain2" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
),
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("commonMain"),
|
||||
absolutePath("commonMain2"),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test collision across levels`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.ToCopy(
|
||||
listOf(
|
||||
absolutePath("iosMain"),
|
||||
absolutePath("commonMain"),
|
||||
)
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
),
|
||||
"iosMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
)
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("iosMain"),
|
||||
),
|
||||
listOf(
|
||||
absolutePath("commonMain"),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test collision only in directories`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.ToCopy(
|
||||
listOf(
|
||||
absolutePath("commonMain"),
|
||||
absolutePath("commonMain2"),
|
||||
)
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"foo" to null,
|
||||
)
|
||||
),
|
||||
"commonMain2" to mapOf(
|
||||
"res" to mapOf(
|
||||
"bar" to null,
|
||||
)
|
||||
),
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("commonMain"),
|
||||
absolutePath("commonMain2"),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test collision at higher level`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.Collision(
|
||||
absolutePath("commonMain2", "res", "collision").toFile(),
|
||||
absolutePath("commonMain", "res", "collision").toFile(),
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
),
|
||||
"commonMain2" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
),
|
||||
"iosMain" to mapOf(
|
||||
"res" to mapOf(
|
||||
"collision" to null,
|
||||
)
|
||||
),
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("iosMain")
|
||||
),
|
||||
listOf(
|
||||
absolutePath("commonMain"),
|
||||
absolutePath("commonMain2"),
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test non-existent directories`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.ToCopy(
|
||||
emptyList()
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("commonMain")
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test empty directories`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.ToCopy(
|
||||
listOf(
|
||||
absolutePath("commonMain")
|
||||
)
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to emptyMap<String, Any>()
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("commonMain")
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test not a directory`() {
|
||||
assertEquals(
|
||||
AssembleHierarchicalResourcesTask.SourceSetWalk.Result.NotDirectory(
|
||||
absolutePath("commonMain").toFile(),
|
||||
),
|
||||
directoriesToCopy(
|
||||
hashMapOf(
|
||||
"commonMain" to null,
|
||||
),
|
||||
listOf(
|
||||
listOf(
|
||||
absolutePath("commonMain")
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun absolutePath(first: String, vararg next: String): Path {
|
||||
return Paths.get("").toAbsolutePath().root.resolve(Paths.get(first, *next))
|
||||
}
|
||||
|
||||
private fun directoriesToCopy(
|
||||
fileSystem: HashMap<String, Any?>,
|
||||
resourcesSplitByLevel: List<List<Path>>,
|
||||
): AssembleHierarchicalResourcesTask.SourceSetWalk.Result<List<Path>> {
|
||||
val fakeFs = buildFakeFileSystem(contents = fileSystem)
|
||||
return AssembleHierarchicalResourcesTask.SourceSetWalk<File, Path>(
|
||||
fileSystem = object : AssembleHierarchicalResourcesTask.FileSystem<File> {
|
||||
override fun walk(root: File): Sequence<File> = fakeFs.walkFileSystemFrom(root.toPath()).map {
|
||||
it.path.toFile()
|
||||
}
|
||||
override fun exists(file: File): Boolean = fakeFs.exists(file.toPath())
|
||||
override fun isDirectory(file: File): Boolean = fakeFs.fileSystemAt(file.toPath()) is FakeFileSystem.FakeDirectory
|
||||
},
|
||||
basePathFromResource = { it },
|
||||
fileTreeToCopyFromResource = { it.toPath() }
|
||||
).directoriesToCopy(
|
||||
resourcesSplitByLevel.map { it.map { it.toFile() } },
|
||||
)
|
||||
}
|
||||
|
||||
fun buildFakeFileSystem(
|
||||
path: Path = Paths.get("").toAbsolutePath().root,
|
||||
contents: Map<String, Any?>
|
||||
): FakeFileSystem {
|
||||
return FakeFileSystem.FakeDirectory(
|
||||
path,
|
||||
contents.mapValues {
|
||||
val value = it.value
|
||||
return@mapValues when (value) {
|
||||
is Map<*, *> -> buildFakeFileSystem(
|
||||
path.resolve(it.key),
|
||||
value as Map<String, Any>,
|
||||
)
|
||||
null -> FakeFileSystem.FakeFile(path.resolve(it.key))
|
||||
else -> error("Unexpected component ${value} in fake fs")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
sealed class FakeFileSystem(val path: Path) {
|
||||
class FakeFile(path: Path) : FakeFileSystem(path)
|
||||
class FakeDirectory(path: Path, val contents: Map<String, FakeFileSystem>) : FakeFileSystem(path)
|
||||
|
||||
fun fileSystemAt(path: Path): FakeFileSystem? {
|
||||
if (this is FakeFile) error("File has no sub filesystem")
|
||||
(this as FakeDirectory)
|
||||
|
||||
val components: MutableList<String> = path.map { it.fileName?.toString().orEmpty() }.reversed().toMutableList()
|
||||
var current = contents
|
||||
var value: FakeFileSystem? = null
|
||||
|
||||
while (components.isNotEmpty()) {
|
||||
value = current[components.popLast()]
|
||||
|
||||
when (value) {
|
||||
is FakeDirectory -> current = value.contents
|
||||
is FakeFile -> if (components.isNotEmpty()) return null
|
||||
null -> return null
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
fun subdirectoryAt(path: Path): FakeDirectory {
|
||||
when (val directory = fileSystemAt(path)) {
|
||||
is FakeDirectory -> return directory
|
||||
is FakeFile -> error("Path $path is a file")
|
||||
null -> error("Subdirectory $path doesn't exist")
|
||||
}
|
||||
}
|
||||
|
||||
fun exists(path: Path): Boolean = fileSystemAt(path) != null
|
||||
|
||||
fun walkFileSystemFrom(path: Path): Sequence<FakeFileSystem> {
|
||||
return sequence {
|
||||
walkFileSystem(subdirectoryAt(path))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SequenceScope<FakeFileSystem>.walkFileSystem(
|
||||
file: FakeFileSystem,
|
||||
) {
|
||||
if (file is FakeFile) return yield(file)
|
||||
(file as FakeDirectory)
|
||||
yield(file)
|
||||
file.contents.values.forEach {
|
||||
walkFileSystem(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 org.gradle.api.Project
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.AssembleHierarchicalResourcesTask
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublication
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.KotlinTargetResourcesPublicationImpl
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.resources.registerAssembleHierarchicalResourcesTaskProvider
|
||||
import org.jetbrains.kotlin.gradle.util.assertContainsDiagnostic
|
||||
import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP
|
||||
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AssembleHierarchicalResourcesTaskTests {
|
||||
|
||||
@Test
|
||||
fun `test copying order - matches default target hierarchy`() {
|
||||
with(buildProjectWithMPP()) {
|
||||
val kotlin = multiplatformExtension
|
||||
val compilation = kotlin.linuxArm64().compilations.getByName("main")
|
||||
|
||||
evaluate()
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
listOf("commonMain"),
|
||||
listOf("nativeMain"),
|
||||
listOf("linuxMain"),
|
||||
listOf("linuxArm64Main"),
|
||||
),
|
||||
resourceDirectoriesCopyingOrder(
|
||||
compilation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test copying order - with additional source sets in platform source set`() {
|
||||
with(buildProjectWithMPP()) {
|
||||
val kotlin = multiplatformExtension
|
||||
val compilation = kotlin.jvm().compilations.getByName("main")
|
||||
|
||||
val a = kotlin.sourceSets.create("a")
|
||||
|
||||
compilation.defaultSourceSet.dependsOn(a)
|
||||
|
||||
evaluate()
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
listOf("a", "commonMain"),
|
||||
listOf("jvmMain"),
|
||||
),
|
||||
resourceDirectoriesCopyingOrder(
|
||||
compilation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test copying order - with source sets more remote than common`() {
|
||||
with(buildProjectWithMPP()) {
|
||||
val kotlin = multiplatformExtension
|
||||
val compilation = kotlin.jvm().compilations.getByName("main")
|
||||
|
||||
val a = kotlin.sourceSets.create("a")
|
||||
val b = kotlin.sourceSets.create("b")
|
||||
val c = kotlin.sourceSets.create("c")
|
||||
val d = kotlin.sourceSets.create("d")
|
||||
val e = kotlin.sourceSets.create("e")
|
||||
|
||||
compilation.defaultSourceSet.dependsOn(a)
|
||||
compilation.defaultSourceSet.dependsOn(b)
|
||||
a.dependsOn(c)
|
||||
b.dependsOn(d)
|
||||
d.dependsOn(e)
|
||||
|
||||
evaluate()
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
listOf("e"),
|
||||
listOf("c", "d"),
|
||||
listOf("a", "b", "commonMain"),
|
||||
listOf("jvmMain"),
|
||||
),
|
||||
resourceDirectoriesCopyingOrder(
|
||||
compilation
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test registering multiple resources assembling tasks - results in a diagnostic`() {
|
||||
with(buildProjectWithMPP()) {
|
||||
val kotlin = multiplatformExtension
|
||||
val compilation = kotlin.jvm().compilations.getByName("main")
|
||||
|
||||
evaluate()
|
||||
|
||||
registerFakeResourcesTask(compilation)
|
||||
registerFakeResourcesTask(compilation)
|
||||
|
||||
assertContainsDiagnostic(KotlinToolingDiagnostics.ResourcePublishedMoreThanOncePerTarget)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.resourceDirectoriesCopyingOrder(
|
||||
compilation: KotlinCompilation<*>,
|
||||
): List<List<String>> {
|
||||
return registerFakeResourcesTask(compilation).get().resourceDirectoriesByLevel.get().map { resourcesLevel ->
|
||||
resourcesLevel.map { resource ->
|
||||
resource.resourcesBaseDirectory.get().name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.registerFakeResourcesTask(compilation: KotlinCompilation<*>): TaskProvider<AssembleHierarchicalResourcesTask> {
|
||||
return compilation.registerAssembleHierarchicalResourcesTaskProvider(
|
||||
"test",
|
||||
resources = KotlinTargetResourcesPublicationImpl.TargetResources(
|
||||
{ ss ->
|
||||
KotlinTargetResourcesPublication.ResourceRoot(
|
||||
provider { File(ss.name) },
|
||||
emptyList(),
|
||||
emptyList(),
|
||||
)
|
||||
},
|
||||
provider { File("stub") }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user