Add the package
Add DroppyKit to your droplet with Swift Package Manager.
A droplet is a Swift package that builds a dynamic library and links DroppyKit. Droppy loads the built bundle into its own process, so the library type matters: a static library has nothing for the loader to open.
Requirements
The droppykit tool
droppykit new, run, build, validate, submit, docs, agent, mcp, version and update live in the SDK repository's Scripts folder, not in the package your droplet depends on: SwiftPM checks that out for the compiler, not for you. Clone the repository once and put the folder on your PATH.
git clone https://gitlab.com/droppyformac1/droppykit.git
export PATH="$PWD/droppykit/Scripts:$PATH"
droppykitAdd the export line to your shell profile to keep it. Running droppykit with no command prints the list above.
droppykit version # which SDK this is, which one the PATH finds, what a droplet pins
droppykit update # pull the newest SDK; inside a droplet, move its pin too
droppykit update --raise # and require the new SDK: Package.swift from: and kit.minAPITwo SDK versions are in play for every droplet: the checkout whose scripts build the bundle, and the tag the droplet's Package.swift pins, which is what its compile and its harness use. They drift apart when the checkout is pulled and the pin is not; droppykit update run inside the droplet moves both, and droppykit version shows both.
droppykit update does not raise what the droplet declares it needs, from: in Package.swift and kit.minAPI in droplet.json: a droplet that uses nothing new keeps loading in older Droppys. When it starts using something the new release added, droppykit update --raise requires that release in both. droppykit build checks the other direction: it reads which DroppyKit API the built droplet calls and fails when kit.minAPI is older than the newest of them, naming those calls and the version to declare. Declared too low, a droplet passes the handshake on an older Droppy and then fails to load there with "Symbol not found".
In Package.swift
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "WorldClock",
platforms: [.macOS(.v14)],
products: [
.library(name: "WorldClock", type: .dynamic, targets: ["WorldClock"])
],
dependencies: [
.package(url: "https://gitlab.com/droppyformac1/droppykit.git", from: "1.6.0")
],
targets: [
.target(
name: "WorldClock",
dependencies: [.product(name: "DroppyKit", package: "droppykit")]
),
.executableTarget(
name: "WorldClockHarness",
dependencies: [
"WorldClock",
.product(name: "DroppyKitHarness", package: "droppykit")
]
)
]
)The second target is the development harness. It is not shipped in your droplet — see The harness.
In Xcode
File ▸ Add Package Dependencies, then paste https://gitlab.com/droppyformac1/droppykit.git. Add DroppyKit to your droplet target and DroppyKitHarness to the harness target.
Do not build with a bare swift build
Important. Always build the shipping bundle with Scripts/build-droplet.sh.
A shipped droplet must contain its own code and not a copy of DroppyKit. Droppy already has DroppyKit as a resilient framework; a droplet carrying a second copy gives every shared type two metadata records, and the cast the loader makes between Droppy's Droplet and yours fails with no useful diagnostic. A plain swift build produces exactly that second copy, because SwiftPM folds the dependency in statically.
droppykit build does the link itself: it builds DroppyKit resiliently, wraps it in a framework whose install name is the one Droppy ships, and links your target's object file against that. The bundle then carries an undefined reference Droppy resolves at load.
It also compiles with -enable-library-evolution. Without it a droplet emits direct-access imports the resilient framework does not export, and the bundle passes every structural check, loads in the harness, and dies inside Droppy at dyld with Symbol not found.
Scripts/validate-droplet.sh refuses a bundle carrying either wrong shape, so a mistake here is caught before submission rather than after install.
Checking the link
# 0 in a correct bundle, non-zero in a broken one.
nm -u WorldClock.droplet/Contents/MacOS/WorldClock | grep -c '9DroppyKit.*vau'
# Must name the framework, and must not name libDroppyKit.
otool -L WorldClock.droplet/Contents/MacOS/WorldClock | grep DroppyKit