How To Add A Dynamic Swift Framework To A Command Line Tool
This post originally appeared on Medium.
Let’s walk through how I tried to add a dynamic framework to my Command Line Tool and discuss what went wrong each step of the way.
Step 1: Add it to the ‘Linked Frameworks and Libraries’ section
This is what happens when you run your app:
The ThirdParty.framework
is trying to find libswiftAppKit.dylib
(which is part of the Swift standard libraries) in the @rpath
directory.
We can see how ThirdParty.framework
defines @rpath
by running
Well shoot. Those aren’t relevant to our Command Line Tool. We don’t have any folders named /Frameworks or ../Frameworks. Why is it looking for the Swift standard libraries there?
Because it’s built for iOS and Mac apps. Here’s the directory structure inside a Mac app:
That explains path @executable_path/../Frameworks
And for iOS, the directory structure inside the app is:
And that explains path @loader_path/Frameworks (offset 12)
But what about us, the humble command line tool developer? The Swift standard libraries are statically linked inside our executable, but our third party frameworks can’t find them. Unfortunately they’re not stored in a standard place on every Mac. Developers can get access to them buried inside Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx
(but please don’t require your users to install Xcode).
So what do we do? The standard practice I’ve seen is to create a custom framework for your project, import all of your dependencies into it, then copy the swift standard libraries as well.
Step 2: Create a custom framework and copy the standard Swift libraries
Perfect! Now let’s go ahead and run our Command Line Tool…
Your app is now seeing two different copies of the Swift standard libraries: the ones statically linked inside your executable, and the ones inside the /Frameworks folder.
There are two solutions from here.
Step 3 (option A): Make a Mac app instead and extract the executable
I investigated famous command line tools Carthage and SwiftLint to see how they handled this problem. Turns out they’re not set up as command line tools! They’re Mac apps! Why? Because a Mac app doesn’t statically link the standard libraries. They deploy themselves as command line tools by adding a run phase that extracts out the executable from the app package.
You can go ahead and do it that way, no problem. But I found another way around this issue.
Step 3 (option B): Disable static linking
Add these into your User Defined Build Settings:
This will force your executable to dynamically link all libraries. Make sure to tell Xcode where to find them by adding the framework directory to the ‘Runpath Search Paths’
@executable_path/FirstParty.framework/Versions/Current/Frameworks
Good luck with your command line tool!
Sean tries to get Xcode to compile at Livefront