Browse Source
* Simplify resource management for iOS Introduces new a new task 'sync<FRAMEWORK_CLASSIFIER>ComposeIosResources', which collects resources from all source sets, included in iOS targets. With this change: * CocoaPods integration does not require any configuration or calling 'pod install' after changing resources. * Important: existing projects need to remove 'extraSpecAttributes["resources"] = ...' from build scripts, and rerun `./gradlew podInstall` once! * Without CocoaPods, the resource directory should be added to XCode build phases once. Resolves #3073 Resolves #3113 Resolves #3066ivan.matkov/skiko-dsl v1.5.0-dev1104
Alexey Tsvetkov
1 year ago
committed by
GitHub
22 changed files with 554 additions and 109 deletions
@ -1,6 +0,0 @@ |
|||||||
target 'iosApp' do |
|
||||||
use_frameworks! |
|
||||||
platform :ios, '14.1' |
|
||||||
pod 'shared', :path => '../shared' |
|
||||||
project 'iosApp.xcodeproj' |
|
||||||
end |
|
@ -0,0 +1,56 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.internal.resources |
||||||
|
|
||||||
|
import org.gradle.api.provider.Property |
||||||
|
import org.gradle.api.provider.SetProperty |
||||||
|
import org.gradle.api.tasks.Input |
||||||
|
import java.io.ObjectInputStream |
||||||
|
import java.io.ObjectOutputStream |
||||||
|
import java.io.Serializable |
||||||
|
|
||||||
|
internal abstract class IosTargetResources : Serializable { |
||||||
|
@get:Input |
||||||
|
abstract val name: Property<String> |
||||||
|
|
||||||
|
@get:Input |
||||||
|
abstract val konanTarget: Property<String> |
||||||
|
|
||||||
|
@get:Input |
||||||
|
abstract val dirs: SetProperty<String> |
||||||
|
|
||||||
|
@Suppress("unused") // used by Gradle Configuration Cache |
||||||
|
fun readObject(input: ObjectInputStream) { |
||||||
|
name.set(input.readUTF()) |
||||||
|
konanTarget.set(input.readUTF()) |
||||||
|
dirs.set(input.readUTFStrings()) |
||||||
|
} |
||||||
|
|
||||||
|
@Suppress("unused") // used by Gradle Configuration Cache |
||||||
|
fun writeObject(output: ObjectOutputStream) { |
||||||
|
output.writeUTF(name.get()) |
||||||
|
output.writeUTF(konanTarget.get()) |
||||||
|
output.writeUTFStrings(dirs.get()) |
||||||
|
} |
||||||
|
|
||||||
|
private fun ObjectOutputStream.writeUTFStrings(collection: Collection<String>) { |
||||||
|
writeInt(collection.size) |
||||||
|
collection.forEach { writeUTF(it) } |
||||||
|
} |
||||||
|
|
||||||
|
private fun ObjectInputStream.readUTFStrings(): Set<String> { |
||||||
|
val size = readInt() |
||||||
|
return LinkedHashSet<String>(size).apply { |
||||||
|
repeat(size) { |
||||||
|
add(readUTF()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
private const val serialVersionUID: Long = 0 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,178 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.internal.resources |
||||||
|
|
||||||
|
import org.gradle.api.Project |
||||||
|
import org.gradle.api.file.Directory |
||||||
|
import org.gradle.api.provider.Provider |
||||||
|
import org.gradle.api.tasks.TaskContainer |
||||||
|
import org.jetbrains.compose.experimental.uikit.internal.utils.IosGradleProperties |
||||||
|
import org.jetbrains.compose.experimental.uikit.internal.utils.asIosNativeTargetOrNull |
||||||
|
import org.jetbrains.compose.experimental.uikit.internal.utils.cocoapodsExt |
||||||
|
import org.jetbrains.compose.experimental.uikit.internal.utils.withCocoapodsPlugin |
||||||
|
import org.jetbrains.compose.experimental.uikit.tasks.SyncComposeResourcesForIosTask |
||||||
|
import org.jetbrains.compose.internal.utils.joinLowerCamelCase |
||||||
|
import org.jetbrains.compose.internal.utils.new |
||||||
|
import org.jetbrains.compose.internal.utils.registerOrConfigure |
||||||
|
import org.jetbrains.compose.internal.utils.uppercaseFirstChar |
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension |
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework |
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType |
||||||
|
import java.io.File |
||||||
|
|
||||||
|
internal fun Project.configureSyncTask(mppExt: KotlinMultiplatformExtension) { |
||||||
|
if (!IosGradleProperties.syncResources(providers).get()) return |
||||||
|
|
||||||
|
with (SyncIosResourcesContext(project, mppExt)) { |
||||||
|
configureSyncResourcesTasks() |
||||||
|
configureCocoapodsResourcesAttribute() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class SyncIosResourcesContext( |
||||||
|
val project: Project, |
||||||
|
val mppExt: KotlinMultiplatformExtension |
||||||
|
) { |
||||||
|
fun syncDirFor(framework: Framework): Provider<Directory> { |
||||||
|
val providers = framework.project.providers |
||||||
|
return if (framework.isCocoapodsFramework) { |
||||||
|
project.layout.buildDirectory.dir("compose/ios/${framework.baseName}/compose-resources/") |
||||||
|
} else { |
||||||
|
providers.environmentVariable("BUILT_PRODUCTS_DIR") |
||||||
|
.zip(providers.environmentVariable("CONTENTS_FOLDER_PATH")) { builtProductsDir, contentsFolderPath -> |
||||||
|
File(builtProductsDir) |
||||||
|
.resolve(contentsFolderPath) |
||||||
|
.resolve("compose-resources") |
||||||
|
.canonicalPath |
||||||
|
}.flatMap { |
||||||
|
framework.project.objects.directoryProperty().apply { set(File(it)) } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
fun configureEachIosFramework(fn: (Framework) -> Unit) { |
||||||
|
mppExt.targets.all { target -> |
||||||
|
target.asIosNativeTargetOrNull()?.let { iosTarget -> |
||||||
|
iosTarget.binaries.withType(Framework::class.java).configureEach { framework -> |
||||||
|
fn(framework) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private const val RESOURCES_SPEC_ATTR = "resources" |
||||||
|
private fun SyncIosResourcesContext.configureCocoapodsResourcesAttribute() { |
||||||
|
project.withCocoapodsPlugin { |
||||||
|
project.gradle.taskGraph.whenReady { |
||||||
|
val cocoapodsExt = mppExt.cocoapodsExt |
||||||
|
val specAttributes = cocoapodsExt.extraSpecAttributes |
||||||
|
val resourcesSpec = specAttributes[RESOURCES_SPEC_ATTR] |
||||||
|
if (!resourcesSpec.isNullOrBlank()) { |
||||||
|
project.logger.warn("Warning: kotlin.cocoapods.extraSpecAttributes[\"resources\"] is ignored by Compose Multiplatform's resource synchronization for iOS") |
||||||
|
} |
||||||
|
cocoapodsExt.framework { |
||||||
|
val syncDir = syncDirFor(this).get().asFile |
||||||
|
specAttributes[RESOURCES_SPEC_ATTR] = "['${syncDir.relativeTo(project.projectDir).path}']" |
||||||
|
project.tasks.named("podInstall").configure { |
||||||
|
it.doFirst { |
||||||
|
syncDir.mkdirs() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun SyncIosResourcesContext.configureSyncResourcesTasks() { |
||||||
|
val lazyTasksDependencies = LazyTasksDependencyConfigurator(project.tasks) |
||||||
|
configureEachIosFramework { framework -> |
||||||
|
val frameworkClassifier = framework.namePrefix.uppercaseFirstChar() |
||||||
|
val syncResourcesTaskName = "sync${frameworkClassifier}ComposeResourcesForIos" |
||||||
|
val syncTask = framework.project.tasks.registerOrConfigure<SyncComposeResourcesForIosTask>(syncResourcesTaskName) { |
||||||
|
outputDir.set(syncDirFor(framework)) |
||||||
|
iosTargets.add(iosTargetResourcesProvider(framework)) |
||||||
|
} |
||||||
|
with (lazyTasksDependencies) { |
||||||
|
if (framework.isCocoapodsFramework) { |
||||||
|
"syncFramework".lazyDependsOn(syncTask.name) |
||||||
|
} else { |
||||||
|
"embedAndSign${frameworkClassifier}AppleFrameworkForXcode".lazyDependsOn(syncTask.name) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private val Framework.isCocoapodsFramework: Boolean |
||||||
|
get() = name.startsWith("pod") |
||||||
|
|
||||||
|
private val Framework.namePrefix: String |
||||||
|
get() = extractPrefixFromBinaryName( |
||||||
|
name, |
||||||
|
buildType, |
||||||
|
outputKind.taskNameClassifier |
||||||
|
) |
||||||
|
|
||||||
|
private fun extractPrefixFromBinaryName(name: String, buildType: NativeBuildType, outputKindClassifier: String): String { |
||||||
|
val suffix = joinLowerCamelCase(buildType.getName(), outputKindClassifier) |
||||||
|
return if (name == suffix) |
||||||
|
"" |
||||||
|
else |
||||||
|
name.substringBeforeLast(suffix.uppercaseFirstChar()) |
||||||
|
} |
||||||
|
|
||||||
|
private fun iosTargetResourcesProvider(framework: Framework): Provider<IosTargetResources> { |
||||||
|
val kotlinTarget = framework.target |
||||||
|
val project = framework.project |
||||||
|
return project.provider { |
||||||
|
val resourceDirs = framework.compilation.allKotlinSourceSets |
||||||
|
.flatMap { sourceSet -> |
||||||
|
sourceSet.resources.srcDirs.map { it.canonicalPath } |
||||||
|
} |
||||||
|
project.objects.new<IosTargetResources>().apply { |
||||||
|
name.set(kotlinTarget.name) |
||||||
|
konanTarget.set(kotlinTarget.konanTarget.name) |
||||||
|
dirs.set(resourceDirs) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Ensures, that a dependency between tasks is set up, |
||||||
|
* when a dependent task (fromTask) is created, while avoiding eager configuration. |
||||||
|
*/ |
||||||
|
private class LazyTasksDependencyConfigurator(private val tasks: TaskContainer) { |
||||||
|
private val existingDependencies = HashSet<Pair<String, String>>() |
||||||
|
private val requestedDependencies = HashMap<String, MutableSet<String>>() |
||||||
|
|
||||||
|
init { |
||||||
|
tasks.configureEach { fromTask -> |
||||||
|
val onTasks = requestedDependencies.remove(fromTask.name) ?: return@configureEach |
||||||
|
for (onTaskName in onTasks) { |
||||||
|
val dependency = fromTask.name to onTaskName |
||||||
|
if (existingDependencies.add(dependency)) { |
||||||
|
fromTask.dependsOn(onTaskName) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun String.lazyDependsOn(dependencyTask: String) { |
||||||
|
val dependingTask = this |
||||||
|
val dependency = dependingTask to dependencyTask |
||||||
|
if (dependency in existingDependencies) return |
||||||
|
|
||||||
|
if (dependingTask in tasks.names) { |
||||||
|
tasks.named(dependingTask).configure { it.dependsOn(dependencyTask) } |
||||||
|
existingDependencies.add(dependency) |
||||||
|
} else { |
||||||
|
requestedDependencies |
||||||
|
.getOrPut(dependingTask) { HashSet() } |
||||||
|
.add(dependencyTask) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.internal.resources |
||||||
|
|
||||||
|
import org.jetbrains.kotlin.konan.target.KonanTarget |
||||||
|
|
||||||
|
// based on AppleSdk.kt from Kotlin Gradle Plugin |
||||||
|
// See https://github.com/JetBrains/kotlin/blob/142421da5b966049b4eab44ce6856eb172cf122a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/apple/AppleSdk.kt |
||||||
|
internal fun determineIosKonanTargetsFromEnv(platform: String, archs: List<String>): List<KonanTarget> { |
||||||
|
val targets: MutableSet<KonanTarget> = mutableSetOf() |
||||||
|
|
||||||
|
when { |
||||||
|
platform.startsWith("iphoneos") -> { |
||||||
|
targets.addAll(archs.map { arch -> |
||||||
|
when (arch) { |
||||||
|
"arm64", "arm64e" -> KonanTarget.IOS_ARM64 |
||||||
|
"armv7", "armv7s" -> KonanTarget.IOS_ARM32 |
||||||
|
else -> error("Unknown iOS device arch: '$arch'") |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
platform.startsWith("iphonesimulator") -> { |
||||||
|
targets.addAll(archs.map { arch -> |
||||||
|
when (arch) { |
||||||
|
"arm64", "arm64e" -> KonanTarget.IOS_SIMULATOR_ARM64 |
||||||
|
"x86_64" -> KonanTarget.IOS_X64 |
||||||
|
else -> error("Unknown iOS simulator arch: '$arch'") |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
else -> error("Unknown iOS platform: '$platform'") |
||||||
|
} |
||||||
|
|
||||||
|
return targets.toList() |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.internal.utils |
||||||
|
|
||||||
|
import org.gradle.api.provider.Provider |
||||||
|
import org.gradle.api.provider.ProviderFactory |
||||||
|
import org.jetbrains.compose.internal.utils.findProperty |
||||||
|
import org.jetbrains.compose.internal.utils.toBooleanProvider |
||||||
|
|
||||||
|
internal object IosGradleProperties { |
||||||
|
const val SYNC_RESOURCES_PROPERTY = "org.jetbrains.compose.ios.resources.sync" |
||||||
|
|
||||||
|
fun syncResources(providers: ProviderFactory): Provider<Boolean> = |
||||||
|
providers.findProperty(SYNC_RESOURCES_PROPERTY).toBooleanProvider(true) |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.internal.utils |
||||||
|
|
||||||
|
import org.gradle.api.Project |
||||||
|
import org.gradle.api.plugins.ExtensionAware |
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension |
||||||
|
import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension |
||||||
|
|
||||||
|
private const val COCOAPODS_PLUGIN_ID = "org.jetbrains.kotlin.native.cocoapods" |
||||||
|
internal fun Project.withCocoapodsPlugin(fn: () -> Unit) { |
||||||
|
project.plugins.withId(COCOAPODS_PLUGIN_ID) { |
||||||
|
fn() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
internal val KotlinMultiplatformExtension.cocoapodsExt: CocoapodsExtension |
||||||
|
get() { |
||||||
|
val extensionAware = (this as? ExtensionAware) ?: error("KotlinMultiplatformExtension is not ExtensionAware") |
||||||
|
val extName = "cocoapods" |
||||||
|
val ext = extensionAware.extensions.findByName(extName) ?: error("KotlinMultiplatformExtension does not contain '$extName' extension") |
||||||
|
return ext as CocoapodsExtension |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.internal.utils |
||||||
|
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget |
||||||
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget |
||||||
|
import org.jetbrains.kotlin.konan.target.KonanTarget |
||||||
|
|
||||||
|
internal fun KotlinNativeTarget.isIosSimulatorTarget(): Boolean = |
||||||
|
konanTarget === KonanTarget.IOS_X64 || konanTarget === KonanTarget.IOS_SIMULATOR_ARM64 |
||||||
|
|
||||||
|
internal fun KotlinNativeTarget.isIosDeviceTarget(): Boolean = |
||||||
|
konanTarget === KonanTarget.IOS_ARM64 || konanTarget === KonanTarget.IOS_ARM32 |
||||||
|
|
||||||
|
internal fun KotlinNativeTarget.isIosTarget(): Boolean = |
||||||
|
isIosSimulatorTarget() || isIosDeviceTarget() |
||||||
|
|
||||||
|
internal fun KotlinTarget.asIosNativeTargetOrNull(): KotlinNativeTarget? = |
||||||
|
(this as? KotlinNativeTarget)?.takeIf { it.isIosTarget() } |
@ -0,0 +1,100 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers. |
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.jetbrains.compose.experimental.uikit.tasks |
||||||
|
|
||||||
|
import org.gradle.api.file.DirectoryProperty |
||||||
|
import org.gradle.api.file.FileCollection |
||||||
|
import org.gradle.api.provider.Provider |
||||||
|
import org.gradle.api.provider.SetProperty |
||||||
|
import org.gradle.api.tasks.* |
||||||
|
import org.jetbrains.compose.experimental.uikit.internal.resources.determineIosKonanTargetsFromEnv |
||||||
|
import org.jetbrains.compose.experimental.uikit.internal.resources.IosTargetResources |
||||||
|
import org.jetbrains.compose.internal.utils.clearDirs |
||||||
|
import java.io.File |
||||||
|
import kotlin.io.path.Path |
||||||
|
import kotlin.io.path.pathString |
||||||
|
import kotlin.io.path.relativeTo |
||||||
|
|
||||||
|
abstract class SyncComposeResourcesForIosTask : AbstractComposeIosTask() { |
||||||
|
private fun missingTargetEnvAttributeError(attribute: String): Provider<Nothing> = |
||||||
|
providers.provider { |
||||||
|
error( |
||||||
|
"Could not infer iOS target $attribute. Make sure to build " + |
||||||
|
"via XCode (directly or via Kotlin Multiplatform Mobile plugin for Android Studio)") |
||||||
|
} |
||||||
|
|
||||||
|
@get:Input |
||||||
|
val xcodeTargetPlatform: Provider<String> = |
||||||
|
providers.gradleProperty("compose.ios.resources.platform") |
||||||
|
.orElse(providers.environmentVariable("PLATFORM_NAME")) |
||||||
|
.orElse(missingTargetEnvAttributeError("platform")) |
||||||
|
|
||||||
|
@get:Input |
||||||
|
val xcodeTargetArchs: Provider<List<String>> = |
||||||
|
providers.gradleProperty("compose.ios.resources.archs") |
||||||
|
.orElse(providers.environmentVariable("ARCHS")) |
||||||
|
.orElse(missingTargetEnvAttributeError("architectures")) |
||||||
|
.map { it.split(",", " ").filter { it.isNotBlank() } } |
||||||
|
|
||||||
|
@get:Input |
||||||
|
internal val iosTargets: SetProperty<IosTargetResources> = objects.setProperty(IosTargetResources::class.java) |
||||||
|
|
||||||
|
@get:PathSensitive(PathSensitivity.ABSOLUTE) |
||||||
|
@get:InputFiles |
||||||
|
val resourceFiles: Provider<FileCollection> = xcodeTargetPlatform.zip(xcodeTargetArchs, ::Pair) |
||||||
|
.map { (xcodeTargetPlatform, xcodeTargetArchs) -> |
||||||
|
val allResources = objects.fileCollection() |
||||||
|
val activeKonanTargets = determineIosKonanTargetsFromEnv(xcodeTargetPlatform, xcodeTargetArchs) |
||||||
|
.mapTo(HashSet()) { it.name } |
||||||
|
val dirsToInclude = iosTargets.get() |
||||||
|
.filter { it.konanTarget.get() in activeKonanTargets } |
||||||
|
.flatMapTo(HashSet()) { it.dirs.get() } |
||||||
|
for (dirPath in dirsToInclude) { |
||||||
|
val fileTree = objects.fileTree().apply { |
||||||
|
setDir(layout.projectDirectory.dir(dirPath)) |
||||||
|
include("**/*") |
||||||
|
} |
||||||
|
allResources.from(fileTree) |
||||||
|
} |
||||||
|
allResources |
||||||
|
} |
||||||
|
|
||||||
|
@get:OutputDirectory |
||||||
|
val outputDir: DirectoryProperty = objects.directoryProperty() |
||||||
|
|
||||||
|
@TaskAction |
||||||
|
fun run() { |
||||||
|
val outputDir = outputDir.get().asFile |
||||||
|
fileOperations.clearDirs(outputDir) |
||||||
|
val allResourceDirs = iosTargets.get().flatMapTo(HashSet()) { it.dirs.get().map { Path(it).toAbsolutePath() } } |
||||||
|
|
||||||
|
fun copyFileToOutputDir(file: File) { |
||||||
|
for (dir in allResourceDirs) { |
||||||
|
val path = file.toPath().toAbsolutePath() |
||||||
|
if (path.startsWith(dir)) { |
||||||
|
val targetFile = outputDir.resolve(path.relativeTo(dir).pathString) |
||||||
|
file.copyTo(targetFile, overwrite = true) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
error( |
||||||
|
buildString { |
||||||
|
appendLine("Resource file '$file' does not belong to a known resource directory:") |
||||||
|
allResourceDirs.forEach { |
||||||
|
appendLine("* $it") |
||||||
|
} |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
val resourceFiles = resourceFiles.get().files |
||||||
|
for (file in resourceFiles) { |
||||||
|
copyFileToOutputDir(file) |
||||||
|
} |
||||||
|
logger.info("Synced Compose resource files. Copied ${resourceFiles.size} files to $outputDir") |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue