From 26fa7e3c556c6173020f5d4f148228636d40b0eb Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 10 Jan 2025 03:41:27 +0000 Subject: [PATCH 1/8] Updated unit tests --- Tests/BluetoothTests/AddressTests.swift | 80 +++++++++++++------------ 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/Tests/BluetoothTests/AddressTests.swift b/Tests/BluetoothTests/AddressTests.swift index 50ba4a251..4fe239a1c 100644 --- a/Tests/BluetoothTests/AddressTests.swift +++ b/Tests/BluetoothTests/AddressTests.swift @@ -6,30 +6,31 @@ // Copyright © 2018 PureSwift. All rights reserved. // -import XCTest import Foundation +import Testing @testable import Bluetooth -final class AddressTests: XCTestCase { +@Suite("Bluetooth Address Tests") +struct BluetoothAddressTests { - func testProperties() { + @Test func properties() throws { - XCTAssertEqual(BluetoothAddress.bitWidth, MemoryLayout.size * 8) - XCTAssertEqual(BluetoothAddress.bitWidth, 48) + #expect(BluetoothAddress.bitWidth == MemoryLayout.size * 8) + #expect(BluetoothAddress.bitWidth == 48) - Data(BluetoothAddress.min).forEach { XCTAssertEqual($0, .min) } - Data(BluetoothAddress.max).forEach { XCTAssertEqual($0, .max) } - Data(BluetoothAddress.zero).forEach { XCTAssertEqual($0, 0) } - XCTAssertEqual(BluetoothAddress.zero, BluetoothAddress.min) + Data(BluetoothAddress.min).forEach { #expect($0 == .min) } + Data(BluetoothAddress.max).forEach { #expect($0 == .max) } + Data(BluetoothAddress.zero).forEach { #expect($0 == 0) } + #expect(BluetoothAddress.zero == BluetoothAddress.min) - guard let address = BluetoothAddress(rawValue: "00:1A:7D:DA:71:13") - else { XCTFail("Could not parse"); return } + let address = try #require(BluetoothAddress(rawValue: "00:1A:7D:DA:71:13")) - XCTAssertNotEqual(address.hashValue, 0) - XCTAssertEqual(address.description, "00:1A:7D:DA:71:13") + #expect(address.hashValue != 0) + #expect(address.description == "00:1A:7D:DA:71:13") + #expect(address == #BluetoothAddress("00:1A:7D:DA:71:13")) } - func testBytes() { + @Test func bytes() throws { let testData: [(rawValue: String, bytes: BluetoothAddress.ByteValue)] = [ ("00:1A:7D:DA:71:13", (0x00, 0x1A, 0x7D, 0xDA, 0x71, 0x13)), @@ -39,22 +40,22 @@ final class AddressTests: XCTestCase { for test in testData { - guard let address = BluetoothAddress(rawValue: test.rawValue) - else { XCTFail("Could not parse"); continue } + let address = try #require(BluetoothAddress(rawValue: test.rawValue)) - XCTAssertEqual(address.rawValue, test.rawValue) - XCTAssertEqual(address, BluetoothAddress(bigEndian: BluetoothAddress(bytes: test.bytes))) + #expect(address.rawValue == test.rawValue) + #expect(address == BluetoothAddress(bigEndian: BluetoothAddress(bytes: test.bytes))) - XCTAssertEqual(address.bigEndian.bytes.0, test.bytes.0) - XCTAssertEqual(address.bigEndian.bytes.1, test.bytes.1) - XCTAssertEqual(address.bigEndian.bytes.2, test.bytes.2) - XCTAssertEqual(address.bigEndian.bytes.3, test.bytes.3) - XCTAssertEqual(address.bigEndian.bytes.4, test.bytes.4) - XCTAssertEqual(address.bigEndian.bytes.5, test.bytes.5) + #expect(address.bigEndian.bytes.0 == test.bytes.0) + #expect(address.bigEndian.bytes.1 == test.bytes.1) + #expect(address.bigEndian.bytes.2 == test.bytes.2) + #expect(address.bigEndian.bytes.3 == test.bytes.3) + #expect(address.bigEndian.bytes.4 == test.bytes.4) + #expect(address.bigEndian.bytes.5 == test.bytes.5) } } - func testMalformedString() { + @Test func malformedString() throws { + let malformed = [ "0", @@ -79,21 +80,25 @@ final class AddressTests: XCTestCase { "00:1A:7D:DA:71;13" ] - malformed.forEach { XCTAssertNil(BluetoothAddress(rawValue: $0), $0) } + for string in malformed { + #expect(BluetoothAddress(rawValue: string) == nil) + } } - func testString() { + func validString() { let rawValues = [ "00:1A:7D:DA:71:13", "59:80:ED:81:EE:35", "AC:BC:32:A6:67:42" ] - - rawValues.forEach { XCTAssertEqual(BluetoothAddress(rawValue: $0)?.rawValue, $0) } + + for string in rawValues { + #expect(BluetoothAddress(rawValue: string)?.rawValue == string) + } } - func testData() { + func validData() throws { let testData: [(rawValue: String, data: Data)] = [ ("00:1A:7D:DA:71:13", Data([0x00, 0x1A, 0x7D, 0xDA, 0x71, 0x13])), @@ -103,16 +108,15 @@ final class AddressTests: XCTestCase { for test in testData { - guard let address = BluetoothAddress(rawValue: test.rawValue) - else { XCTFail("Could not parse"); continue } + let address = try #require(BluetoothAddress(rawValue: test.rawValue)) - XCTAssertEqual(address.rawValue, test.rawValue) - XCTAssertEqual(address, BluetoothAddress(bigEndian: BluetoothAddress(data: test.data)!)) - XCTAssertEqual(Data(address.bigEndian), test.data) + #expect(address.rawValue == test.rawValue) + #expect(address == BluetoothAddress(bigEndian: BluetoothAddress(data: test.data)!)) + #expect(Data(address.bigEndian) == test.data) } } - func testMalformedData() { + func malformedData() { let malformed = [ Data(), @@ -124,6 +128,8 @@ final class AddressTests: XCTestCase { Data([0x00, 0x1A, 0x7D, 0xDA, 0x71, 0x13, 0xAA]) ] - malformed.forEach { XCTAssertNil(BluetoothAddress(data: $0)) } + for data in malformed { + #expect(BluetoothAddress(data: data) == nil) + } } } From fe9b0a6b79049a72ab676dacaf982a981d740547 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 10 Jan 2025 03:43:16 +0000 Subject: [PATCH 2/8] Add macros --- Package.swift | 39 ++++- .../Extensions/Hexadecimal.swift | 159 ++++++++++++++++++ Sources/BluetoothMacros/Plugins.swift | 17 ++ 3 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 Sources/BluetoothMacros/Extensions/Hexadecimal.swift create mode 100644 Sources/BluetoothMacros/Plugins.swift diff --git a/Package.swift b/Package.swift index 909b6a6a6..3f9985722 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,6 @@ // swift-tools-version:6.0 import PackageDescription +import CompilerPluginSupport import class Foundation.ProcessInfo // get environment variables @@ -13,6 +14,12 @@ let libraryType: PackageDescription.Product.Library.LibraryType? = dynamicLibrar var package = Package( name: "Bluetooth", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .watchOS(.v6), + .tvOS(.v13), + ], products: [ .library( name: "Bluetooth", @@ -35,32 +42,50 @@ var package = Package( targets: ["BluetoothHCI"] ) ], + dependencies: [ + .package( + url: "https://github.com/swiftlang/swift-syntax.git", + from: "600.0.1" + ) + ], targets: [ .target( name: "Bluetooth", - swiftSettings: [.swiftLanguageMode(.v6)] + dependencies: [ + "BluetoothMacros" + ] ), .target( name: "BluetoothGAP", dependencies: [ "Bluetooth" - ], - swiftSettings: [.swiftLanguageMode(.v6)] + ] ), .target( name: "BluetoothGATT", dependencies: [ "Bluetooth", - ], - swiftSettings: [.swiftLanguageMode(.v6)] + ] ), .target( name: "BluetoothHCI", dependencies: [ "Bluetooth", "BluetoothGAP" - ], - swiftSettings: [.swiftLanguageMode(.v6)] + ] + ), + .macro( + name: "BluetoothMacros", + dependencies: [ + .product( + name: "SwiftSyntaxMacros", + package: "swift-syntax" + ), + .product( + name: "SwiftCompilerPlugin", + package: "swift-syntax" + ) + ] ), .testTarget( name: "BluetoothTests", diff --git a/Sources/BluetoothMacros/Extensions/Hexadecimal.swift b/Sources/BluetoothMacros/Extensions/Hexadecimal.swift new file mode 100644 index 000000000..1723fbfce --- /dev/null +++ b/Sources/BluetoothMacros/Extensions/Hexadecimal.swift @@ -0,0 +1,159 @@ +// +// Hexadecimal.swift +// Bluetooth +// +// Created by Alsey Coleman Miller on 3/2/16. +// Copyright © 2016 PureSwift. All rights reserved. +// + +internal extension FixedWidthInteger { + + func toHexadecimal() -> String { + let length = MemoryLayout.size * 2 + var string: String + string = String(self, radix: 16, uppercase: true) + // Add Zero padding + while string.utf8.count < length { + string = "0" + string + } + assert(string.utf8.count == length) + #if !hasFeature(Embedded) + assert(string == string.uppercased(), "String should be uppercased") + #endif + return string + } +} + +internal extension String { + + /// Converts a byte to its uppercase hexadecimal representation. + init(hexadecimal byte: UInt8) { + let length = 2 + self.init(byte, radix: 16, uppercase: true) + // Add Zero padding + while self.utf8.count < length { + self = "0" + self + } + assert(self.utf8.count == length) + #if !hasFeature(Embedded) + assert(self == self.uppercased(), "String should be uppercased") + #endif + } +} + +internal extension Collection where Element: FixedWidthInteger { + + func toHexadecimal() -> String { + let length = count * MemoryLayout.size * 2 + var string = "" + string.reserveCapacity(length) + string = reduce(into: string) { $0 += $1.toHexadecimal() } + assert(string.utf8.count == length) + return string + } +} + +internal extension FixedWidthInteger { + + init?(parse string: S, radix: Self) { + #if !hasFeature(Embedded) + let string = string.uppercased() + #endif + self.init(utf8: string.utf8, radix: radix) + } + + init?(hexadecimal string: S) { + guard string.utf8.count == MemoryLayout.size * 2 else { + return nil + } + self.init(string, radix: 16) + } + + init?(hexadecimal utf8: C) where C: Collection, C.Element == UInt8 { + guard utf8.count == MemoryLayout.size * 2 else { + return nil + } + guard let value = Self(utf8: utf8, radix: 16) else { + return nil + } + self.init(value) + } + + /// Expects uppercase UTF8 data. + init?(utf8: C, radix: Self) where C: Collection, C.Element == UInt8 { + #if !hasFeature(Embedded) + assert(String(decoding: utf8, as: UTF8.self) == String(decoding: utf8, as: UTF8.self).uppercased(), "Expected uppercase string") + #endif + let digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".utf8 + var result = Self(0) + for character in utf8 { + if let stringIndex = digits.enumerated().first(where: { $0.element == character })?.offset { + let val = Self(stringIndex) + if val >= radix { + return nil + } + result = result * radix + val + } else { + return nil + } + } + self = result + } +} + +#if !hasFeature(Embedded) +internal extension String.UTF16View.Element { + + // Convert 0 ... 9, a ... f, A ...F to their decimal value, + // return nil for all other input characters + func decodeHexNibble() -> UInt8? { + switch self { + case 0x30 ... 0x39: + return UInt8(self - 0x30) + case 0x41 ... 0x46: + return UInt8(self - 0x41 + 10) + case 0x61 ... 0x66: + return UInt8(self - 0x61 + 10) + default: + return nil + } + } +} + +internal extension [UInt8] { + + init?(hexadecimal string: S) { + + let str = String(string) + let utf16: String.UTF16View + if (str.count % 2 == 1) { + utf16 = ("0" + str).utf16 + } else { + utf16 = str.utf16 + } + var data = [UInt8]() + data.reserveCapacity(utf16.count / 2) + + var i = utf16.startIndex + while i != utf16.endIndex { + guard let hi = utf16[i].decodeHexNibble(), + let nxt = utf16.index(i, offsetBy:1, limitedBy: utf16.endIndex), + let lo = utf16[nxt].decodeHexNibble() + else { + return nil + } + + let value = hi << 4 + lo + data.append(value) + + guard let next = utf16.index(i, offsetBy:2, limitedBy: utf16.endIndex) else { + break + } + i = next + } + + self = data + + } +} +#endif diff --git a/Sources/BluetoothMacros/Plugins.swift b/Sources/BluetoothMacros/Plugins.swift new file mode 100644 index 000000000..c191b582b --- /dev/null +++ b/Sources/BluetoothMacros/Plugins.swift @@ -0,0 +1,17 @@ +// +// Plugins.swift +// Bluetooth +// +// Created by Alsey Coleman Miller on 1/9/25. +// + +import Foundation +import SwiftCompilerPlugin +import SwiftSyntaxMacros + +@main +struct Plugins: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + BluetoothAddressMacro.self + ] +} From 6a0f57a4cf024c19533d4ed5bdef67ec51e91c8e Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 10 Jan 2025 03:46:21 +0000 Subject: [PATCH 3/8] #171 Add `BluetoothAddress` macro --- Sources/Bluetooth/Address.swift | 7 ++ .../BluetoothMacros/BluetoothAddress.swift | 77 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 Sources/BluetoothMacros/BluetoothAddress.swift diff --git a/Sources/Bluetooth/Address.swift b/Sources/Bluetooth/Address.swift index 3d8e8c633..0f190a259 100644 --- a/Sources/Bluetooth/Address.swift +++ b/Sources/Bluetooth/Address.swift @@ -166,3 +166,10 @@ extension BluetoothAddress: DataConvertible { #if !hasFeature(Embedded) extension BluetoothAddress: Codable { } #endif + +// MARK: - Macro + +#if !hasFeature(Embedded) +@freestanding(expression) +public macro BluetoothAddress(_ string: String) -> BluetoothAddress = #externalMacro(module: "BluetoothMacros", type: "BluetoothAddressMacro") +#endif diff --git a/Sources/BluetoothMacros/BluetoothAddress.swift b/Sources/BluetoothMacros/BluetoothAddress.swift new file mode 100644 index 000000000..a76017688 --- /dev/null +++ b/Sources/BluetoothMacros/BluetoothAddress.swift @@ -0,0 +1,77 @@ +// +// Address.swift +// Bluetooth +// +// Created by Alsey Coleman Miller on 1/9/25. +// + +import Foundation +import SwiftSyntaxMacros +import SwiftSyntax + +struct BluetoothAddressMacro: ExpressionMacro { + + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws(Error) -> ExprSyntax { + + guard let argument = node.arguments.first?.expression, + let segments = argument.as(StringLiteralExprSyntax.self)?.segments, + segments.count == 1, + case .stringSegment(let literalSegment)? = segments.first else { + throw .requiresStaticStringLiteral + } + + guard let _ = Self.parse(literalSegment.content.text) else { + throw .invalidString("\(argument)") + } + + return "BluetoothAddress(rawValue: \(argument))!" + } + + /// Initialize a Bluetooth Address from its big endian string representation (e.g. `00:1A:7D:DA:71:13`). + static func parse(_ rawValue: S) -> ByteValue? { + + // verify string length + let characters = rawValue.utf8 + guard characters.count == 17, + let separator = ":".utf8.first + else { return nil } + + var bytes: ByteValue = (0, 0, 0, 0, 0, 0) + + let components = characters.split(whereSeparator: { $0 == separator }) + + guard components.count == 6 + else { return nil } + + for (index, subsequence) in components.enumerated() { + + guard subsequence.count == 2, + let byte = UInt8(hexadecimal: subsequence) + else { return nil } + + withUnsafeMutablePointer(to: &bytes) { + $0.withMemoryRebound(to: UInt8.self, capacity: 6) { + $0.advanced(by: index).pointee = byte + } + } + } + + return bytes + } +} + +// MARK: - Supporting Types + +extension BluetoothAddressMacro { + + /// Raw Bluetooth Address 6 byte (48 bit) value. + typealias ByteValue = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8) + + enum Error: Swift.Error { + case requiresStaticStringLiteral + case invalidString(String) + } +} From 83fde95e6c60c7a4e7200e7f23949fa3a95629a6 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 10 Jan 2025 03:57:48 +0000 Subject: [PATCH 4/8] #171 Add `BluetoothUUID` macro --- Sources/Bluetooth/BluetoothUUID.swift | 7 +++ Sources/BluetoothMacros/BluetoothUUID.swift | 61 +++++++++++++++++++++ Sources/BluetoothMacros/Plugins.swift | 3 +- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 Sources/BluetoothMacros/BluetoothUUID.swift diff --git a/Sources/Bluetooth/BluetoothUUID.swift b/Sources/Bluetooth/BluetoothUUID.swift index dadae30f7..df6826d26 100644 --- a/Sources/Bluetooth/BluetoothUUID.swift +++ b/Sources/Bluetooth/BluetoothUUID.swift @@ -382,3 +382,10 @@ public extension CBUUID { } #endif + +// MARK: - Macro + +#if !hasFeature(Embedded) +@freestanding(expression) +public macro BluetoothUUID(_ string: String) -> BluetoothUUID = #externalMacro(module: "BluetoothMacros", type: "BluetoothUUIDMacro") +#endif diff --git a/Sources/BluetoothMacros/BluetoothUUID.swift b/Sources/BluetoothMacros/BluetoothUUID.swift new file mode 100644 index 000000000..471051785 --- /dev/null +++ b/Sources/BluetoothMacros/BluetoothUUID.swift @@ -0,0 +1,61 @@ +// +// BluetoothUUID.swift +// Bluetooth +// +// Created by Alsey Coleman Miller on 1/9/25. +// + +import Foundation +import SwiftSyntaxMacros +import SwiftSyntax + +struct BluetoothUUIDMacro: ExpressionMacro { + + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws(Error) -> ExprSyntax { + + guard let argument = node.arguments.first?.expression, + let segments = argument.as(StringLiteralExprSyntax.self)?.segments, + segments.count == 1, + case .stringSegment(let literalSegment)? = segments.first else { + throw .requiresStaticStringLiteral + } + + guard validate(literalSegment.content.text) else { + throw .invalidString("\(argument)") + } + + return "BluetoothUUID(rawValue: \(argument))!" + } + + static func validate(_ rawValue: String) -> Bool { + switch rawValue.utf8.count { + case 4: + guard let _ = UInt16(hexadecimal: rawValue) + else { return false } + return true + case 8: + guard let _ = UInt32(hexadecimal: rawValue) + else { return false } + return true + case 36: + guard let _ = UUID(uuidString: rawValue) + else { return false } + return true + default: + return false + } + } +} + +// MARK: - Supporting Types + +extension BluetoothUUIDMacro { + + enum Error: Swift.Error { + case requiresStaticStringLiteral + case invalidString(String) + } +} diff --git a/Sources/BluetoothMacros/Plugins.swift b/Sources/BluetoothMacros/Plugins.swift index c191b582b..c4485bbc3 100644 --- a/Sources/BluetoothMacros/Plugins.swift +++ b/Sources/BluetoothMacros/Plugins.swift @@ -12,6 +12,7 @@ import SwiftSyntaxMacros @main struct Plugins: CompilerPlugin { let providingMacros: [Macro.Type] = [ - BluetoothAddressMacro.self + BluetoothAddressMacro.self, + BluetoothUUIDMacro.self ] } From 123598abde2cd2fb0d4d976b8b55d881160baa35 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 10 Jan 2025 03:59:52 +0000 Subject: [PATCH 5/8] Updated unit tests --- Tests/BluetoothTests/BluetoothUUIDTests.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/BluetoothTests/BluetoothUUIDTests.swift b/Tests/BluetoothTests/BluetoothUUIDTests.swift index 54881ca3b..79fcfdcf0 100644 --- a/Tests/BluetoothTests/BluetoothUUIDTests.swift +++ b/Tests/BluetoothTests/BluetoothUUIDTests.swift @@ -103,6 +103,7 @@ final class BluetoothUUIDTests: XCTestCase { XCTAssert(Data(uuid.littleEndian) != Data([0x28, 0x00])) XCTAssert(Data(uuid.bigEndian) == Data([0x28, 0x00])) XCTAssertEqual(uuid, BluetoothUUID(data: Data(uuid))) + XCTAssertEqual(uuid, #BluetoothUUID("2800")) } func test128BitUUID() { @@ -124,6 +125,7 @@ final class BluetoothUUIDTests: XCTestCase { XCTAssert(BluetoothUUID.init(littleEndian: BluetoothUUID.init(data: Data([0xC7, 0xA8, 0xD5, 0x70, 0xE0, 0x23, 0x4F, 0xB8, 0xE5, 0x11, 0x72, 0xF9, 0xE2, 0x4F, 0xF1, 0x60]))!) == uuid) XCTAssert(BluetoothUUID(littleEndian: BluetoothUUID(data: Data([0xC7, 0xA8, 0xD5, 0x70, 0xE0, 0x23, 0x4F, 0xB8, 0xE5, 0x11, 0x72, 0xF9, 0xE2, 0x4F, 0xF1, 0x60]))!) == uuid) XCTAssertEqual(uuid, BluetoothUUID(data: Data(uuid))) + XCTAssertEqual(uuid, #BluetoothUUID("60F14FE2-F972-11E5-B84F-23E070D5A8C7")) } func testDefinedUUID() { @@ -155,6 +157,7 @@ final class BluetoothUUIDTests: XCTestCase { uuidValue.bigEndian.bytes.1])) XCTAssertEqual(uuid, BluetoothUUID(data: Data(uuid))) + XCTAssertEqual(uuid, #BluetoothUUID("FEA9")) } func test32BitUUID() { @@ -189,7 +192,7 @@ final class BluetoothUUIDTests: XCTestCase { uuidValue.bigEndian.bytes.3])) XCTAssertEqual(uuid, BluetoothUUID(data: Data(uuid))) - XCTAssertEqual(BluetoothUUID.bit16(1000).rawValue, "03E8") + XCTAssertEqual(uuid, #BluetoothUUID("12345678")) } func test16BitBaseUUID() { From 58bc5db654b07921bfd2e856d72b47c2dd2219f4 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Fri, 10 Jan 2025 04:00:21 +0000 Subject: [PATCH 6/8] Ignore SwiftPM dependencies --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e08d4a910..479d1f653 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ playground.xcworkspace # Packages/ Package.pins +Package.resolved .build/ Bluetooth.xcodeproj/* .swiftpm From c22335e8f9ca648d0024dd7cb198e606b2ee0193 Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 9 Jan 2025 23:13:54 -0500 Subject: [PATCH 7/8] #154 Add macro disabling support --- Package.swift | 64 ++++++++++++++++----------- Sources/Bluetooth/Address.swift | 2 +- Sources/Bluetooth/BluetoothUUID.swift | 2 +- 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Package.swift b/Package.swift index 3f9985722..046203a2c 100644 --- a/Package.swift +++ b/Package.swift @@ -8,6 +8,7 @@ let environment = ProcessInfo.processInfo.environment let dynamicLibrary = environment["SWIFT_BUILD_DYNAMIC_LIBRARY"] != nil let generateCode = environment["SWIFTPM_ENABLE_PLUGINS"] != nil let buildDocs = environment["BUILDING_FOR_DOCUMENTATION_GENERATION"] != nil +let enableMacros = environment["SWIFTPM_ENABLE_MACROS"] != "0" // enabled by default // force building as dynamic library let libraryType: PackageDescription.Product.Library.LibraryType? = dynamicLibrary ? .dynamic : nil @@ -42,18 +43,9 @@ var package = Package( targets: ["BluetoothHCI"] ) ], - dependencies: [ - .package( - url: "https://github.com/swiftlang/swift-syntax.git", - from: "600.0.1" - ) - ], targets: [ .target( - name: "Bluetooth", - dependencies: [ - "BluetoothMacros" - ] + name: "Bluetooth" ), .target( name: "BluetoothGAP", @@ -74,19 +66,6 @@ var package = Package( "BluetoothGAP" ] ), - .macro( - name: "BluetoothMacros", - dependencies: [ - .product( - name: "SwiftSyntaxMacros", - package: "swift-syntax" - ), - .product( - name: "SwiftCompilerPlugin", - package: "swift-syntax" - ) - ] - ), .testTarget( name: "BluetoothTests", dependencies: [ @@ -109,12 +88,17 @@ var package = Package( ] ) -// SwiftPM command plugins are only supported by Swift version 5.6 and later. +// SwiftPM plugins + if buildDocs { package.dependencies += [ - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), + .package( + url: "https://github.com/apple/swift-docc-plugin", + from: "1.0.0" + ) ] } + if generateCode { for (index, _) in package.targets.enumerated() { package.targets[index].swiftSettings = [ @@ -141,3 +125,33 @@ if generateCode { "GenerateBluetoothDefinitions" ] } + +if enableMacros { + package.targets[0].swiftSettings = [ + .define("SWIFTPM_ENABLE_MACROS") + ] + package.dependencies += [ + .package( + url: "https://github.com/swiftlang/swift-syntax.git", + from: "600.0.1" + ) + ] + package.targets += [ + .macro( + name: "BluetoothMacros", + dependencies: [ + .product( + name: "SwiftSyntaxMacros", + package: "swift-syntax" + ), + .product( + name: "SwiftCompilerPlugin", + package: "swift-syntax" + ) + ] + ) + ] + package.targets[0].dependencies += [ + "BluetoothMacros" + ] +} diff --git a/Sources/Bluetooth/Address.swift b/Sources/Bluetooth/Address.swift index 0f190a259..7bf687cf5 100644 --- a/Sources/Bluetooth/Address.swift +++ b/Sources/Bluetooth/Address.swift @@ -169,7 +169,7 @@ extension BluetoothAddress: Codable { } // MARK: - Macro -#if !hasFeature(Embedded) +#if !hasFeature(Embedded) && SWIFTPM_ENABLE_MACROS @freestanding(expression) public macro BluetoothAddress(_ string: String) -> BluetoothAddress = #externalMacro(module: "BluetoothMacros", type: "BluetoothAddressMacro") #endif diff --git a/Sources/Bluetooth/BluetoothUUID.swift b/Sources/Bluetooth/BluetoothUUID.swift index df6826d26..b790fc52d 100644 --- a/Sources/Bluetooth/BluetoothUUID.swift +++ b/Sources/Bluetooth/BluetoothUUID.swift @@ -385,7 +385,7 @@ public extension CBUUID { // MARK: - Macro -#if !hasFeature(Embedded) +#if !hasFeature(Embedded) && SWIFTPM_ENABLE_MACROS @freestanding(expression) public macro BluetoothUUID(_ string: String) -> BluetoothUUID = #externalMacro(module: "BluetoothMacros", type: "BluetoothUUIDMacro") #endif From 4868c429e1ce8be78c92e73933d8d673c08326ac Mon Sep 17 00:00:00 2001 From: Alsey Coleman Miller Date: Thu, 9 Jan 2025 23:14:01 -0500 Subject: [PATCH 8/8] Updated GitHub CI --- .github/workflows/swift-arm.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swift-arm.yml b/.github/workflows/swift-arm.yml index 29fe1d496..bfede6bac 100644 --- a/.github/workflows/swift-arm.yml +++ b/.github/workflows/swift-arm.yml @@ -15,6 +15,6 @@ jobs: - name: Swift Version run: swift --version - name: Build Bluetooth - run: swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target Bluetooth + run: SWIFTPM_ENABLE_MACROS=0 swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target Bluetooth - name: Build BluetoothGAP - run: swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target BluetoothGAP + run: SWIFTPM_ENABLE_MACROS=0 swift build --triple armv6m-apple-none-macho --configuration release --verbose -Xswiftc -enable-experimental-feature -Xswiftc Embedded -Xswiftc -disable-stack-protector -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m0plus -Xcc -mthumb --target BluetoothGAP