Skip to content

Commit 5fa5ef8

Browse files
JUSTINMKAUFMANJustin Kaufmange-org
authored
Enable Customization of Package Name (#5)
Co-authored-by: Justin Kaufman <[email protected]> Co-authored-by: Georg Dresler <[email protected]>
1 parent 04dc1d2 commit 5fa5ef8

16 files changed

+155
-18
lines changed

README.md

+13
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ multiplatformSwiftPackage {
4848
}
4949
```
5050

51+
### Package Name
52+
By default, the name of the Swift package will be the base name of the first framework found in the project.
53+
However, you can declare a different name for the package.
54+
This might be useful if your frameworks have different base names, and you want your package to have a common name.
55+
56+
```kotlin
57+
packageName("MyAwesomeKit")
58+
```
59+
60+
Hint:
61+
If the cocoapods plugin is applied the name of the package will default to the value assigned to the `frameworkName` property.
62+
Otherwise, the value of the `baseName` property of the framework configuration will be used.
63+
5164
### Output Directory
5265
By default, the plugin will write all files into the _swiftpackage_ folder in the project directory.
5366
You can configure the output folder by providing a File object pointing to it.

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies {
1717
testImplementation("io.kotest:kotest-assertions-core:4.3.0")
1818
testImplementation("io.kotest:kotest-property:4.3.0")
1919
testImplementation("io.mockk:mockk:1.10.0")
20+
testImplementation(kotlin("gradle-plugin"))
2021
}
2122

2223
java {

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/SwiftPackageExtension.kt

+11
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,23 @@ import java.io.File
1212
public open class SwiftPackageExtension(project: Project) {
1313

1414
internal var buildConfiguration: BuildConfiguration = BuildConfiguration.Release
15+
internal var packageName: Either<PluginConfigurationError, PackageName>? = null
1516
internal var outputDirectory: OutputDirectory = OutputDirectory(File(project.projectDir, "swiftpackage"))
1617
internal var swiftToolsVersion: SwiftToolVersion? = null
1718
internal var distributionMode: DistributionMode = DistributionMode.Local
1819
internal var targetPlatforms: Collection<Either<List<PluginConfigurationError>, TargetPlatform>> = emptyList()
1920
internal var appleTargets: Collection<AppleTarget> = emptyList()
2021

22+
/**
23+
* Sets the name of the Swift package.
24+
* Defaults to the base name of the first framework found in the project.
25+
*
26+
* @param name of the Swift package.
27+
*/
28+
public fun packageName(name: String) {
29+
packageName = PackageName.of(name)
30+
}
31+
2132
/**
2233
* Sets the directory where files like the Package.swift and XCFramework will be created.
2334
* Defaults to $projectDir/swiftpackage

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/Either.kt

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ internal sealed class Either<out L, out R> {
44
data class Left<out L, out R>(val value: L) : Either<L, R>()
55
data class Right<out L, out R>(val value: R) : Either<L, R>()
66

7+
val leftValueOrNull: L? get() = (this as? Left)?.value
8+
9+
val orNull: R? get() = (this as? Right)?.value
10+
711
fun <T> fold(l: (L) -> T, r: (R) -> T): T = when (this) {
812
is Left -> l(value)
913
is Right -> r(value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.chromaticnoise.multiplatformswiftpackage.domain
2+
3+
import com.chromaticnoise.multiplatformswiftpackage.domain.PluginConfiguration.PluginConfigurationError
4+
import com.chromaticnoise.multiplatformswiftpackage.domain.PluginConfiguration.PluginConfigurationError.InvalidPackageName
5+
6+
internal class PackageName private constructor(val value: String) {
7+
8+
internal companion object {
9+
fun of(name: String?): Either<PluginConfigurationError, PackageName> =
10+
name?.ifNotBlank { Either.Right(PackageName(it)) }
11+
?: Either.Left(InvalidPackageName(name))
12+
}
13+
14+
override fun equals(other: Any?): Boolean = value == (other as? PackageName)?.value
15+
16+
override fun hashCode(): Int = value.hashCode()
17+
18+
override fun toString(): String = "PackageName(value='$value')"
19+
}

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/PluginConfiguration.kt

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import com.chromaticnoise.multiplatformswiftpackage.domain.PluginConfiguration.P
55

66
internal class PluginConfiguration private constructor(
77
val buildConfiguration: BuildConfiguration,
8+
val packageName: PackageName,
89
val outputDirectory: OutputDirectory,
910
val swiftToolsVersion: SwiftToolVersion,
1011
val distributionMode: DistributionMode,
@@ -14,6 +15,7 @@ internal class PluginConfiguration private constructor(
1415
internal companion object {
1516
fun of(extension: SwiftPackageExtension): Either<List<PluginConfigurationError>, PluginConfiguration> {
1617
val targetPlatforms = extension.targetPlatforms.platforms
18+
val packageName = extension.getPackageName()
1719

1820
val errors = mutableListOf<PluginConfigurationError>().apply {
1921
if (extension.swiftToolsVersion == null) {
@@ -32,12 +34,15 @@ internal class PluginConfiguration private constructor(
3234
if (extension.appleTargets.isEmpty() && targetPlatforms.isNotEmpty()) {
3335
add(MissingAppleTargets)
3436
}
37+
38+
packageName.leftValueOrNull?.let { error -> add(error) }
3539
}
3640

3741
return if (errors.isEmpty()) {
3842
Either.Right(
3943
PluginConfiguration(
4044
extension.buildConfiguration,
45+
packageName.orNull!!,
4146
extension.outputDirectory,
4247
extension.swiftToolsVersion!!,
4348
extension.distributionMode,
@@ -49,12 +54,18 @@ internal class PluginConfiguration private constructor(
4954
Either.Left(errors)
5055
}
5156
}
57+
58+
private fun SwiftPackageExtension.getPackageName(): Either<PluginConfigurationError, PackageName> = packageName
59+
?: appleTargets.getFrameworks(buildConfiguration).firstOrNull()?.let { framework ->
60+
PackageName.of(framework.baseName)
61+
} ?: Either.Left(InvalidPackageName(null))
5262
}
5363

5464
internal sealed class PluginConfigurationError {
5565
object MissingSwiftToolsVersion : PluginConfigurationError()
5666
data class InvalidTargetName(val name: String) : PluginConfigurationError()
5767
object MissingTargetPlatforms : PluginConfigurationError()
5868
object MissingAppleTargets : PluginConfigurationError()
69+
data class InvalidPackageName(val name: String?) : PluginConfigurationError()
5970
}
6071
}

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/Project+PluginConfiguration.kt

+4
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,9 @@ private fun List<PluginConfigurationError>.toErrorMessage() = joinToString("\n\n
3333
* Target name is invalid: ${error.name}
3434
Only the following target names are valid: ${TargetName.values().joinToString { it.identifier }}
3535
""".trimIndent()
36+
is PluginConfigurationError.InvalidPackageName -> """
37+
* Package name is invalid: ${error.name}
38+
Either declare the base name of your frameworks or use a non-empty package name.
39+
""".trimIndent()
3640
}
3741
}

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfiguration.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.gradle.api.Project
66

77
internal data class SwiftPackageConfiguration(
88
private val project: Project,
9+
private val packageName: PackageName,
910
private val toolVersion: SwiftToolVersion,
1011
private val platforms: String,
1112
private val distributionMode: DistributionMode,
@@ -14,12 +15,12 @@ internal data class SwiftPackageConfiguration(
1415

1516
private val distributionUrl = when (distributionMode) {
1617
DistributionMode.Local -> null
17-
is DistributionMode.Remote -> distributionMode.url.appendPath(zipFileName(project))
18+
is DistributionMode.Remote -> distributionMode.url.appendPath(zipFileName(project, packageName))
1819
}
1920

2021
internal val templateProperties = mapOf(
2122
"toolsVersion" to toolVersion.name,
22-
"name" to project.name,
23+
"name" to packageName.value,
2324
"platforms" to platforms,
2425
"isLocal" to (distributionMode == DistributionMode.Local),
2526
"url" to distributionUrl?.value,

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/extensions.kt

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.chromaticnoise.multiplatformswiftpackage.domain
22

33
import org.gradle.api.Action
4+
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
45
import org.jetbrains.kotlin.konan.target.Family
56
import org.jetbrains.kotlin.konan.target.KonanTarget
67

@@ -34,3 +35,8 @@ internal val TargetName.konanTarget: KonanTarget get() = when (this) {
3435
TargetName.TvOSx64 -> KonanTarget.TVOS_X64
3536
TargetName.MacOSx64 -> KonanTarget.MACOS_X64
3637
}
38+
39+
internal fun Collection<AppleTarget>.getFrameworks(buildConfiguration: BuildConfiguration): Collection<Framework> =
40+
mapNotNull { target ->
41+
target.framework(buildConfiguration)
42+
}

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateSwiftPackageTask.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ internal fun Project.registerCreateSwiftPackageTask() {
2222

2323
val packageConfiguration = SwiftPackageConfiguration(
2424
project = project,
25+
packageName = configuration.packageName,
2526
toolVersion = configuration.swiftToolsVersion,
2627
platforms = platforms(configuration),
2728
distributionMode = configuration.distributionMode,
28-
zipChecksum = zipFileChecksum(project, configuration.outputDirectory)
29+
zipChecksum = zipFileChecksum(project, configuration.outputDirectory, configuration.packageName)
2930
)
3031

3132
SimpleTemplateEngine()

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateXCFrameworkTask.kt

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.chromaticnoise.multiplatformswiftpackage.task
22

33
import com.chromaticnoise.multiplatformswiftpackage.domain.getConfigurationOrThrow
4+
import com.chromaticnoise.multiplatformswiftpackage.domain.getFrameworks
45
import org.gradle.api.Project
56
import org.gradle.api.tasks.Exec
67
import java.io.File
@@ -10,12 +11,8 @@ internal fun Project.registerCreateXCFrameworkTask() = tasks.register("createXCF
1011
description = "Creates an XCFramework for all declared Apple targets"
1112

1213
val configuration = getConfigurationOrThrow()
13-
val buildConfiguration = configuration.buildConfiguration
14-
val xcFrameworkDestination = File(configuration.outputDirectory.value, "${project.name}.xcframework")
15-
16-
val frameworks = configuration.appleTargets.mapNotNull { target ->
17-
target.framework(buildConfiguration)
18-
}
14+
val xcFrameworkDestination = File(configuration.outputDirectory.value, "${configuration.packageName.value}.xcframework")
15+
val frameworks = configuration.appleTargets.getFrameworks(configuration.buildConfiguration)
1916

2017
dependsOn(frameworks.map { it.linkTaskName })
2118

@@ -28,7 +25,7 @@ internal fun Project.registerCreateXCFrameworkTask() = tasks.register("createXCF
2825
add("-framework")
2926
add(framework.outputFile.path)
3027

31-
val dsymFile = File(framework.outputFile.parent, "${project.name}.framework.dSYM")
28+
val dsymFile = File(framework.outputFile.parent, "${framework.baseName}.framework.dSYM")
3229
if (dsymFile.exists()) {
3330
add("-debug-symbols")
3431
add(dsymFile.path)

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/CreateZipFileTask.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal fun Project.registerCreateZipFileTask() {
1313

1414
val configuration = getConfigurationOrThrow()
1515
val outputDirectory = configuration.outputDirectory.value
16-
archiveFileName.set(zipFileName(project))
16+
archiveFileName.set(zipFileName(project, configuration.packageName))
1717
destinationDirectory.set(outputDirectory)
1818
from(outputDirectory) {
1919
include("**/*.xcframework/")

src/main/kotlin/com/chromaticnoise/multiplatformswiftpackage/task/zip-functions.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.chromaticnoise.multiplatformswiftpackage.task
22

3+
import com.chromaticnoise.multiplatformswiftpackage.domain.PackageName
34
import com.chromaticnoise.multiplatformswiftpackage.domain.OutputDirectory
45
import org.gradle.api.Project
56
import java.io.ByteArrayOutputStream
67
import java.io.File
78

8-
internal fun zipFileChecksum(project: Project, outputDirectory: OutputDirectory): String {
9+
internal fun zipFileChecksum(project: Project, outputDirectory: OutputDirectory, packageName: PackageName): String {
910
val outputPath = outputDirectory.value
10-
return File(outputPath, zipFileName(project))
11+
return File(outputPath, zipFileName(project, packageName))
1112
.takeIf { it.exists() }
1213
?.let { zipFile ->
1314
ByteArrayOutputStream().use { os ->
@@ -22,4 +23,4 @@ internal fun zipFileChecksum(project: Project, outputDirectory: OutputDirectory)
2223
} ?: ""
2324
}
2425

25-
internal fun zipFileName(project: Project) = "${project.name}-${project.version}.zip"
26+
internal fun zipFileName(project: Project, packageName: PackageName) = "${packageName.value}-${project.version}.zip"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.chromaticnoise.multiplatformswiftpackage.domain
2+
3+
import com.chromaticnoise.multiplatformswiftpackage.domain.PluginConfiguration.PluginConfigurationError.InvalidPackageName
4+
import io.kotest.core.spec.style.StringSpec
5+
import io.kotest.matchers.booleans.shouldBeFalse
6+
import io.kotest.matchers.booleans.shouldBeTrue
7+
import io.kotest.matchers.types.shouldBeInstanceOf
8+
import io.kotest.property.Arb
9+
import io.kotest.property.arbitrary.filter
10+
import io.kotest.property.arbitrary.string
11+
import io.kotest.property.forAll
12+
13+
class PackageNameTest : StringSpec({
14+
15+
"empty name should not be valid" {
16+
PackageName.of("").leftValueOrNull!!.shouldBeInstanceOf<InvalidPackageName>()
17+
}
18+
19+
"blank names should not be valid" {
20+
forAll(Arb.string().filter { it.isBlank() }) { name ->
21+
PackageName.of(name).leftValueOrNull is InvalidPackageName
22+
}
23+
}
24+
25+
"two instances should be equal if their names are equal" {
26+
(PackageName.of("equal name") == PackageName.of("equal name"))
27+
.shouldBeTrue()
28+
}
29+
30+
"two instances should not be equal if their names differ" {
31+
(PackageName.of("some name") == PackageName.of("different name"))
32+
.shouldBeFalse()
33+
}
34+
})

src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/PluginConfigurationTest.kt

+34
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import com.chromaticnoise.multiplatformswiftpackage.SwiftPackageExtension
44
import com.chromaticnoise.multiplatformswiftpackage.domain.PluginConfiguration.PluginConfigurationError.*
55
import io.kotest.core.spec.style.BehaviorSpec
66
import io.kotest.core.test.TestCase
7+
import io.kotest.matchers.shouldBe
78
import io.mockk.every
89
import io.mockk.mockk
910
import org.gradle.api.Project
11+
import org.jetbrains.kotlin.gradle.plugin.mpp.Framework
1012
import java.io.File
1113

1214
class PluginConfigurationTest : BehaviorSpec() {
@@ -48,6 +50,38 @@ class PluginConfigurationTest : BehaviorSpec() {
4850
(PluginConfiguration.of(extension) as Either.Left).value.contains(MissingAppleTargets)
4951
}
5052
}
53+
54+
When("the package name produced errors") {
55+
val expectedError = InvalidPackageName(null)
56+
extension.packageName = Either.Left(expectedError)
57+
58+
Then("an error should be returned") {
59+
(PluginConfiguration.of(extension) as Either.Left).value.contains(expectedError)
60+
}
61+
}
62+
63+
When("the package name is null and no apple framework exists") {
64+
extension.packageName = null
65+
extension.appleTargets = emptyList()
66+
67+
Then("an error should be returned") {
68+
(PluginConfiguration.of(extension) as Either.Left).value.contains(InvalidPackageName(null))
69+
}
70+
}
71+
72+
When("the package name is null and apple frameworks exist") {
73+
val expectedName = "expected name"
74+
val framework = mockk<Framework> { every { baseName } returns expectedName }
75+
extension.swiftToolsVersion = SwiftToolVersion.of("42")
76+
extension.packageName = null
77+
extension.appleTargets = listOf(
78+
mockk { every { framework(any()) } returns framework }
79+
)
80+
81+
Then("the base name of the first framework should be used") {
82+
PluginConfiguration.of(extension).orNull!!.packageName.value shouldBe expectedName
83+
}
84+
}
5185
}
5286
}
5387

src/test/kotlin/com/chromaticnoise/multiplatformswiftpackage/domain/SwiftPackageConfigurationTest.kt

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import io.kotest.matchers.nulls.shouldBeNull
55
import io.kotest.matchers.shouldBe
66
import io.kotest.matchers.string.shouldEndWith
77
import io.kotest.matchers.string.shouldStartWith
8-
import io.mockk.every
98
import io.mockk.mockk
109

1110
class SwiftPackageConfigurationTest : StringSpec() {
@@ -17,10 +16,10 @@ class SwiftPackageConfigurationTest : StringSpec() {
1716
.templateProperties["toolsVersion"].shouldBe("42")
1817
}
1918

20-
"name property should match the project name" {
19+
"name property should match the configured package name" {
2120
configuration()
22-
.copy(project = mockk(relaxed = true) { every { name } returns "project name" })
23-
.templateProperties["name"].shouldBe("project name")
21+
.copy(packageName = PackageName.of("expected name").orNull!!)
22+
.templateProperties["name"].shouldBe("expected name")
2423
}
2524

2625
"platforms property should match the given platforms" {
@@ -68,6 +67,7 @@ class SwiftPackageConfigurationTest : StringSpec() {
6867

6968
private fun configuration() = SwiftPackageConfiguration(
7069
project = mockk(relaxed = true),
70+
packageName = PackageName.of("package name").orNull!!,
7171
toolVersion = SwiftToolVersion.of("42")!!,
7272
platforms = "",
7373
distributionMode = DistributionMode.Local,

0 commit comments

Comments
 (0)