diff --git a/api/multiplatform-swiftpackage.api b/api/multiplatform-swiftpackage.api index 6882dfb..214541c 100644 --- a/api/multiplatform-swiftpackage.api +++ b/api/multiplatform-swiftpackage.api @@ -6,11 +6,17 @@ public final class com/chromaticnoise/multiplatformswiftpackage/MultiplatformSwi } public class com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension { + public static final field Companion Lcom/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension$Companion; public fun (Lorg/gradle/api/Project;)V public final fun buildConfiguration (Lorg/gradle/api/Action;)V public final fun distributionMode (Lorg/gradle/api/Action;)V public final fun outputDirectory (Ljava/io/File;)V public final fun packageName (Ljava/lang/String;)V + public final fun packageTemplate (Lgroovy/lang/Closure;)V + public final fun packageTemplate (Ljava/io/File;)V + public final fun packageTemplate (Ljava/io/File;Lgroovy/lang/Closure;)V + public final fun packageTemplate (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V + public final fun packageTemplate (Lkotlin/jvm/functions/Function1;)V public final fun swiftToolsVersion (Ljava/lang/String;)V public final fun targetPlatforms (Lorg/gradle/api/Action;)V } @@ -28,6 +34,58 @@ public final class com/chromaticnoise/multiplatformswiftpackage/dsl/Distribution public final fun remote (Ljava/lang/String;)V } +public final class com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL { + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/util/Collection; + public final fun component4 ()Lcom/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistribution; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/Collection;Lcom/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistribution;Ljava/util/Map;)Lcom/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL; + public static synthetic fun copy$default (Lcom/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL;Ljava/lang/String;Ljava/lang/String;Ljava/util/Collection;Lcom/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistribution;Ljava/util/Map;ILjava/lang/Object;)Lcom/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL; + public fun equals (Ljava/lang/Object;)Z + public final fun get (Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey;)Ljava/lang/Object; + public final fun get (Ljava/lang/String;)Ljava/lang/Object; + public final fun getPackageName ()Ljava/lang/String; + public final fun getPlatforms ()Ljava/util/Collection; + public final fun getRemoteDistribution ()Lcom/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistribution; + public final fun getToolsVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun set (Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey;Ljava/lang/Object;)V + public final fun set (Ljava/lang/String;Ljava/lang/Object;)V + public final fun setPackageName (Ljava/lang/String;)V + public final fun setPlatforms (Ljava/util/Collection;)V + public final fun setRemoteDistribution (Lcom/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistribution;)V + public final fun setToolsVersion (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/chromaticnoise/multiplatformswiftpackage/dsl/Platform { + public fun (Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/chromaticnoise/multiplatformswiftpackage/dsl/Platform; + public static synthetic fun copy$default (Lcom/chromaticnoise/multiplatformswiftpackage/dsl/Platform;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/chromaticnoise/multiplatformswiftpackage/dsl/Platform; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public final fun getVersion ()Ljava/lang/String; + public fun hashCode ()I + public final fun setName (Ljava/lang/String;)V + public final fun setVersion (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + +public final class com/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistribution { + public fun equals (Ljava/lang/Object;)Z + public final fun getBaseUrl ()Ljava/lang/String; + public final fun getFullUrl ()Ljava/lang/String; + public final fun getZipFileChecksum ()Ljava/lang/String; + public final fun getZipFileName ()Ljava/lang/String; + public fun hashCode ()I + public final fun setBaseUrl (Ljava/lang/String;)V + public final fun setZipFileChecksum (Ljava/lang/String;)V + public final fun setZipFileName (Ljava/lang/String;)V + public fun toString ()Ljava/lang/String; +} + public final class com/chromaticnoise/multiplatformswiftpackage/dsl/TargetPlatformDsl { public fun ()V public final fun iOS (Lorg/gradle/api/Action;)V @@ -42,3 +100,14 @@ public final class com/chromaticnoise/multiplatformswiftpackage/dsl/TargetPlatfo public final fun v (Ljava/lang/String;)V } +public final class com/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey : java/lang/Enum { + public static final field Checksum Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static final field IsLocal Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static final field Name Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static final field Platforms Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static final field ToolsVersion Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static final field Url Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static fun valueOf (Ljava/lang/String;)Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; + public static fun values ()[Lcom/chromaticnoise/multiplatformswiftpackage/dsl/TemplateKey; +} + diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension.kt index ba1043c..de4cc1d 100644 --- a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension.kt +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension.kt @@ -2,11 +2,15 @@ package com.chromaticnoise.multiplatformswiftpackage import com.chromaticnoise.multiplatformswiftpackage.domain.* import com.chromaticnoise.multiplatformswiftpackage.domain.PluginConfiguration.PluginConfigurationError +import com.chromaticnoise.multiplatformswiftpackage.domain.SwiftPackageTemplate.TemplateFile import com.chromaticnoise.multiplatformswiftpackage.dsl.BuildConfigurationDSL import com.chromaticnoise.multiplatformswiftpackage.dsl.DistributionModeDSL +import com.chromaticnoise.multiplatformswiftpackage.dsl.PackageTemplateDSL import com.chromaticnoise.multiplatformswiftpackage.dsl.TargetPlatformDsl +import groovy.lang.Closure import org.gradle.api.Action import org.gradle.api.Project +import org.gradle.util.ConfigureUtil import java.io.File public open class SwiftPackageExtension(project: Project) { @@ -18,6 +22,7 @@ public open class SwiftPackageExtension(project: Project) { internal var distributionMode: DistributionMode = DistributionMode.Local internal var targetPlatforms: Collection, TargetPlatform>> = emptyList() internal var appleTargets: Collection = emptyList() + internal var packageTemplate: SwiftPackageTemplate = SwiftPackageTemplate(file = DEFAULT_TEMPLATE_FILE, configure = {}) /** * Sets the name of the Swift package. @@ -73,4 +78,38 @@ public open class SwiftPackageExtension(project: Project) { targetPlatforms = dsl.targetPlatforms } } + + /** + * Builder for the [SwiftPackageTemplate]. + * + * @param path that points to the template file. + * @param configure closure that configures the properties of the template. + */ + public fun packageTemplate(path: File, configure: PackageTemplateDSL.() -> Unit) { + packageTemplate = SwiftPackageTemplate( + file = TemplateFile.File(path), + configure = configure + ) + } + + public fun packageTemplate(path: File, configure: Closure) { + packageTemplate(path) { ConfigureUtil.configure(configure, this) } + } + + public fun packageTemplate(path: File) { + packageTemplate(path) {} + } + + public fun packageTemplate(configure: PackageTemplateDSL.() -> Unit) { + packageTemplate = SwiftPackageTemplate(DEFAULT_TEMPLATE_FILE, configure) + } + + public fun packageTemplate(configure: Closure) { + packageTemplate = SwiftPackageTemplate(DEFAULT_TEMPLATE_FILE) { ConfigureUtil.configure(configure, this) } + } + + private companion object { + private val DEFAULT_TEMPLATE_FILE = + TemplateFile.Resource(MultiplatformSwiftPackagePlugin::class.java.getResource("/templates/Package.swift.template")) + } } diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/DistributionURL.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/DistributionURL.kt index b4c9a5f..32604fa 100644 --- a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/DistributionURL.kt +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/DistributionURL.kt @@ -1,9 +1,5 @@ package com.chromaticnoise.multiplatformswiftpackage.domain internal data class DistributionURL(val value: String) { - private val slashTerminatedValue: String get() = - value.takeIf { it.endsWith("/") } ?: - "$value/" - - fun appendPath(path: String) = DistributionURL("$slashTerminatedValue$path") + fun appendPath(path: String) = DistributionURL("${slashTerminatedUrl(value)}$path") } diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/PluginConfiguration.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/PluginConfiguration.kt index 6a180c7..b9bd1b5 100644 --- a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/PluginConfiguration.kt +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/PluginConfiguration.kt @@ -10,7 +10,8 @@ internal class PluginConfiguration private constructor( val swiftToolsVersion: SwiftToolVersion, val distributionMode: DistributionMode, val targetPlatforms: Collection, - val appleTargets: Collection + val appleTargets: Collection, + val packageTemplate: SwiftPackageTemplate ) { internal companion object { fun of(extension: SwiftPackageExtension): Either, PluginConfiguration> { @@ -47,7 +48,8 @@ internal class PluginConfiguration private constructor( extension.swiftToolsVersion!!, extension.distributionMode, targetPlatforms, - extension.appleTargets + extension.appleTargets, + extension.packageTemplate ) ) } else { diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfiguration.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfiguration.kt index 56a3958..cfaa98a 100644 --- a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfiguration.kt +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfiguration.kt @@ -1,36 +1,66 @@ package com.chromaticnoise.multiplatformswiftpackage.domain -import com.chromaticnoise.multiplatformswiftpackage.MultiplatformSwiftPackagePlugin +import com.chromaticnoise.multiplatformswiftpackage.domain.SwiftPackageTemplate.TemplateFile +import com.chromaticnoise.multiplatformswiftpackage.dsl.PackageTemplateDSL +import com.chromaticnoise.multiplatformswiftpackage.dsl.Platform +import com.chromaticnoise.multiplatformswiftpackage.dsl.RemoteDistribution +import com.chromaticnoise.multiplatformswiftpackage.dsl.TemplateKey +import com.chromaticnoise.multiplatformswiftpackage.dsl.TemplateKey.* +import com.chromaticnoise.multiplatformswiftpackage.task.zipFileChecksum import com.chromaticnoise.multiplatformswiftpackage.task.zipFileName import org.gradle.api.Project internal data class SwiftPackageConfiguration( private val project: Project, + private val packageTemplate: SwiftPackageTemplate, + private val toolsVersion: SwiftToolVersion, private val packageName: PackageName, - private val toolVersion: SwiftToolVersion, - private val platforms: String, private val distributionMode: DistributionMode, - private val zipChecksum: String + private val outputDirectory: OutputDirectory, + private val targetPlatforms: Collection, + private val appleTargets: Collection ) { - private val distributionUrl = when (distributionMode) { - DistributionMode.Local -> null - is DistributionMode.Remote -> distributionMode.url.appendPath(zipFileName(project, packageName)) + internal val templateFile: TemplateFile get() = packageTemplate.file + + internal val templateProperties: Map get() = PackageTemplateDSL( + toolsVersion = toolsVersion.name, + packageName = packageName.value, + platforms = platforms, + remoteDistribution = remoteDistribution + ).run { + packageTemplate.configure(this) + mapOf( + ToolsVersion to toolsVersion, + Name to packageName, + Platforms to platforms.joinToString(",\n") { ".${it.name}(.v${it.version})" }, + IsLocal to (remoteDistribution == null), + Url to remoteDistribution?.fullUrl, + Checksum to remoteDistribution?.zipFileChecksum + ) + .withStringKeys() + .apply { putAll(extraProperties) } + } + + private val remoteDistribution get() = (distributionMode as? DistributionMode.Remote)?.let { mode -> + RemoteDistribution( + baseUrl = mode.url.value, + zipFileName = zipFileName(project, packageName), + zipFileChecksum = zipFileChecksum(project, outputDirectory, packageName).trim() + ) } - internal val templateProperties = mapOf( - "toolsVersion" to toolVersion.name, - "name" to packageName.value, - "platforms" to platforms, - "isLocal" to (distributionMode == DistributionMode.Local), - "url" to distributionUrl?.value, - "checksum" to zipChecksum.trim() - ) + private val platforms: MutableCollection get() = targetPlatforms.flatMap { platform -> + appleTargets + .filter { appleTarget -> platform.targets.firstOrNull { it.konanTarget == appleTarget.nativeTarget.konanTarget } != null } + .mapNotNull { target -> target.nativeTarget.konanTarget.family.swiftPackagePlatformName } + .distinct() + .map { platformName -> Platform(platformName, platform.version.name) } + }.toMutableList() + + private fun Map.withStringKeys(): MutableMap = mapKeys { it.key.value }.toMutableMap() internal companion object { internal const val FILE_NAME = "Package.swift" - - internal val templateFile = - MultiplatformSwiftPackagePlugin::class.java.getResource("/templates/Package.swift.template") } } diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageTemplate.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageTemplate.kt new file mode 100644 index 0000000..62ea524 --- /dev/null +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageTemplate.kt @@ -0,0 +1,15 @@ +package com.chromaticnoise.multiplatformswiftpackage.domain + +import com.chromaticnoise.multiplatformswiftpackage.dsl.PackageTemplateDSL +import java.net.URL + +internal data class SwiftPackageTemplate( + val file: TemplateFile, + val configure: PackageTemplateDSL.() -> Unit +) { + + internal sealed class TemplateFile { + data class Resource(val url: URL) : TemplateFile() + data class File(val value: java.io.File) : TemplateFile() + } +} diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/functions.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/functions.kt index 2787b77..f3b4338 100644 --- a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/functions.kt +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/functions.kt @@ -1,3 +1,5 @@ package com.chromaticnoise.multiplatformswiftpackage.domain internal fun String.ifNotBlank(f: (String) -> T?): T? = takeIf { it.isNotBlank() }?.let { f(it) } + +internal fun slashTerminatedUrl(url: String): String = url.takeIf { it.endsWith("/") } ?: "$url/" diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL.kt new file mode 100644 index 0000000..521fd2a --- /dev/null +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSL.kt @@ -0,0 +1,131 @@ +package com.chromaticnoise.multiplatformswiftpackage.dsl + +import com.chromaticnoise.multiplatformswiftpackage.domain.slashTerminatedUrl + +/** + * DSL to configure the rendering of the Package.swift file. + * + * @param toolsVersion Version of the Swift tools. That's the version added to the Package.swift header. + * @param packageName Name of the Swift package + * @param platforms All target platforms + * @param remoteDistribution Null if the framework is to be distributed locally. + * Otherwise represents info about the remote distribution. + */ +public data class PackageTemplateDSL internal constructor( + public var toolsVersion: String, + public var packageName: String, + public var platforms: MutableCollection, + public var remoteDistribution: RemoteDistribution?, + internal val extraProperties: MutableMap = mutableMapOf() +) { + + /** + * Sets the [value] as a custom property with the given [key]. + */ + public operator fun set(key: String, value: Any?) { extraProperties[key] = value } + public operator fun set(templateKey: TemplateKey, value: Any?) { set(templateKey.value, value) } + + public operator fun get(key: String): Any? = extraProperties[key] + public operator fun get(key: TemplateKey): Any? = extraProperties[key.value] +} + +/** + * Information related to distributing the framework remotely. + * + * @param baseUrl Base URL of the [fullUrl]. The initial value is guaranteed to end with a slash (/). + * @param zipFileName Name of the ZIP file. + * @param zipFileChecksum Checksum of the ZIP file. + */ +public class RemoteDistribution internal constructor( + baseUrl: String, + public var zipFileName: String, + public var zipFileChecksum: String +) { + + public var baseUrl: String = slashTerminatedUrl(baseUrl) + + /** + * URL pointing to the ZIP file. + * Calculated by concatenating the [baseUrl] and [zipFileName] with a slash (/). + */ + public val fullUrl: String get() = "${slashTerminatedUrl(baseUrl)}$zipFileName" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RemoteDistribution) return false + + if (zipFileName != other.zipFileName) return false + if (zipFileChecksum != other.zipFileChecksum) return false + if (baseUrl != other.baseUrl) return false + + return true + } + + override fun hashCode(): Int { + var result = zipFileName.hashCode() + result = 31 * result + zipFileChecksum.hashCode() + result = 31 * result + baseUrl.hashCode() + return result + } + + override fun toString(): String = + "RemoteDistribution(zipFileName='$zipFileName', zipFileChecksum='$zipFileChecksum', baseUrl='$baseUrl')" +} + +/** + * Information about a target platform. + * + * @param name The name of the platform. E.g. iOS + * @param version The version targeted by the platform. + */ +public data class Platform( + public var name: String, + public var version: String +) + +/** + * Key used in the default Package.swift template to access the associated property. + */ +public enum class TemplateKey(internal val value: String) { + /** + * Version of the Swift tools. + * + * @see [PackageTemplateDSL.toolsVersion] + */ + ToolsVersion("toolsVersion"), + + /** + * Name of the package. + * + * @see [PackageTemplateDSL.packageName] + */ + Name("name"), + + /** + * Target platforms. + * + * @see [PackageTemplateDSL.platforms] + */ + Platforms("platforms"), + + /** + * Indicates if the framework will be distributed locally or remotely. + * + * @see [PackageTemplateDSL.remoteDistribution] + */ + IsLocal("isLocal"), + + /** + * URL of the ZIP file. + * + * @see [RemoteDistribution.fullUrl] + */ + Url("url"), + + /** + * Checksum of the ZIP file. + * + * @see [RemoteDistribution.zipFileChecksum] + */ + Checksum("checksum") +} diff --git a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateSwiftPackageTask.kt b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateSwiftPackageTask.kt index 2a0d22c..7de3fc1 100644 --- a/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateSwiftPackageTask.kt +++ b/src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateSwiftPackageTask.kt @@ -1,6 +1,8 @@ package com.chromaticnoise.multiplatformswiftpackage.task -import com.chromaticnoise.multiplatformswiftpackage.domain.* +import com.chromaticnoise.multiplatformswiftpackage.domain.SwiftPackageConfiguration +import com.chromaticnoise.multiplatformswiftpackage.domain.SwiftPackageTemplate.TemplateFile +import com.chromaticnoise.multiplatformswiftpackage.domain.getConfigurationOrThrow import groovy.text.SimpleTemplateEngine import org.gradle.api.Project import java.io.File @@ -21,26 +23,25 @@ internal fun Project.registerCreateSwiftPackageTask() { } val packageConfiguration = SwiftPackageConfiguration( - project = project, + project, + packageTemplate = configuration.packageTemplate, + toolsVersion = configuration.swiftToolsVersion, packageName = configuration.packageName, - toolVersion = configuration.swiftToolsVersion, - platforms = platforms(configuration), distributionMode = configuration.distributionMode, - zipChecksum = zipFileChecksum(project, configuration.outputDirectory, configuration.packageName) + outputDirectory = configuration.outputDirectory, + targetPlatforms = configuration.targetPlatforms, + appleTargets = configuration.appleTargets ) SimpleTemplateEngine() - .createTemplate(SwiftPackageConfiguration.templateFile) + .run { + when (val file = packageConfiguration.templateFile) { + is TemplateFile.Resource -> createTemplate(file.url) + is TemplateFile.File -> createTemplate(file.value) + } + } .make(packageConfiguration.templateProperties) .writeTo(packageFile.writer()) } } } - -private fun platforms(configuration: PluginConfiguration): String = configuration.targetPlatforms.flatMap { platform -> - configuration.appleTargets - .filter { appleTarget -> platform.targets.firstOrNull { it.konanTarget == appleTarget.nativeTarget.konanTarget } != null } - .mapNotNull { target -> target.nativeTarget.konanTarget.family.swiftPackagePlatformName } - .distinct() - .map { platformName -> ".$platformName(.v${platform.version.name})" } -}.joinToString(",\n") diff --git a/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtensionTest.kt b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtensionTest.kt new file mode 100644 index 0000000..187988f --- /dev/null +++ b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtensionTest.kt @@ -0,0 +1,170 @@ +package com.chromaticnoise.multiplatformswiftpackage + +import com.chromaticnoise.multiplatformswiftpackage.domain.BuildConfiguration +import com.chromaticnoise.multiplatformswiftpackage.domain.DistributionMode +import com.chromaticnoise.multiplatformswiftpackage.domain.DistributionURL +import com.chromaticnoise.multiplatformswiftpackage.domain.SwiftPackageTemplate +import com.chromaticnoise.multiplatformswiftpackage.dsl.PackageTemplateDSL +import groovy.lang.Closure +import io.kotest.core.spec.style.DescribeSpec +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.collections.shouldNotBeEmpty +import io.kotest.matchers.comparables.shouldBeEqualComparingTo +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.mockk.every +import io.mockk.mockk +import org.gradle.api.Project +import org.gradle.kotlin.dsl.closureOf +import org.gradle.util.ClosureBackedAction +import java.io.File + +class SwiftPackageExtensionTest : DescribeSpec() { + + init { + describe("package name") { + val ext = SwiftPackageExtension(project) + + it("has no default") { + ext.packageName shouldBe null + } + + it("can be set") { + ext.packageName("new name") + ext.packageName!!.orNull!!.value shouldBeEqualComparingTo "new name" + } + } + + describe("output directory") { + val ext = SwiftPackageExtension(project) + + it("defaults to the project dir") { + ext.outputDirectory.value shouldBeEqualComparingTo File(project.projectDir, "swiftpackage") + } + + it("can be set") { + ext.outputDirectory(File("new file")) + ext.outputDirectory.value shouldBeEqualComparingTo File("new file") + } + } + + describe("swift tools version") { + val ext = SwiftPackageExtension(project) + + it("has no default") { + ext.swiftToolsVersion shouldBe null + } + + it("can be set") { + ext.swiftToolsVersion("new version") + ext.swiftToolsVersion!!.name shouldBeEqualComparingTo "new version" + } + } + + describe("build configuration") { + val ext = SwiftPackageExtension(project) + + it("should default to release") { + ext.buildConfiguration shouldBe BuildConfiguration.Release + } + + it("invokes the DSL configuration") { + var invoked = false + ext.buildConfiguration(action { invoked = true }) + invoked shouldBe true + } + + it("can be set") { + ext.buildConfiguration(action { debug() }) + ext.buildConfiguration shouldBe BuildConfiguration.Debug + } + } + + describe("distribution mode") { + val ext = SwiftPackageExtension(project) + + it("should default to local") { + ext.distributionMode shouldBe DistributionMode.Local + } + + it("invokes the DSL configuration") { + var invoked = false + ext.distributionMode(action { invoked = true }) + invoked shouldBe true + } + + it("can be set") { + ext.distributionMode(action { remote("some url") }) + ext.distributionMode shouldBe DistributionMode.Remote(DistributionURL("some url")) + } + } + + describe("target platforms") { + val ext = SwiftPackageExtension(project) + + it("should default to an empty collection") { + ext.targetPlatforms.shouldBeEmpty() + } + + it("invokes the DSL configuration") { + var invoked = false + ext.targetPlatforms(action { invoked = true }) + invoked shouldBe true + } + + it("can be set") { + ext.targetPlatforms(action { iOS(action { v("42") }) }) + ext.targetPlatforms.shouldNotBeEmpty() + } + } + + describe("package template") { + val ext = SwiftPackageExtension(project) + + it("should default to the bundled template file") { + ext.packageTemplate.file.shouldBeInstanceOf() + } + + it("can be configured") { + var invoked = false + ext.packageTemplate(File("")) { invoked = true } + ext.packageTemplate.configure(PackageTemplateDSL("", "", mutableListOf(), null, mutableMapOf())) + invoked shouldBe true + } + + it("can be configured by Groovy") { + var invoked = false + ext.packageTemplate(File(""), closure { invoked = true }) + ext.packageTemplate.configure(PackageTemplateDSL("", "", mutableListOf(), null, mutableMapOf())) + invoked shouldBe true + } + + it("can configure the default template file") { + var invoked = false + ext.packageTemplate { invoked = true } + ext.packageTemplate.configure(PackageTemplateDSL("", "", mutableListOf(), null, mutableMapOf())) + invoked shouldBe true + } + + it("can configure the default template file from Groovu") { + var invoked = false + ext.packageTemplate(closure { invoked = true }) + ext.packageTemplate.configure(PackageTemplateDSL("", "", mutableListOf(), null, mutableMapOf())) + invoked shouldBe true + } + + it("can set the template file") { + ext.packageTemplate(File("the file")) + (ext.packageTemplate.file as SwiftPackageTemplate.TemplateFile.File).value shouldBeEqualComparingTo File("the file") + } + } + } + + private fun action(f: T.() -> Unit) = ClosureBackedAction(closureOf(f)) + + private fun closure(f: T.() -> Unit) = closureOf(f) as Closure + + private val project: Project get() = mockk(relaxed = true) { + every { projectDir } returns File("project dir") + } +} diff --git a/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/FunctionsTest.kt b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/FunctionsTest.kt new file mode 100644 index 0000000..0c7dbb1 --- /dev/null +++ b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/FunctionsTest.kt @@ -0,0 +1,32 @@ +package com.chromaticnoise.multiplatformswiftpackage.domain + +import io.kotest.core.spec.style.StringSpec +import io.kotest.property.Arb +import io.kotest.property.arbitrary.filter +import io.kotest.property.arbitrary.string +import io.kotest.property.forAll + +class FunctionsTest : StringSpec({ + + "closure should not be invoked on blank string" { + forAll(Arb.string().filter { it.isBlank() }) { str -> + var invoked = false + str.ifNotBlank { invoked = true } + !invoked + } + } + + "closure should not be invoked on non-blank string" { + forAll(Arb.string().filter { it.isNotBlank() }) { str -> + var invoked = false + str.ifNotBlank { invoked = true } + invoked + } + } + + "when getting slash-terminated URL it should end with a slash" { + forAll(Arb.string()) { url -> + slashTerminatedUrl(url).endsWith("/") + } + } +}) diff --git a/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfigurationTest.kt b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfigurationTest.kt index 8408504..cb760ac 100644 --- a/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfigurationTest.kt +++ b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfigurationTest.kt @@ -1,18 +1,24 @@ package com.chromaticnoise.multiplatformswiftpackage.domain +import com.chromaticnoise.multiplatformswiftpackage.domain.SwiftPackageTemplate.TemplateFile +import com.chromaticnoise.multiplatformswiftpackage.dsl.PackageTemplateDSL import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.booleans.shouldBeTrue +import io.kotest.matchers.comparables.shouldBeEqualComparingTo import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldEndWith import io.kotest.matchers.string.shouldStartWith import io.mockk.mockk +import java.io.File class SwiftPackageConfigurationTest : StringSpec() { init { "tools version property should match the tools version name" { configuration() - .copy(toolVersion = SwiftToolVersion.of("42")!!) + .copy(toolsVersion = SwiftToolVersion.of("42")!!) .templateProperties["toolsVersion"].shouldBe("42") } @@ -22,12 +28,6 @@ class SwiftPackageConfigurationTest : StringSpec() { .templateProperties["name"].shouldBe("expected name") } - "platforms property should match the given platforms" { - configuration() - .copy(platforms = "my platforms") - .templateProperties["platforms"].shouldBe("my platforms") - } - "is-local property should be true if distribution mode is local" { configuration() .copy(distributionMode = DistributionMode.Local) @@ -58,19 +58,74 @@ class SwiftPackageConfigurationTest : StringSpec() { .templateProperties["url"] as String).shouldEndWith(".zip") } - "checksum property should match the value of zip checksum" { - configuration() - .copy(zipChecksum = "the checksum") - .templateProperties["checksum"].shouldBe("the checksum") + "configuration closure should be invoked" { + var wasInvoked = false + configuration { wasInvoked = true } + .templateProperties + + wasInvoked.shouldBeTrue() + } + + "configuration closure should receive tools version" { + verifyDsl({ + copy(toolsVersion = SwiftToolVersion.of("expected version")!!) + }) { + toolsVersion shouldBeEqualComparingTo "expected version" + } + } + + "configuration closure should receive package name" { + verifyDsl({ + copy(packageName = PackageName.of("expected package name").orNull!!) + }) { + packageName shouldBeEqualComparingTo "expected package name" + } + } + + "configuration closure should not receive remote configuration if locally distributed" { + verifyDsl({ + copy(distributionMode = DistributionMode.Local) + }) { + remoteDistribution shouldBe null + } + } + + "configuration closure should receive remote configuration if remotely distributed" { + verifyDsl({ + copy(distributionMode = DistributionMode.Remote(DistributionURL("url"))) + }) { + remoteDistribution shouldNotBe null + } + } + + "configuration closure should receive slash terminated remote distribution url" { + verifyDsl({ + copy(distributionMode = DistributionMode.Remote(DistributionURL("url"))) + }) { + remoteDistribution!!.baseUrl shouldEndWith "/" + } } } - private fun configuration() = SwiftPackageConfiguration( + private fun configuration(configuration: PackageTemplateDSL.() -> Unit = {}) = SwiftPackageConfiguration( project = mockk(relaxed = true), + packageTemplate = SwiftPackageTemplate(TemplateFile.File(File("")), configuration), + toolsVersion = SwiftToolVersion.of("42")!!, packageName = PackageName.of("package name").orNull!!, - toolVersion = SwiftToolVersion.of("42")!!, - platforms = "", distributionMode = DistributionMode.Local, - zipChecksum = "" + outputDirectory = OutputDirectory(File("")), + targetPlatforms = emptyList(), + appleTargets = emptyList() ) + + private fun verifyDsl(c: SwiftPackageConfiguration.() -> SwiftPackageConfiguration, f: PackageTemplateDSL.() -> Unit) { + var invoked = false + c(configuration()).copy( + packageTemplate = SwiftPackageTemplate(TemplateFile.File(File(""))) { + invoked = true + f(this) + } + ).templateProperties + invoked.shouldBeTrue() + } } diff --git a/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSLTest.kt b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSLTest.kt new file mode 100644 index 0000000..fc4530f --- /dev/null +++ b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/PackageTemplateDSLTest.kt @@ -0,0 +1,35 @@ +package com.chromaticnoise.multiplatformswiftpackage.dsl + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +class PackageTemplateDSLTest : StringSpec() { + + init { + "setting property by key should add it to the extras" { + val dsl = dsl() + + dsl["key"] = "value" + + dsl.extraProperties["key"] shouldBe "value" + } + + "setting property by template key should add it to the extras" { + TemplateKey.values().forEach { key -> + val dsl = dsl() + + dsl[key] = "value" + + dsl.extraProperties[key.value] shouldBe "value" + } + } + } + + private fun dsl() = PackageTemplateDSL( + toolsVersion = "version", + packageName = "package name", + platforms = mutableListOf(), + remoteDistribution = RemoteDistribution("url", "name", "checksum"), + extraProperties = mutableMapOf() + ) +} diff --git a/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistributionTest.kt b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistributionTest.kt new file mode 100644 index 0000000..75e3d36 --- /dev/null +++ b/src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/dsl/RemoteDistributionTest.kt @@ -0,0 +1,22 @@ +package com.chromaticnoise.multiplatformswiftpackage.dsl + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.comparables.shouldBeEqualComparingTo + +class RemoteDistributionTest : StringSpec({ + "base URL initially ends with a slash" { + RemoteDistribution("base url", "zip file name", "") + .baseUrl shouldBeEqualComparingTo "base url/" + } + + "base URL is not automatically slash-terminated after changing it" { + val data = RemoteDistribution("base url", "zip file name", "") + data.baseUrl = "no slash at the end" + data.baseUrl shouldBeEqualComparingTo "no slash at the end" + } + + "full URL contains base URL and ZIP file name" { + RemoteDistribution("base url", "zip file name", "") + .fullUrl shouldBeEqualComparingTo "base url/zip file name" + } +})