Skip to content

Commit 0498937

Browse files
authored
Merge pull request #60 from touchlab/kpg/spm_local_dev
Kpg/spm local dev
2 parents 8eca20f + 3478799 commit 0498937

File tree

8 files changed

+164
-21
lines changed

8 files changed

+164
-21
lines changed

docs/IOS_DEV_SETUP.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
To use the published Xcode Framework, you'll need to integrate it into your Xcode project. You'll also need to understand how to add authentication information, if required by your artifact storage.
44

5-
For developers editing Kotlin, you will want to test locally-built Kotlin code directly in your Xcode project from time to time. How that works differs depending on which dependency manager you use. For Cocoapods see [IOS_LOCAL_DEV_COCOAPODS](IOS_LOCAL_DEV_COCOAPODS.md). For SPM see [IOS_LOCAL_DEV_SPM](IOS_LOCAL_DEV_SPM.md).
5+
For developers editing Kotlin, you will want to test locally-built Kotlin code directly in your Xcode project from time to time. How that works differs depending on which dependency manager you use. For Cocoapods see [IOS_LOCAL_DEV_COCOAPODS](IOS_LOCAL_DEV_COCOAPODS.md). For SPM see [IOS_SPM_DEV](IOS_SPM_DEV.md).
66

77
## Private Github Releases
88

@@ -18,6 +18,4 @@ If you are using private Github artifacts, you'll need to add auth info for that
1818

1919
## Using SPM
2020

21-
* Add Packages, use GIthub
22-
* Select "Up to next major version"
23-
21+
See [IOS_SPM_DEV](IOS_SPM_DEV.md).

docs/IOS_SPM_DEV.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Using Swift Pacakge Manager
2+
3+
Swift Package Manager is a newer dependency manager directly from Apple. In some ways it's more integrated into Xcode, but is also less flexible than Cocoapods. Much of that seems by design, as it's very difficult to introduce side effects into the `Package.swift` build scripts. While that is likely to result in more reliable builds for the average Xcode project, for Kotlin builds, that means some more manual processes at present.
4+
5+
Out of the box, the Kotlin tools are far less integrated into SPM. We have some basic support for SPM development, but this is a work in progress. Feedback welcome.
6+
7+
## Kotlin Project Configuration
8+
9+
After setting up KMM Bridge in your Kotlin project, you should configure SPM for library publishing. Generally speaking, SPM wants to have the `Package.swift` file in the root of the repo. Xcode and SPM use git repos as an organizational and discovery unit. The `Package.swift` file goes in the root, and Xcode clones from Github (or others) to read info about the library and source code.
10+
11+
If you don't have a `Package.swift` file, or don't know how to set one up, that's OK. KMM Bridge currently generates these files for you.
12+
13+
> Note: If you'd prefer, or need to, manage your own `Package.swift` file, please reach out. An earlier version of the plugin modified the file rather than replacing it. We may add that feature back after KMM Bridge is more stable.
14+
15+
In the `kmmbridge` block, add `spm()`, and point the parameter to the root folder of your repo.
16+
17+
```kotlin
18+
kmmbridge {
19+
spm("..")
20+
// Other config...
21+
}
22+
```
23+
24+
In the example above, the Kotlin module is one folder down, so we add the parent path string `..`
25+
26+
![spmfolder](https://tl-navigator-images.s3.us-east-1.amazonaws.com/docimages/2022-10-06_06-43-spmfolder.png)
27+
28+
SPM uses git for versioning, so you'll want to use either git tag or Github release version managers, and likely want to use Github artifacts.
29+
30+
Here is the suggested config for SPM:
31+
32+
```kotlin
33+
kmmbridge {
34+
githubReleaseArtifacts()
35+
githubReleaseVersions()
36+
versionPrefix.set("0.1")
37+
spm("..")
38+
}
39+
```
40+
41+
Once this is all set up, run a build so you have at least one version available.
42+
43+
## Xcode Configuration
44+
45+
Open or create an Xcode project. To add an SPM package, go to `File > Add Packages` in the Xcode menu. Add your source control account (presumably Github). You can usually browse for the package at that point, but depending on how many repos you have, it may be easier to copy/paste the repo URL in the top/right search bar. After finding the package, you should generally add the pacakge by version ("Up to Next Major Version" suggested).
46+
47+
![addpackages](https://tl-navigator-images.s3.us-east-1.amazonaws.com/docimages/2022-10-06_06-57-addpackages.png)
48+
49+
Once added, you should be able to import the Kotlin module into Swift/Objc files and build!
50+
51+
![import](https://tl-navigator-images.s3.us-east-1.amazonaws.com/docimages/2022-10-06_07-00-import.png)
52+
53+
## Updating Builds
54+
55+
Run the KMM Bridge build again. It should automatically create another build version and publish that to the Github repo. In Xcode, you can update your imported version by right-clicking on the module and selecting "Update Package":
56+
57+
![updatepackage](https://tl-navigator-images.s3.us-east-1.amazonaws.com/docimages/2022-10-06_07-04-updatepackage.png)
58+
59+
![updatepackagedone](https://tl-navigator-images.s3.us-east-1.amazonaws.com/docimages/2022-10-06_07-17-updatepackagedone.png)
60+
61+
## SPM Local Dev Flow
62+
63+
***Experimental:*** *While all of KMM Bridge is relatively new, the SPM dev flow is very experimental. The most likely issues with be path resolutions.*
64+
65+
If you are editing the Kotlin code, you will periodically want to test your local edits directly in Xcode.
66+
67+
> As mentioned, SPM is not really integrated out of the box with Kotlin, and SPM itself does not make integrations easy (generally speaking). We have some support for local dev flow, but we are looking for feedback and improvements.
68+
69+
Since KMM Bridge is generating your `Package.swift` files, the first step is to run a dev build step to build the local dev `Package.swift` and locally build a debug version of the Kotlin code.
70+
71+
```shell
72+
./gradlew spmDevBuild
73+
```
74+
75+
This should:
76+
77+
* Build a debug version of the XCFramework
78+
* Write a local dev path to `Package.swift`
79+
80+
Next, you need to manually copy the whole Kotlin project into Xcode. That means, quite liteally, drag the Kotlin project's folder into Xcode.
81+
82+
<video src="dragspm.mp4"></video>
83+
84+
In the sample above, the pacakge `allshared` is inside `KevsKmmTest`. When you drag it in, if Xcode properly recognizes it, you'll see `allshared` disappear, but when you build, things should work as expected.
85+
86+
When you are done build, select the folder you dragged in, and remove it by right-clicking it and selecting "Delete". Make sure to select "Remove References" on the popup. Xcode should then reload the version you had previously.
87+
88+
<video src="removelocal.mp4"></video>
89+
90+
When you run `spmDevBuild`, it will build all architectures, which you probably don't need for testing on a simulator. To restrict architectures when building, you can pass in a Gradle param.
91+
92+
```shell
93+
./gradlew spmDevBuild -PspmBuildTargets=ios_x64
94+
```
95+
96+
For Intel Macs, use `ios_x64`. For arm Macs, use `ios_simulator_arm64`. You can pass in multiple architectures by separating them with commas.

docs/dragspm.mp4

173 KB
Binary file not shown.

docs/removelocal.mp4

206 KB
Binary file not shown.

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ android.useAndroidX=true
1212
org.gradle.jvmargs=-Xmx2g
1313

1414
GROUP=co.touchlab.faktory
15-
VERSION_NAME=0.1.16-SNAPSHOT
15+
VERSION_NAME=0.1.17-SNAPSHOT
1616
KOTLIN_VERSION=1.6.21
1717

1818
POM_URL=https://github.com/touchlab/KMMBridge

kmmbridge/src/main/kotlin/KMMBridge.kt

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ class KMMBridgePlugin : Plugin<Project> {
163163
val extension = extensions.getByType<KmmBridgeExtension>()
164164
var xcFrameworkConfig: XCFrameworkConfig? = null
165165

166+
val spmBuildTargets: Set<String> = project.spmBuildTargets?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }?.toSet() ?: emptySet()
167+
166168
kotlin.targets
167169
.withType<KotlinNativeTarget>()
168170
.filter { it.konanTarget.family.isAppleFamily }
@@ -177,26 +179,16 @@ class KMMBridgePlugin : Plugin<Project> {
177179
throw IllegalStateException("Only one framework name currently allowed. Found $currentName and $theName")
178180
}
179181
}
180-
if (xcFrameworkConfig == null) {
181-
xcFrameworkConfig = XCFramework(theName)
182+
val shouldAddTarget = spmBuildTargets.isEmpty() || spmBuildTargets.contains(framework.target.konanTarget.name)
183+
if(shouldAddTarget) {
184+
if (xcFrameworkConfig == null) {
185+
xcFrameworkConfig = XCFramework(theName)
186+
}
187+
xcFrameworkConfig!!.add(framework)
182188
}
183-
xcFrameworkConfig!!.add(framework)
184189
}
185190
}
186191

187-
private fun Project.findXCFrameworkAssembleTask(): Task {
188-
val extension = extensions.getByType<KmmBridgeExtension>()
189-
val name = extension.frameworkName.get()
190-
val buildTypeString = extension.buildType.get().getName().capitalize()
191-
val taskWithoutName = "assemble${buildTypeString}XCFramework"
192-
val taskWithName = "assemble${name.capitalize()}${buildTypeString}XCFramework"
193-
return try {
194-
tasks.findByName(taskWithName) ?: tasks.findByPath(taskWithoutName)!!
195-
} catch (e: Exception) {
196-
throw UnknownTaskException("Cannot find XCFramework assemble task. Tried ${taskWithName} and ${taskWithoutName}.")
197-
}
198-
}
199-
200192
private fun Project.configureDeploy() {
201193
val extension = extensions.getByType<KmmBridgeExtension>()
202194

@@ -248,3 +240,16 @@ class KMMBridgePlugin : Plugin<Project> {
248240
}
249241
}
250242

243+
internal fun Project.findXCFrameworkAssembleTask(buildType: NativeBuildType? = null): Task {
244+
val extension = extensions.getByType<KmmBridgeExtension>()
245+
val name = extension.frameworkName.get()
246+
val buildTypeString = (buildType ?: extension.buildType.get()).getName().capitalize()
247+
val taskWithoutName = "assemble${buildTypeString}XCFramework"
248+
val taskWithName = "assemble${name.capitalize()}${buildTypeString}XCFramework"
249+
return try {
250+
tasks.findByName(taskWithName) ?: tasks.findByPath(taskWithoutName)!!
251+
} catch (e: Exception) {
252+
throw UnknownTaskException("Cannot find XCFramework assemble task. Tried ${taskWithName} and ${taskWithoutName}.")
253+
}
254+
}
255+

kmmbridge/src/main/kotlin/ProjectExtensions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ internal val Project.githubRepo
2727
get() = (project.findStringProperty("GITHUB_REPO")
2828
?: throw IllegalArgumentException("KMM Bridge Github operations need a repo param or property GITHUB_REPO"))
2929

30+
internal val Project.spmBuildTargets: String?
31+
get() = project.findStringProperty("spmBuildTargets")
32+
3033
internal fun Project.zipFilePath(): File {
3134
val tempDir = file("$buildDir/faktory/zip")
3235
val artifactName = "frameworkarchive.zip"

kmmbridge/src/main/kotlin/SpmDependencyManager.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package co.touchlab.faktory
33
import co.touchlab.faktory.internal.procRunFailLog
44
import org.gradle.api.Project
55
import org.gradle.api.Task
6+
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
67
import java.io.ByteArrayOutputStream
78
import java.io.File
89
import java.nio.charset.Charset
@@ -39,6 +40,12 @@ class SpmDependencyManager(
3940

4041
updatePackageSwiftTask.dependsOn(uploadTask)
4142
publishRemoteTask.dependsOn(updatePackageSwiftTask)
43+
44+
project.task("spmDevBuild") {
45+
group = TASK_GROUP_NAME
46+
dependsOn(project.findXCFrameworkAssembleTask(NativeBuildType.DEBUG))
47+
project.writePackageFile(makeLocalDevPackageFileText(packageName, project))
48+
}
4249
}
4350

4451
private fun Project.writePackageFile(packageName: String, url: String, checksum: String){
@@ -90,6 +97,40 @@ internal fun stripEndSlash(path: String): String {
9097
}
9198
}
9299

100+
private fun makeLocalDevPackageFileText(packageName: String, project: Project): String {
101+
val extension = project.kmmBridgeExtension
102+
103+
val xcFrameworkPath = extension.xcFrameworkPath.getOrElse("${project.projectDir.name}/build/XCFrameworks/${NativeBuildType.DEBUG.getName()}")
104+
val packageFileString = """
105+
// swift-tools-version:5.3
106+
import PackageDescription
107+
108+
let packageName = "$packageName"
109+
110+
let package = Package(
111+
name: packageName,
112+
platforms: [
113+
.iOS(.v13)
114+
],
115+
products: [
116+
.library(
117+
name: packageName,
118+
targets: [packageName]
119+
),
120+
],
121+
targets: [
122+
.binaryTarget(
123+
name: packageName,
124+
path: "./${xcFrameworkPath}/\(packageName).xcframework"
125+
)
126+
,
127+
]
128+
)
129+
""".trimIndent()
130+
131+
return packageFileString
132+
}
133+
93134
private fun makePackageFileText(packageName: String, url:String, checksum: String): String = """
94135
// swift-tools-version:5.3
95136
import PackageDescription

0 commit comments

Comments
 (0)