SwifterKit wraps DriverKit for Swift 6 driver authors. Driver configuration and behavior stay in Swift; SwifterKit owns the generated C++/IIG boundary required by the DriverKit ABI.
See the SwifterKit 0.2.0 release notes for the breaking release summary and hardware validation status.
Use SwifterKit when a driver needs:
- a generated DriverKit extension project without application-owned C++, C, Objective-C, or IIG glue;
- typed commands and events for supported DriverKit families;
- raw user-client access when no capability-specific API fits; or
- one Swift concurrency model for driver lifecycle, events, and completion work.
- Swift 6.1 (Xcode 16.3) or later for package consumers
- the Swift version in
.swift-versionfor local development - macOS 10.15 or later for the IOKit host transport and base DriverKit runtime; configuration, validation, protocol, and generation code also builds on Linux, where the IOKit transport is unavailable
- Xcode with the DriverKit SDK for generated extension builds
- Apple-approved entitlements, signing assets, a host application, and physical hardware for deployment testing
Signing is not required to build or test the package or its generated extension project locally.
DriverKit family frameworks were added across later releases.
DriverExtensionGenerationOptions.deploymentTarget defaults to 19.0; the
generator rejects a capability when its native runtime needs a newer DriverKit version.
Build a 19.0 or 20.x target with an Xcode whose DriverKit SDK still accepts
it, such as Xcode 16.3 (DriverKit 24.4 accepts 19.0 and later). The DriverKit
SDK in Xcode 27 accepts deployment targets from 21.0.
The package target and the generated extension target are independent. Swift package clients can run on macOS 10.15 while a generated extension selects the narrowest DriverKit target required by its capabilities.
| Generated capability | Minimum DriverKit target | Host availability |
|---|---|---|
| Base runtime, HID device, USB HID device, USB, serial, interrupts, memory | 19.0 | macOS 10.15 |
| PCI | 19.0 | macOS 11.1 |
| SCSI controller | 20.4 | macOS 11.3 |
| Block storage, audio, HID event service | 21.0 | macOS 12 |
| Networking, SCSI peripheral | 22.0 | macOS 13 |
| MIDI | 24.0 | macOS 15 |
| Video | 25.5 | macOS 26.5 |
The networking runtime uses queue registration introduced in DriverKit 22.0 even though NetworkingDriverKit itself appeared earlier. Apple currently marks VideoDriverKit as beta; video generation requires an SDK containing that framework. The DriverKit 25.5 SDK in Xcode 26.6, which pairs with the macOS 26.5 SDK, is the oldest SDK known to contain it: its VideoDriverKit headers and exported symbols match the DriverKit 27.0 SDK's, and the DriverKit 24.4 SDK has no VideoDriverKit. See DriverKit and the family framework documentation for Apple’s platform availability.
For a local checkout, add SwifterKit to Package.swift:
let package = Package(
dependencies: [
.package(path: "../SwifterKit")
],
targets: [
.target(
name: "MyDriver",
dependencies: [
.product(name: "SwifterKit", package: "SwifterKit")
]
)
]
)Use a repository URL and a tagged version after publishing SwifterKit through a Swift package host.
A SwiftDriver declares static extension metadata and handles runtime work in Swift:
import SwifterKit
struct ExampleHIDDriver: SwiftDriver {
static let configuration = DriverConfiguration(
bundleIdentifier: "com.example.ExampleHID",
providerClass: "IOUserResources",
matchingProperties: ["IOResourceMatch": .string("IOKit")],
capabilities: .hid,
hidDevice: HIDDeviceConfiguration(
reportDescriptor: [
0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x00,
0x75, 0x08, 0x95, 0x0F, 0x09, 0x02, 0x91, 0x02, 0x09, 0x03, 0x81, 0x02,
0xC0,
],
vendorID: 0x1234,
productID: 0x5678,
manufacturer: "Example",
product: "Swift HID",
serialNumber: "swift-hid-1",
primaryUsagePage: 0xFF00,
primaryUsage: 1,
acceptedHostReportTypes: .output
)
)
func start(context: DriverContext) async throws {
try await context.submitHIDInputReport(
HIDReport(bytes: [0], type: .input)
)
}
func handle(event: DriverEvent, context: DriverContext) async throws {
guard let report = try event.hidReport() else { return }
// Handle the allowlisted output report here.
_ = report
}
}Generate the internal extension project from the same configuration:
import Foundation
import SwifterKit
let outputDirectory = URL(fileURLWithPath: "/tmp/ExampleHID")
try DriverExtensionGenerator.generate(
configuration: ExampleHIDDriver.configuration,
at: outputDirectory
)The generator writes the personality, entitlements, runtime configuration, IIG declarations, native sources, and Xcode project. Generation rejects unsupported capability combinations and never overwrites an existing destination.
HIDDeviceConfiguration accepts both output and feature reports by default.
The example selects acceptedHostReportTypes: .output because its descriptor
supports only output reports. Rejected report types return
kIOReturnUnsupported without being forwarded to the Swift host.
| Capability | Configuration | Swift operations |
|---|---|---|
| HID | HIDDeviceConfiguration |
Input reports; allowlisted output and feature events |
| USB | USBDeviceConfiguration |
Interface or device providers; synchronous and asynchronous control transfers, synchronous and asynchronous endpoint I/O, bundled bulk I/O over descriptor rings, isochronous I/O, endpoint bandwidth adjustment, descriptors, configuration, frame numbers, idle policy, aborts |
| PCI | PCIDeviceConfiguration, PCIInterruptConfiguration |
Configuration space, bounded BAR access with access options, device location, capability search, MSI/MSI-X allocation, reset, state save/restore, power management, link speed, ASPM, sleep properties |
| Serial | SerialPortConfiguration |
Queue I/O, modem state, receive errors, UART events |
| USB serial | USBSerialPortConfiguration |
IOUserUSBSerial on a USB interface; UART events, modem state, receive errors, received and interrupt packet events |
| Block storage | BlockStorageDeviceConfiguration |
Eject, synchronize, unmap, read/write requests and completions |
| MIDI | MIDIDeviceConfiguration |
Endpoint topology, Universal MIDI Packet sends, destination events, object identity and names, typed property get, set, and copy, device running state, adding and removing the entity, sources, and destinations |
| Networking | EthernetDeviceConfiguration |
Packet queues and pools, transmit completion, receive injection, link status, quality, and bandwidths, offloads (checksum, TSO, LRO, VLAN, wake on magic packet, NIC proxy), MTU range, hardware counters, BPF tap, hybrid polling, per-packet metadata in both directions (offsets, checksum/TSO/LRO, VLAN, timestamps, service class, trace IDs), batched receive and completion, queue enable/purge/service, private interface commands |
| Audio | AudioDeviceConfiguration |
Stream rings, timestamps, formats, controls, custom properties, boxes and acquisition, clock devices (sample rates, clock domain and algorithm, latency, zero timestamps), object and element names, property-change notifications, StartDevice/StopDevice events, device defaults, safety offsets, preferred channels and layouts, stream state, formats, terminal type, and ring resizing, control ranges, panning channels, and selector items, adding and removing streams, controls, and custom properties |
| SCSI | SCSIControllerConfiguration or SCSIPeripheralConfiguration |
Parallel tasks, task management, CDBs, logical-unit services |
| Video | VideoDeviceConfiguration |
Formats, controls, buffers, queues, timestamps, stream events, boxes, clock devices, device defaults, safety offsets, preferred channels and layouts, stream state and queues, buffer identity, capacity, IDs, and membership, control scope, owner, ranges, channels, and selector items, custom-property info, stream and control attachment |
| Interrupts | InterruptSourceConfiguration |
Delivery control, interrupt metadata, typed events |
| Memory and DMA | MemoryPoolConfiguration |
Bounded buffers, valid lengths, provider mappings, DMA lifecycle |
| Service (every extension) | None | Registry properties, provider properties, name and registry ID, power-state requests and changes, power override, PM assertions, busy state, bus-stall limits, termination, system state items, CoreAnalytics events |
| Timers and watches (every extension) | None | One-shot and repeating timers with leeway, service match and termination events, system state item changes |
| IOReporting (every extension) | ReportingConfiguration |
Simple, state, and histogram reporters with a published legend; value and state updates and reads |
A generated extension advertises only the capabilities implemented by its native runtime. Some device-family combinations are invalid because DriverKit requires different superclasses or providers.
See Capability APIs for configuration and completion details.
coverage/driverkit.json lists every class and member function declared in
the DriverKit SDK .iig headers, with the SDK versions that declare it, the
introduced and deprecated DriverKit versions when the header's availability
attribute gives them, and how SwifterKit handles it:
| Status | Meaning |
|---|---|
gap |
Not yet reachable from Swift |
generated |
Called or overridden by the generated extension runtime |
swift-api |
Exposed through the typed Swift API named in swiftSymbol |
fast-path |
Declarable through the native fast path |
excluded |
Out of scope, with the reason in note |
Members start as gaps. The tool excludes private members, private EXTENDS
class extensions, init/free lifecycle hooks, and declarations compiled only
for the kernel or under #if 0 or private conditions. generated entries were
inferred from the runtime naming the class and the member, and overloaded names
were left as gaps; confirm an entry before relying on it.
Print the current counts per framework:
swift run SwifterKitCoverage summary --manifest coverage/driverkit.jsonCI checks the manifest against the DriverKit SDK in each job and fails when the
SDK declares a member the manifest does not list, or when a generated,
swift-api, fast-path, or excluded entry lacks supporting source or a
note. After installing a new SDK, record its surface:
swift run SwifterKitCoverage update --manifest coverage/driverkit.json \
--sdk "$(xcrun --sdk driverkit --show-sdk-path)"DriverExtensionGenerator copies a single packaged native source tree from
Sources/SwifterKit/Resources/DriverKitExtension. Driver authors do not supply
native glue to the generator.
The generated extension and the Swift host exchange versioned runtime messages.
Swift receives typed values, opaque memory handles, and bounded payloads rather
than DriverKit objects or native pointers. The extension notifies the host when
events are queued, and DriverHost.runEvents() delivers them without polling. Ethernet, block-storage, SCSI, and
other completion-based APIs require Swift to return the matching request
identifier after transport work finishes.
DriverClient, DriverSession, and DriverCommand expose raw user-client
calls for operations that do not belong to a typed capability API.
Read NativeBoundary before adding raw commands or memory operations.
Run the complete validation suite:
./scripts/ci/validate.shThe suite checks Swift and C++ formatting, SwiftLint, Swift 6 tests, a release build with warnings as errors, DocC links, C++20 static analysis, unsigned arm64 and x86_64 DriverKit builds, property lists, and source LOC limits.
The compatibility job runs the tests with Xcode 16.3, Swift 6.1, and the
DriverKit 24.4 SDK while retaining the macOS 10.15 deployment target. A Linux
job runs the tests in the swift:6.1 container. The main validation job uses
the latest passing local toolchain.
Tests that build a generated extension use the DriverKit SDK in
SWIFTERKIT_DRIVERKIT_DEVELOPER_DIR, or DEVELOPER_DIR when that is unset.
Point it at Xcode 16.3 to build the default DriverKit 19.0 target unchanged.
validate.sh builds the checked-in native project, which links every family
framework including VideoDriverKit, with DEVELOPER_DIR. With a newer SDK the
build's deployment target is raised to the SDK minimum (the Xcode 27 SDK starts
at DriverKit 21.0). Build tests for projects that need a newer SDK, such as
video (DriverKit 25.5), report as skipped on older SDKs; the Xcode 26.6 and
Xcode 27 SDKs build video. Set SWIFTERKIT_REQUIRE_DRIVERKIT=1 to fail
the run when no SDK is installed. Unset TOOLCHAINS when it selects a
swift.org toolchain, because the generated-project builds must use Xcode's
compilers.
CI does not run on macOS 10.15 and cannot activate a signed extension or attach physical hardware. The following macOS 10.15 runtime paths remain compile-checked but unexecuted in CI:
- IOKit service discovery and user-client opening through
kIOMasterPortDefault; - Swift concurrency back-deployment during the driver lifecycle and event loop;
- event notifications through
IOConnectCallAsyncStructMethod, the IOKit notification port, and the extension'sAsyncCompletioncalls; - generated extension activation, runtime negotiation, and device I/O on a macOS 10.15 host; and
- signed entitlement, provisioning, and hardware behavior.
SwifterKit 0.2.1 has not been run on physical hardware. This includes fast-path programs, DMA rings, host-shared data queues, wrapped or mapped host memory, and device I/O for every capability family: HID, USB and USB serial, PCI, serial, block storage, MIDI, Ethernet networking, audio, SCSI, video, interrupts, memory and DMA, service operations, timers and watches, and IOReporting.
Unit tests model the extension's event notifications with mock connections. IOKit port selection depends on the running operating system and needs a macOS 10.15 host for runtime coverage.
For a smaller Swift-only cycle:
swift test -Xswiftc -warnings-as-errors
swiftlint lint --strict
xcrun swift-format lint --strict --recursive Sources Tests Package.swiftFormat edited source before submitting a change:
xcrun swift-format format --in-place --recursive Sources Tests Package.swift
xcrun clang-format -i Sources/SwifterKit/Resources/DriverKitExtension/Sources/*.{cpp,h,iig}Source and test files must remain below 800 lines after formatting; aim for
500. Directories below Tests/SwifterKitTests mirror the matching Sources/ SwifterKit areas.
Generation reports that the destination exists. DriverExtensionGenerator
does not overwrite files. Choose a new directory or remove the old output after
confirming it is disposable.
A capability configuration is rejected. Check that RuntimeCapabilities,
the matching configuration value, and the DriverKit provider class describe the
same device family. Some families cannot share one generated superclass.
The DriverKit SDK cannot be found. Select an Xcode installation that
includes DriverKit. CI sets DEVELOPER_DIR explicitly; local commands can do
the same when multiple Xcode versions are installed.
A signed build fails. Confirm that the certificate, private key, provisioning profile, team identifier, bundle identifier, and approved entitlements agree. See Publishing for the local and GitHub Actions setup.
A source release does not need Apple signing. A downstream driver needs approved DriverKit entitlements, matching certificates and provisioning profiles, a host application, and the target device environment.
The host application needs the com.apple.developer.driverkit.userclient-access
entitlement, and its array must contain the extension's bundle identifier.
Every generated extension rejects runtime connections from processes without
it. See The native DriverKit boundary.
Publishing covers release tags, GitHub Actions, local . env files, signed validation, and Apple account requirements.
- Getting started with a Swift driver
- Capability APIs
- The native DriverKit boundary
- Publishing
- Changelog
Read CONTRIBUTING.md before opening a change.
Read AGENTS.md after this README. CLAUDE.md and GEMINI.md
point to the same repository guidance.