Update BluetoothGATT trait and support Embedded Swift in GATTPeripheral#43
Merged
Conversation
The GATTServer trait previously gated the BluetoothGATT and BluetoothHCI dependencies, which removed the GATT client (GATTCentral) along with the server when disabled. Make those dependencies unconditional so the central is always available, and gate only GATTPeripheral and GATTServerConnection behind the trait's compilation condition. Guard the pure Swift central and peripheral against Embedded Swift and WASI explicitly, since the modules are now always importable there. Keep the code generation plugin enabled in the WASM CI job because BluetoothGATT now always builds.
The trait now controls whether the BluetoothGATT module (and BluetoothHCI) is imported. When enabled, the pure Swift GATT server (GATTPeripheral) is available and the central uses the BluetoothGATT types (GATTCharacteristicProperties, ATTAttributePermissions, ATTMaximumTransmissionUnit) via typealiases. When disabled, the lightweight shim types are used instead, for central-only clients built on a platform Bluetooth stack such as CoreBluetooth on iOS or the Android Bluetooth API. DarwinGATT's BluetoothGATT dependency is also gated on the trait so DarwinPeripheral is only built when the GATT module provides PeripheralManager. The WASM CI job disables plugins again since BluetoothGATT is no longer built with default traits disabled.
Gate the pure Swift GATT peripheral and central purely on canImport(BluetoothGATT)/canImport(BluetoothHCI), controlled by the BluetoothGATT trait.
Make the pure Swift GATT server compile for Embedded Swift targets (Pi Pico W, ESP32, nRF52840) running stacks like BTStack and NimBLE: - Add a non-blocking run() method that accepts pending connections and services I/O for connected centrals, so the peripheral can be driven by polling from the platform's run loop. - Drop the BluetoothHostControllerInterface constraint from the class; the async protocol cannot be implemented by synchronous embedded stacks like BTStack. The HCI-driven advertising APIs (async start() and start(options:)) move to a constrained extension, and re-advertising after a disconnect is now a stored callback installed by start(options:). - Replace NSLock with an internal Lock wrapper that is a no-op on platforms without threading. - Keep the background thread driver on platforms with Foundation threads; the blocking accept loop moved to acceptThread(_:) and shares the non-blocking accept(_:) implementation. - Capture the peripheral strongly in server callbacks on Embedded, where weak references are unavailable. - GATTCentral remains excluded from Embedded builds because GATTClient in BluetoothGATT is an actor and requires concurrency. - Build both trait configurations in the WebAssembly CI jobs.
colemancda
force-pushed
the
feature/swiftpm-traits
branch
from
July 18, 2026 15:07
95175ee to
7d0fda8
Compare
Code Coverage OverviewLanguages: Swift Swift / code-coverage/llvm-covThe overall coverage in the branch is 78%. The coverage in the branch is 81%. Show a code coverage summary of the most impacted files.
Updated |
Remove BluetoothGATT from the default trait set so GATT builds against the lightweight shim types unless the consumer opts in. This suits central-only clients on a platform Bluetooth stack (CoreBluetooth on iOS, the Android Bluetooth API) and keeps the default dependency footprint small. Gate the trait-conditional code on the BluetoothGATT compilation condition (which SwiftPM defines when the trait is enabled) instead of canImport(BluetoothGATT)/canImport(BluetoothHCI). canImport was a false positive: the module is discoverable on the dependency package's search path even when the trait leaves it unlinked, so the test executable failed to link once the trait was no longer enabled by default. The trait condition tracks the dependency exactly. CI now exercises both trait configurations: the macOS, Linux, Windows and WebAssembly jobs build (and where applicable test) with and without the trait, the coverage job enables the trait via SWIFT_BUILD_FLAGS to measure the pure Swift stack, and the ARM job keeps the trait on so the published libGATT.so still ships the full stack for BlueZ/BluetoothLinux. On Darwin the CoreBluetooth peripheral (DarwinPeripheral) now requires the trait; DarwinCentral and the shim types remain available by default.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Reworks the SwiftPM trait introduced in #42 and makes the pure Swift GATT server build for Embedded Swift targets so it can be driven by platform stacks like BTStack, NimBLE, and Zephyr.
Trait changes
GATTServertrait toBluetoothGATT: the trait now gates the import of theBluetoothGATTmodule (andBluetoothHCI) rather than individual source files.GATTPeripheral,PeripheralManager) is available and the central uses theBluetoothGATTtypes (GATTCharacteristicProperties,ATTAttributePermissions,ATTMaximumTransmissionUnit) via typealiases.DarwinGATT'sBluetoothGATTdependency is gated on the trait as well, soDarwinPeripheralis only built when the GATT module providesPeripheralManager.Embedded Swift support
GATTPeripheralandGATTServerConnectionnow compile under Embedded Swift:run()method that accepts pending connections and services I/O for connected centrals, so the peripheral can be driven by polling from the platform's run loop (e.g.btstack_run_loopon the Pi Pico W).BluetoothHostControllerInterfaceconstraint from the class, since the all-async HCI protocol cannot be implemented by synchronous embedded stacks. The HCI-driven advertising APIs (asyncstart()andstart(options:)) moved to a constrained extension, so desktop and Linux behavior is unchanged. Re-advertising after a disconnect is a stored callback installed bystart(options:).NSLockwith an internalLockwrapper that is a no-op on platforms without threading, and the server callbacks capture the peripheral strongly on Embedded, where weak references are unavailable.GATTCentralremains excluded from Embedded builds becauseGATTClientinBluetoothGATTis an actor and requires the concurrency runtime.CI
The WebAssembly and Embedded WebAssembly jobs now build both trait configurations.
Testing
DarwinPeripheralcompile to nothing with the trait disabled while the shim types are used.