Skip to content

Commit 9b80ec0

Browse files
authored
Move Swift source code into KMP libraries via SKIE Bundling. (#1655)
This change relocates all Swift source code directly into the respective Kotlin modules, leveraging SKIE's [Swift Code Bundling](https://skie.touchlab.co/features/swift-code-bundling) feature. This allows us to distribute the logic within a single framework. Having the code in a single framework enables downstream projects to build their own "umbrella" XCFramework that encapsulates both their custom Kotlin code and the Multipaz core. Crucially, this prevents the Framework Boundary (or "Diamond Dependency") problem caused by Kotlin/Native's closed-world compilation model. If a downstream iOS app previously depended on a standalone Multipaz SPM package while also using a local KMP shared module that depended on Multipaz, Xcode would generate two isolated Kotlin runtimes. This resulted in fatal type mismatches across the Objective-C headers, where types from the local module (e.g., `Shared.Document`) were entirely incompatible with types from the SPM package (e.g., `Multipaz.Document`). By allowing downstream projects to compile a single umbrella framework, SKIE extracts and compiles our Swift extensions against that unified header, ensuring perfect type resolution and a single runtime. Test: Manually tested TestApp, Swift TestApp, and 3rd party consumer. Signed-off-by: David Zeuthen <zeuthen@google.com>
1 parent 49ff6c0 commit 9b80ec0

45 files changed

Lines changed: 246 additions & 142 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Package.swift

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,9 @@ let package = Package(
77
.iOS(.v26),
88
],
99
products: [
10-
.library(name: "Multipaz", targets: ["MultipazSwift"]),
11-
.library(name: "MultipazCore", targets: ["Multipaz"])
10+
.library(name: "Multipaz", targets: ["Multipaz"]),
1211
],
1312
targets: [
14-
.target(
15-
name: "MultipazSwift",
16-
dependencies: ["Multipaz"],
17-
path: "multipaz-swift/Sources/MultipazSwift",
18-
resources: [
19-
.process("Resources/default_card_art.png")
20-
]
21-
),
2213
.binaryTarget(
2314
name: "Multipaz",
2415
path: "xcframework/build/XCFrameworks/release/Multipaz.xcframework"

README.md

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ The project provides libraries written in [Kotlin Multiplatform](https://kotlinl
2626
- `multipaz-longfellow` bundles the [Google Longfellow-ZK](https://github.com/google/longfellow-zk) library
2727
and integrates with the core `multipaz` for Zero-Knowledge Proofs
2828
according to latest available [ISO/IEC 18013-5 Second Edition draft](https://github.com/ISOWG10/ISO-18013).
29-
- `multipaz-swift` contains Swift and [SwiftUI](https://developer.apple.com/swiftui/)
30-
functionality to easier use Multipaz in Swift applications on iOS.
29+
- `multipaz-swiftui` contains SwiftUI components which can be used in
30+
Swift applications.
3131

3232
## Command-line tool
3333

@@ -62,16 +62,9 @@ late 2026 or early 2027.
6262

6363
We are also making Multipaz available as a [Swift package](https://github.com/openwallet-foundation/multipaz/blob/main/Package.swift)
6464
which includes an the `multipaz`, `multipaz-doctypes`, `multipaz-doctypes`,
65-
`multipaz-longfellow`, and `multipaz-swift` libraries. This is built using
66-
[SKIE](https://skie.touchlab.co/). Be careful relying on this as Swift/Kotlin
67-
interop technology might change in the near future.
68-
69-
The Swift package exposes two products:
70-
71-
- `Multipaz` includes the higher-level `multipaz-swift` wrapper APIs.
72-
- `MultipazCore` exposes the core binary target directly for applications that
73-
build their own UI and only need the lower-level APIs. When using this
74-
product, the module import remains `import Multipaz`.
65+
`multipaz-longfellow`, and `multipaz-swiftui` libraries. This is built using
66+
[SKIE](https://skie.touchlab.co/). Be careful relying on this as Swift/Kotlin interop technology
67+
might change in the near future with e.g. [Swift Export](https://kotlinlang.org/docs/native-swift-export.html).
7568

7669
At this point both API interfaces and data stored on disk is subject to change
7770
but we expect to provide stability guarantees post 1.0. We only expect minor changes
@@ -100,12 +93,16 @@ developers and as such has a lot of options and settings. It's intended to
10093
exercise all code in the libraries. Prebuilt APKs are available from
10194
https://apps.multipaz.org.
10295

96+
For a SwiftUI version of `samples/testapp` see `samples/SwiftTestApp` which works
97+
on iOS and is a testbed for the Swift bindings as well as the SwiftUI components
98+
in `multipaz-swiftui`.
99+
103100
For a fully-featured proximity reader app using Multipaz, see
104101
[MpzIdentityReader](https://github.com/openwallet-foundation/multipaz-identity-reader/).
105102
Prebuilt APKs are available from https://apps.multipaz.org.
106103

107104
For an over-the-Internet verifier supporting OpenID4VP (both W3C DC API and
108-
URI schemes) and ISO/IEC 18013-7 Annex C see https://verifier.multipaz.org.
105+
URI schemes) and ISO/IEC 18013-7 Annex A and C see https://verifier.multipaz.org.
109106

110107
To see how to use the Multipaz in a 3rd party project, see
111108
https://github.com/openwallet-foundation/multipaz-samples/ which includes

multipaz-swift/Sources/MultipazSwift/DocumentExt.swift

Lines changed: 0 additions & 75 deletions
This file was deleted.
Binary file not shown.

multipaz-swiftui/build.gradle.kts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
@file:OptIn(ExperimentalWasmDsl::class)
2+
3+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
4+
plugins {
5+
alias(libs.plugins.kotlinMultiplatform)
6+
alias(libs.plugins.skie)
7+
id("maven-publish")
8+
id("org.jetbrains.dokka") version "2.1.0"
9+
}
10+
11+
val projectVersionCode: Int by rootProject.extra
12+
val projectVersionName: String by rootProject.extra
13+
14+
val disableWebTargets = project.properties["disable.web.targets"]?.toString()?.toBoolean() ?: false
15+
16+
kotlin {
17+
jvmToolchain(17)
18+
19+
compilerOptions {
20+
optIn.add("kotlin.time.ExperimentalTime")
21+
}
22+
23+
jvm()
24+
25+
if (!disableWebTargets) {
26+
js {
27+
outputModuleName = "multipaz-swiftui"
28+
browser {
29+
}
30+
binaries.executable()
31+
}
32+
33+
wasmJs {
34+
outputModuleName = "multipaz-swiftui"
35+
browser {
36+
}
37+
binaries.executable()
38+
}
39+
}
40+
41+
listOf(
42+
iosX64(),
43+
iosArm64(),
44+
iosSimulatorArm64()
45+
).forEach {
46+
val platform = when (it.name) {
47+
"iosX64" -> "iphonesimulator"
48+
"iosArm64" -> "iphoneos"
49+
"iosSimulatorArm64" -> "iphonesimulator"
50+
else -> error("Unsupported target ${it.name}")
51+
}
52+
it.binaries.all {
53+
linkerOpts(
54+
"-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/${platform}/",
55+
"-Wl,-rpath,/usr/lib/swift"
56+
)
57+
}
58+
}
59+
60+
sourceSets {
61+
val commonMain by getting {
62+
dependencies {
63+
implementation(project(":multipaz"))
64+
implementation(libs.kotlinx.datetime)
65+
implementation(libs.kotlinx.io.bytestring)
66+
implementation(libs.kotlinx.serialization.json)
67+
api(project(":multipaz"))
68+
}
69+
}
70+
71+
val commonTest by getting {
72+
dependencies {
73+
implementation(libs.kotlin.test)
74+
implementation(libs.kotlinx.coroutines.test)
75+
}
76+
}
77+
78+
val jvmMain by getting {
79+
dependencies {
80+
}
81+
}
82+
83+
val jvmTest by getting {
84+
dependencies {
85+
}
86+
}
87+
88+
if (!disableWebTargets) {
89+
val jsTest by getting {
90+
dependencies {
91+
implementation(libs.kotlin.wrappers.web)
92+
}
93+
}
94+
}
95+
}
96+
}
97+
98+
group = "org.multipaz"
99+
version = projectVersionName
100+
101+
publishing {
102+
repositories {
103+
maven {
104+
url = uri(rootProject.layout.buildDirectory.dir("staging-repo"))
105+
}
106+
}
107+
publications.withType(MavenPublication::class) {
108+
pom {
109+
name.set("multipaz-swiftui")
110+
description.set("Multipaz SDK SwiftUI module")
111+
url.set("https://github.com/openwallet-foundation/multipaz")
112+
licenses {
113+
license {
114+
name.set("Apache-2.0")
115+
url.set("https://opensource.org/licenses/Apache-2.0")
116+
distribution.set("repo")
117+
}
118+
}
119+
developers {
120+
developer {
121+
id.set("zeuthen")
122+
name.set("David Zeuthen")
123+
email.set("zeuthen@google.com")
124+
}
125+
}
126+
scm {
127+
connection.set("scm:git:git://github.com/openwallet-foundation/multipaz.git")
128+
developerConnection.set("scm:git:ssh://github.com/openwallet-foundation/multipaz.git")
129+
url.set("https://github.com/openwallet-foundation/multipaz")
130+
}
131+
}
132+
}
133+
}
134+

multipaz-swiftui/src/commonMain/kotlin/org/multipaz/Multipaz.kt

Whitespace-only changes.

multipaz-swift/Sources/MultipazSwift/Consent.swift renamed to multipaz-swiftui/src/iosMain/swift/Consent.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Multipaz
21
import SwiftUI
32

43
func getIconName(claim: Claim) -> String {

multipaz-swift/Sources/MultipazSwift/ConsentPromptDialog.swift renamed to multipaz-swiftui/src/iosMain/swift/ConsentPromptDialog.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import SwiftUI
2-
@preconcurrency import Multipaz
32

43
private struct ConsentPromptDialogData: Identifiable, Equatable {
54
let id = UUID()

multipaz-swift/Sources/MultipazSwift/DocumentCarousel.swift renamed to multipaz-swiftui/src/iosMain/swift/DocumentCarousel.swift

File renamed without changes.

multipaz-swiftui/src/iosMain/swift/DocumentExtCardart.swift

Lines changed: 53 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)