Debugging Package Issues
Debugging Swift Packages can be a bit challenging.
Because they are built when we open a consumer project (as opposed to build time), package build errors are not reported in the build log. We will only see errors consistent with the package not being available at build time.
In my experience, I have found that running swift build
, from the Terminal, has been quite effective in understanding problems with packages.
This needs to be done at the package level; not the consumer level.
In the following example, I will demonstrate issues with an existing SPM project: RVS_BlueThoth. This was a project that actually gave me problems, and I will talk about how swift build
helped me to fix them.
The problems were entirely mine, and in the Package.swift
file.
I started off with this:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library( name: "RVS_BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], targets: [ .target( name: "RVS_BlueThoth", path: "./src") ] )
…and ended up with this:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library(name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], dependencies: [ .package(name: "RVS_Generic_Swift_Toolbox", url: "git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git", from: "1.2.1") ], targets: [ .target( name: "RVS_BlueThoth", dependencies: [ .product(name: "RVS-Generic-Swift-Toolbox", package: "RVS_Generic_Swift_Toolbox") ], path: "./src/Source" ) ] )
This post will outline the journey, and how I got from there to here.
The Structure
RVS_BlueThoth depends on RVS_Generic_Swift_Toolbox, which is used in a number of other libraries and apps.
It was this “sub-dependency” that caused issues for me.
Internally, RVS_BlueThoth also depends on RVS_PersistentPrefs; but that is only for the test harness, so it is not exported.
App Store Problems
The first issue that I ran into, was that apps submitted to the App Store failed, because I had problems with a couple of other apps.
In particular, I use underscores (“_
“) a lot in my module names.
But underscores are not allowed in Bundle IDs, which is what needs to be generated for an embedded dylib (I like to use dynamic libraries).
The problem was that the Swift build was creating libraries with a literal interpretation of the Bundle ID, including the underscores. In Xcode, we are used to seeing our Bundle IDs modified with dashes (“-
“) to replace the underscores, but that is not done by Swift Build.
I thrashed around a bit, until I figured out that I could change the Product.library.name
argument to use dashes, and it would only affect the BundleID.
I did that by simple trial and error. swift build was not involved.
So the first change was made:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library( name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], targets: [ .target( name: "RVS_BlueThoth", path: "./src") ] )
At this point, the resulting library will pass muster for the App Store, but we still have an issue. The RVS_Generic_Swift_Toolbox library is not available at build time (which means that it was not built when the project was opened).
If I build RVS_BlueThoth on its own, everything works swimmingly. It consumes the RVS_Generic_Swift_Toolbox package, and the library builds perfectly.
It is only when I build the Blue Van Clef app (the RVS_BlueThoth consumer) that I have an issue.
So I did the following:
:~$ cd /RVS_BlueThoth :/RVS_BlueThoth$ swift build
…which earned me the following response:
Undefined symbols for architecture x86_64: "_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolMp", referenced from: _$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in CGA_Bluetooth_Characteristic.swift.o _$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in CGA_Bluetooth_Peripheral.swift.o _$s13RVS_BlueThoth21CGA_Bluetooth_ServiceC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in CGA_Bluetooth_Service.swift.o _$s13RVS_BlueThoth37CGA_Bluetooth_Characteristic_ProtocolTL in CGA_Bluetooth_Characteristic_Protocol.swift.o _$s13RVS_BlueThoth30CGA_Bluetooth_Service_ProtocolTL in CGA_Bluetooth_Service_Protocol.swift.o _$s13RVS_BlueThothAAC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in RVS_BlueThoth.swift.o "_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolPAAE12makeIterators08IndexingH0VySay7ElementQzGGyF", referenced from: _$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicCSTAAST12makeIterator0H0QzyFTW in CGA_Bluetooth_Characteristic.swift.o _$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralCSTAAST12makeIterator0H0QzyFTW in CGA_Bluetooth_Peripheral.swift.o _$s13RVS_BlueThoth21CGA_Bluetooth_ServiceCSTAAST12makeIterator0H0QzyFTW in CGA_Bluetooth_Service.swift.o _$s13RVS_BlueThothAACSTAAST12makeIterator0E0QzyFTW in RVS_BlueThoth.swift.o "_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolPAAE9removeAllyyF", referenced from: _$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEP9removeAllyyFTW in CGA_Bluetooth_Characteristic.swift.o _$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEP9removeAllyyFTW in CGA_Bluetooth_Peripheral.swift.o _$s13RVS_BlueThoth21CGA_Bluetooth_ServiceC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEP9removeAllyyFTW in CGA_Bluetooth_Service.swift.o _$s13RVS_BlueThothAAC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAacDP9removeAllyyFTW in RVS_BlueThoth.swift.o "_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolPAAEy7ElementQzSicig", referenced from: _$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEPy7ElementQzSicigTW in CGA_Bluetooth_Characteristic.swift.o _$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEPy7ElementQzSicigTW in CGA_Bluetooth_Peripheral.swift.o _$s13RVS_BlueThoth21CGA_Bluetooth_ServiceC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEPy7ElementQzSicigTW in CGA_Bluetooth_Service.swift.o _$s13RVS_BlueThothAAC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAacDPy7ElementQzSicigTW in RVS_BlueThoth.swift.o ld: symbol(s) not found for architecture x86_64 [1/2] Linking libRVS-BlueThoth.dylib
That looks exactly like it can’t find any links to the RVS_Generic_Swift_Toolbox package.
I realized that I didn’t have anything in my dependencies list (D’oh!), so I added that:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library( name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], dependencies: [ .package(name: "RVS-Generic-Swift-Toolbox", url: "git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git", from: "1.2.1") ], targets: [ .target( name: "RVS_BlueThoth", path: "./src") ] )
Note that I used the dashes (“-
“), and not the underscores (“_
“). That’s because I assumed that the library name would be the exported Bundle ID name.
So let’s try swift build
again:
:/RVS_BlueThoth$ swift build 'RVS_BlueThoth' /RVS_BlueThoth: error: declared name 'RVS-Generic-Swift-Toolbox' for package dependency 'git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git' does not match the actual package name 'RVS_Generic_Swift_Toolbox' warning: dependency 'RVS_Generic_Swift_Toolbox' is not used by any target
Note that the swift build output explicitly states the problem: declared name 'RVS-Generic-Swift-Toolbox' for package dependency 'git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git' does not match the actual package name 'RVS_Generic_Swift_Toolbox'
That’s cool. It told me what needs fixing. Looks like I should’ve used the underscores.
Let’s make the change:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library( name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], dependencies: [ .package(name: "RVS_Generic_Swift_Toolbox", url: "git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git", from: "1.2.1") ], targets: [ .target( name: "RVS_BlueThoth", path: "./src") ] )
…and run swift build
again:
:/RVS_BlueThoth$ swift build
warning: dependency 'RVS_Generic_Swift_Toolbox' is not used by any target
Undefined symbols for architecture x86_64:
"_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolMp", referenced from:
_$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in CGA_Bluetooth_Characteristic.swift.o
_$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in CGA_Bluetooth_Peripheral.swift.o
_$s13RVS_BlueThoth21CGA_Bluetooth_ServiceC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in CGA_Bluetooth_Service.swift.o
_$s13RVS_BlueThoth37CGA_Bluetooth_Characteristic_ProtocolTL in CGA_Bluetooth_Characteristic_Protocol.swift.o
_$s13RVS_BlueThoth30CGA_Bluetooth_Service_ProtocolTL in CGA_Bluetooth_Service_Protocol.swift.o
_$s13RVS_BlueThothAAC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAAMc in RVS_BlueThoth.swift.o
"_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolPAAE12makeIterators08IndexingH0VySay7ElementQzGGyF", referenced from:
_$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicCSTAAST12makeIterator0H0QzyFTW in CGA_Bluetooth_Characteristic.swift.o
_$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralCSTAAST12makeIterator0H0QzyFTW in CGA_Bluetooth_Peripheral.swift.o
_$s13RVS_BlueThoth21CGA_Bluetooth_ServiceCSTAAST12makeIterator0H0QzyFTW in CGA_Bluetooth_Service.swift.o
_$s13RVS_BlueThothAACSTAAST12makeIterator0E0QzyFTW in RVS_BlueThoth.swift.o
"_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolPAAE9removeAllyyF", referenced from:
_$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEP9removeAllyyFTW in CGA_Bluetooth_Characteristic.swift.o
_$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEP9removeAllyyFTW in CGA_Bluetooth_Peripheral.swift.o
_$s13RVS_BlueThoth21CGA_Bluetooth_ServiceC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEP9removeAllyyFTW in CGA_Bluetooth_Service.swift.o
_$s13RVS_BlueThothAAC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAacDP9removeAllyyFTW in RVS_BlueThoth.swift.o
"_$s25RVS_Generic_Swift_Toolbox0A17_SequenceProtocolPAAEy7ElementQzSicig", referenced from:
_$s13RVS_BlueThoth28CGA_Bluetooth_CharacteristicC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEPy7ElementQzSicigTW in CGA_Bluetooth_Characteristic.swift.o
_$s13RVS_BlueThoth24CGA_Bluetooth_PeripheralC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEPy7ElementQzSicigTW in CGA_Bluetooth_Peripheral.swift.o
_$s13RVS_BlueThoth21CGA_Bluetooth_ServiceC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAadEPy7ElementQzSicigTW in CGA_Bluetooth_Service.swift.o
_$s13RVS_BlueThothAAC0A22_Generic_Swift_Toolbox0A17_SequenceProtocolAacDPy7ElementQzSicigTW in RVS_BlueThoth.swift.o
ld: symbol(s) not found for architecture x86_64
[0/1] Linking libRVS-BlueThoth.dylib
Look familiar? There is one difference, though:
warning: dependency 'RVS_Generic_Swift_Toolbox' is not used by any target
That tells me that adding the dependency was the correct thing to do, but that I need to apply it.
I realized that I also need to add the dependency to the target:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library(name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], dependencies: [ .package(name: "RVS_Generic_Swift_Toolbox", url: "git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git", from: "1.2.1") ], targets: [ .target( name: "RVS_BlueThoth", dependencies: [ .product(name: "RVS_Generic_Swift_Toolbox", package: "RVS_Generic_Swift_Toolbox") ], path: "./src/Source" ) ] )
Let’s give swift build
another go:
'RVS_BlueThoth' /Volumes/Development/RiftValley/RVS_BlueThoth: error: product 'RVS_Generic_Swift_Toolbox' not found. It is required by target 'RVS_BlueThoth'.
Dang! We’re getting closer, though. This tells me that it linked everything in, but that it couldn’t actually find the link target.
At this point, knowledge of the RVS_Generic_Swift_Toolbox Package.swift
file is useful.
If we look at it, we see that it’s deliverable name uses dashes:
let package = Package( name: "RVS_Generic_Swift_Toolbox", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library( name: "RVS-Generic-Swift-Toolbox", type: .dynamic, targets: ["RVS_Generic_Swift_Toolbox"]) ], targets: [ .target( name: "RVS_Generic_Swift_Toolbox", path: "./src") ] )
So let’s change the dependency product name to match that:
// swift-tools-version:5.2 import PackageDescription let package = Package( name: "RVS_BlueThoth", platforms: [ .iOS(.v11), .tvOS(.v11), .macOS(.v10_14), .watchOS(.v5) ], products: [ .library(name: "RVS-BlueThoth", type: .dynamic, targets: ["RVS_BlueThoth"]) ], dependencies: [ .package(name: "RVS_Generic_Swift_Toolbox", url: "git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git", from: "1.2.1") ], targets: [ .target( name: "RVS_BlueThoth", dependencies: [ .product(name: "RVS-Generic-Swift-Toolbox", package: "RVS_Generic_Swift_Toolbox") ], path: "./src/Source" ) ] )
If we run swift build
now, we will either get something like this:
Fetching git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git Cloning git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git Resolving git@github.com:RiftValleySoftware/RVS_Generic_Swift_Toolbox.git at 1.2.1 [45/45] Linking libRVS-BlueThoth.dylib
Or just that last line.
We’re golden.
That’s an example of how we can use the swift build
command line utility to help diagnose package issues.
One of the things about swift build
, is that its error reports can be extremely useful; sometimes, actually giving us the source code that we need.
One Last Thing
In the end, I ended up switching away from delivering dynamic frameworks, and made everything static.
This was because dylibs are just more complicated, in general; adding the need for embedding and signing, as well as a couple more places for bugs to happen. You’ll see how this can play out, in the examples.
That means that I don’t need to convert underscores to dashes, but I’d suggest we get into the habit, anyway, so we have the choice to convert to dylib, if we want.
Since most of my repos are absurdly simple, there’s no real motivation to release them as dylibs.
NOTE: I Encountered A Strange Anomaly –It May Not Be A Big Deal
If we don’t use the standard Package directory and file layout, it’s possible that the
swift test
utility won’t work. It seems to be a bug, and I’ve reported it, so I suspect it will be fixed.Despite the test failure, everything else works just great, including running the tests via the Xcode GUI.