diff --git a/Package.swift b/Package.swift index be0e8cb11..345b9e297 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,6 @@ import CompilerPluginSupport // get environment variables let dynamicLibrary = Context.environment["SWIFT_BUILD_DYNAMIC_LIBRARY"] == "1" -let buildMetadata = Context.environment["SWIFTPM_BLUETOOTH_METADATA"] != "0" let generateCode = Context.environment["SWIFTPM_ENABLE_PLUGINS"] != "0" let enableMacros = Context.environment["SWIFTPM_ENABLE_MACROS"] != "0" let buildDocs = Context.environment["BUILDING_FOR_DOCUMENTATION_GENERATION"] == "1" @@ -53,6 +52,23 @@ var package = Package( targets: ["BluetoothSDP"] ) ], + traits: [ + .trait( + name: "Metadata", + description: """ + Bluetooth SIG assigned-numbers metadata: the `name` \ + properties of `CompanyIdentifier`, `BluetoothUUID` and \ + `UnitIdentifier`. + + Opt-in because it pulls in Foundation and a resource \ + bundle, which makes the package neither Embedded-clean \ + nor buildable outside SwiftPM. The numeric definitions \ + themselves (`CompanyIdentifier.apple` and friends) are \ + generated at build time and remain available without it. + """ + ), + .default(enabledTraits: []) + ], targets: [ .target( name: "Bluetooth" @@ -100,7 +116,10 @@ var package = Package( "Bluetooth", .target( name: "BluetoothMetadata", - condition: .when(platforms: [.macOS, .linux, .macCatalyst, .windows]) + condition: .when( + platforms: [.macOS, .linux, .macCatalyst, .windows], + traits: ["Metadata"] + ) ), .target( name: "BluetoothGAP", @@ -124,11 +143,16 @@ var package = Package( ) // Optional dependencies -if buildMetadata { - package.targets[0].dependencies += [ - "BluetoothMetadata" - ] -} +// +// The metadata tables are reached only through `canImport`-guarded code +// in `Sources/Bluetooth`, so conditioning the dependency on the trait is +// enough to compile them out. +package.targets[0].dependencies += [ + .target( + name: "BluetoothMetadata", + condition: .when(traits: ["Metadata"]) + ) +] if embeddedFoundation { package.dependencies += [ diff --git a/Sources/Bluetooth/BluetoothUUID.swift b/Sources/Bluetooth/BluetoothUUID.swift index 3ffee2770..d62dc9238 100644 --- a/Sources/Bluetooth/BluetoothUUID.swift +++ b/Sources/Bluetooth/BluetoothUUID.swift @@ -56,7 +56,7 @@ extension BluetoothUUID: Equatable { extension BluetoothUUID: CustomStringConvertible { public var description: String { - #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) + #if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) if let name = self.metadata?.name { return "\(rawValue) (\(name))" } else { @@ -73,7 +73,7 @@ extension BluetoothUUID: CustomStringConvertible { extension BluetoothUUID: LosslessStringConvertible { public init?(_ string: String) { - #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) + #if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) var rawValue = string var name: String? // Find UUID name diff --git a/Sources/Bluetooth/BluetoothUUIDMetadata.swift b/Sources/Bluetooth/BluetoothUUIDMetadata.swift index 7fc5ad5a9..01b81186e 100644 --- a/Sources/Bluetooth/BluetoothUUIDMetadata.swift +++ b/Sources/Bluetooth/BluetoothUUIDMetadata.swift @@ -5,7 +5,7 @@ // Created by Alsey Coleman Miller on 1/12/25. // -#if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) +#if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) #if canImport(FoundationEssentials) import FoundationEssentials #else diff --git a/Sources/Bluetooth/CompanyIdentifier.swift b/Sources/Bluetooth/CompanyIdentifier.swift index 3da0fd8e8..7e5d0dd61 100644 --- a/Sources/Bluetooth/CompanyIdentifier.swift +++ b/Sources/Bluetooth/CompanyIdentifier.swift @@ -50,7 +50,7 @@ extension CompanyIdentifier: ExpressibleByIntegerLiteral { extension CompanyIdentifier: CustomStringConvertible { public var description: String { - #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) + #if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) return name ?? rawValue.description #else return rawValue.description diff --git a/Sources/Bluetooth/CompanyIdentifierMetadata.swift b/Sources/Bluetooth/CompanyIdentifierMetadata.swift index 8ccc236ee..d71f0ce91 100644 --- a/Sources/Bluetooth/CompanyIdentifierMetadata.swift +++ b/Sources/Bluetooth/CompanyIdentifierMetadata.swift @@ -5,7 +5,7 @@ // Created by Alsey Coleman Miller on 1/12/25. // -#if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) +#if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) #if canImport(FoundationEssentials) import FoundationEssentials #else diff --git a/Sources/Bluetooth/UnitIdentifier.swift b/Sources/Bluetooth/UnitIdentifier.swift index 45a0b60d9..a15bb2978 100644 --- a/Sources/Bluetooth/UnitIdentifier.swift +++ b/Sources/Bluetooth/UnitIdentifier.swift @@ -30,7 +30,7 @@ extension UnitIdentifier: ExpressibleByIntegerLiteral { extension UnitIdentifier: CustomStringConvertible { public var description: String { - #if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) + #if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) return self.name ?? rawValueDescription #else return rawValueDescription diff --git a/Sources/Bluetooth/UnitIdentifierMetadata.swift b/Sources/Bluetooth/UnitIdentifierMetadata.swift index df85fe2b7..1c0cd3c10 100644 --- a/Sources/Bluetooth/UnitIdentifierMetadata.swift +++ b/Sources/Bluetooth/UnitIdentifierMetadata.swift @@ -5,7 +5,7 @@ // Created by Alsey Coleman Miller on 1/12/25. // -#if canImport(Foundation) && canImport(BluetoothMetadata) && !os(WASI) && !hasFeature(Embedded) +#if Metadata && canImport(Foundation) && !os(WASI) && !hasFeature(Embedded) #if canImport(FoundationEssentials) import FoundationEssentials #else diff --git a/Tests/BluetoothTests/BluetoothTests.swift b/Tests/BluetoothTests/BluetoothTests.swift index ecb469aa1..4aa765cff 100755 --- a/Tests/BluetoothTests/BluetoothTests.swift +++ b/Tests/BluetoothTests/BluetoothTests.swift @@ -61,8 +61,12 @@ import BluetoothGATT let company: CompanyIdentifier = 76 // Apple, Inc. - #if !os(WASI) + // The description falls back to the raw value without the + // assigned-numbers metadata the Metadata trait gates. + #if Metadata && !os(WASI) #expect(company.description == "Apple, Inc.") + #else + #expect(company.description == "76") #endif #expect(company.hashValue != 0) #expect(company != 77) diff --git a/Tests/BluetoothTests/BluetoothUUIDTests.swift b/Tests/BluetoothTests/BluetoothUUIDTests.swift index 019b42d0e..3d5a36e1e 100644 --- a/Tests/BluetoothTests/BluetoothUUIDTests.swift +++ b/Tests/BluetoothTests/BluetoothUUIDTests.swift @@ -161,8 +161,14 @@ import Bluetooth #if !os(WASI) #expect(uuid == BluetoothUUID.Member.savantSystems2) + #endif + // The name, and the description that embeds it, come from the + // assigned-numbers metadata, which the Metadata trait gates. + #if Metadata && !os(WASI) #expect(uuid.metadata?.name == "Savant Systems LLC") #expect("\(uuid)" == "FEA9 (Savant Systems LLC)") + #else + #expect("\(uuid)" == uuidString) #endif #expect(uuid.rawValue == uuidString) #expect(uuid.hashValue != 0) @@ -198,7 +204,7 @@ import Bluetooth let uuid = try #require(BluetoothUUID(rawValue: uuidString)) - #if !os(WASI) + #if Metadata && !os(WASI) #expect(uuid.metadata == nil) #endif #expect(uuid.rawValue == uuidString) diff --git a/Tests/BluetoothTests/GAPTests.swift b/Tests/BluetoothTests/GAPTests.swift index 817dd0ab4..58abb0fb1 100644 --- a/Tests/BluetoothTests/GAPTests.swift +++ b/Tests/BluetoothTests/GAPTests.swift @@ -182,7 +182,13 @@ import Bluetooth let uuidList: GAPCompleteListOf16BitServiceClassUUIDs = [0x1803, 0x1804, 0x1802] let localName: GAPCompleteLocalName = "Proximity" + // The names come from the assigned-numbers metadata gated by + // the Metadata trait; without it the description is raw values. + #if Metadata #expect(uuidList.description == "[1803 (Link Loss), 1804 (Tx Power), 1802 (Immediate Alert)]") + #else + #expect(uuidList.description == "[1803, 1804, 1802]") + #endif let expectedData: [GAPData] = [flags, uuidList, localName] let types = expectedData.map { type(of: $0) } @@ -219,7 +225,13 @@ import Bluetooth let uuidList: GAPIncompleteListOf16BitServiceClassUUIDs = [0x1803, 0x1804, 0x1802] let localName: GAPCompleteLocalName = "Proximity" + // The names come from the assigned-numbers metadata gated by + // the Metadata trait; without it the description is raw values. + #if Metadata #expect(uuidList.description == "[1803 (Link Loss), 1804 (Tx Power), 1802 (Immediate Alert)]") + #else + #expect(uuidList.description == "[1803, 1804, 1802]") + #endif let expectedData: [GAPData] = [flags, uuidList, localName] let types = expectedData.map { type(of: $0) } diff --git a/Tests/BluetoothTests/GATTCharacteristicTests.swift b/Tests/BluetoothTests/GATTCharacteristicTests.swift index 7bae5ab7c..3eb47b289 100644 --- a/Tests/BluetoothTests/GATTCharacteristicTests.swift +++ b/Tests/BluetoothTests/GATTCharacteristicTests.swift @@ -130,9 +130,14 @@ import Bluetooth (101...UInt8.max).forEach { #expect(GATTBatteryLevel(data: Data([$0])) == nil) } // test percentage + // + // `name`, `type`, and the description that falls back to `name`, + // come from the assigned-numbers metadata gated by the Metadata trait. + #if Metadata #expect(GATTBatteryPercentage.unitType.description == "percentage") #expect(GATTBatteryPercentage.unitType.name == "percentage") #expect(GATTBatteryPercentage.unitType.type == "org.bluetooth.unit.percentage") + #endif #expect(GATTBatteryPercentage.unitType == .percentage) (0...100).forEach { #expect(GATTBatteryPercentage(rawValue: $0) != nil) } (101...UInt8.max).forEach { #expect(GATTBatteryPercentage(rawValue: $0) == nil) } diff --git a/Tests/BluetoothTests/HCITests.swift b/Tests/BluetoothTests/HCITests.swift index 00fe84bc0..f0e322e24 100644 --- a/Tests/BluetoothTests/HCITests.swift +++ b/Tests/BluetoothTests/HCITests.swift @@ -288,7 +288,13 @@ import Foundation #expect(localVersionInformation.lmpVersion == 0x08) #expect(localVersionInformation.lmpSubversion == 0x219A) #expect(localVersionInformation.manufacturer.rawValue == 0x000F) + // The description falls back to the raw value without the + // assigned-numbers metadata the Metadata trait gates. + #if Metadata #expect(localVersionInformation.manufacturer.description == "Broadcom Corporation") + #else + #expect(localVersionInformation.manufacturer.description == "15") + #endif } @Test func readDeviceAddress() async throws {