Within the earlier publish I checked out among the historical past of how we packaged up our library code to be used by our fellow builders. We checked out among the advantages of static libraries versus dynamic frameworks which additionally include headers wanted by the integrator.
Now let’s dive into the steps that had been essential for me to allow SPM assist on the primary few libraries DTCoreText, DTFoundation and Kvitto. It took me a number of days to iron out all of the kinks and I’d like to share with you what I realized within the course of.
We’re used to utilizing Xcode to explain what goes right into a construct: Which information to compile, what exterior libraries to hyperlink to, what assets are wanted and in addition normal construct settings just like the vary and varieties of supported platforms. Extra exactly, these settings are contained within the challenge.pbxproj
file inside your xcodeproj
bundle.
With SwiftPM there isn’t a such challenge file. Moderately all the things is outlined in human-readable type within the Bundle.swift
file.
For some fundamental terminology: we outline sure merchandise (i.e. static library, dynamic framework, app bundle and so on, useful resource bundle, unit check bundle), that relate to quite a few targets (a bucket for a bunch of supply code information and assets). Here’s a distinction from Xcode the place goal and product is used synonymously.
Bundle Definition
Step one, and most necessary one, is so as to add a package deal definition file to the foundation folder of the repository. It must be on this place as a result of Swift Packages are referenced by the repository URL and SwiftPM will solely take a look at the highest folder for Bundle.swift
.
Right here’s the definition for Kvitto, for reference. This has all components you would possibly encounter, together with a dependency on one other package deal, a few assets on prime of the definition of 1 product and a number of goal.
// swift-tools-version:5.3
import PackageDescription
let package deal = Bundle(
title: "Kvitto",
platforms: [
.iOS(.v9), //.v8 - .v13
.macOS(.v10_10), //.v10_10 - .v10_15
.tvOS(.v9), //.v9 - .v13
],
merchandise: [
.library(
name: "Kvitto",
targets: ["Kvitto"]),
],
dependencies: [
.package(url: "https://github.com/Cocoanetics/DTFoundation.git",
from: "1.7.15"),
],
targets: [
.target(
name: "Kvitto",
dependencies: [
.product(name: "DTFoundation",
package: "DTFoundation"),
],
path: "Core",
exclude: ["Info.plist"]),
.testTarget(
title: "KvittoTests",
dependencies: ["Kvitto"],
path: "Take a look at",
exclude: ["Info.plist"],
assets: [.copy("Resources/receipt"),
.copy("Resources/sandboxReceipt")]),
]
)
The primary line would possibly solely appear like a remark to you, however it will be important for the swift instruments to find out what syntax components are supported. Model 5.3 is required when you’ve got assets in any goal. In case you set that to one thing decrease you get syntax errors concerning the useful resource definitions. In case you set that to five.3 however don’t specify useful resource definitions (for non-standard assets) you’ll get warnings about unknown information that you need to both exclude or outline as assets.
I discovered myself conflicted about that, as I had talked about within the earlier article. All code would work on Swift 5.0 and up and solely the check goal has assets. I might get extra inexperienced checkmarks on Swift Bundle Index if I eliminated the .testTarget
definition.
On the opposite aspect the swift instruments allow you to run thusly outlined unit exams from the command line and functioning unit exams additionally ought to depend as an indication of excellent library high quality. Lastly, all people ought to be utilizing Swift 5.3 anyway as that’s the baseline customary for the reason that launch of Xcode 12.
That’s why I selected to go away it at that.
The essential setup of the package deal definition is simple. You will have the package deal title, then some minimal platform variations. Observe that these minimal OS variations don’t imply that that would limit the the package deal to particular platforms.
The merchandise part defines what sort of library comes out of the construct course of. The default setting (invisible) is to supply a static library, by specifying sort: .dynamic
you get a dynamic framework as an alternative. The targets array specifies which targets will get merged into the ultimate product.
I believed for a second that that may be good to have the assets be added to the framework as an alternative of a separate useful resource bundle, like we’re used to. However alas the dealing with of assets stays the identical they usually get bundled right into a Product_Target.bundle. So due to this fact I’d reasonably have the static library – which is able to get merged into the app binary – reasonably than having yet one more separate framework bundle contained in the app bundle.
As I defined within the earlier article, dynamic frameworks ought to be prevented if the supply code for libraries is public. So we’re pleased with the static library default.
The dependencies part lists the exterior reference to different packages. You specify the repository URL and the minimal variations. The proven means with from and a model would settle for all 1.x.x variations from and together with 1.7.15. There are additionally different methods to specify an actual quantity or sure ranges.
Final come the targets. We now have a daily goal for the package deal and a check goal for all of the unit exams. In case you don’t specify a path then SwiftPM expects the supply code within the Sources
folder beneath the goal’s folder and assets in a Sources
folder. I’ve a unique construction, so I specified a customized path.
I’ve to exclude the Data.plist
for each targets as a result of that is utilized by two targets outlined contained in the Xcode challenge. And for the check goal I specify two assets to be copied with the trail relative to the goal customized path. These copy directions are essential as a result of the contained assets don’t have a kind that Xcode is aware of methods to deal with. For issues like strings information or XIBs you don’t should specify something.
Examine the dependencies key of each targets. On the one hand you see that I’m referencing the exterior dependency of the principle goal. However the check goal requires the principle goal to work. That’s additionally a distinction to Xcode the place the examined code resides inside a number software, the place’s right here it’s compiled into the unit check bundle.
Goal Issues
You may be questioning why there’s a distinction between merchandise and targets in SPM. One cause for that you’ve got already seen: there isn’t a cause for the check goal to be represented in a product. Easy packages will typically solely have one product which may solely consist of 1 goal.
Though I already discovered two extra causes, to separate code out into extra particular person targets after which additionally merchandise.
You would possibly assume that Swift Bundle Supervisor would solely all you to have code written in Swift. However you’ll be improper, Any language goes, additionally Goal-C and different C dialects. However SPM doesn’t permit you to combine C-based languages with Swift in a single goal.
In a single challenge I had some Goal-C code for a operate with numerous ifs. I rewrote that in Swift solely to search out that compiling this may take greater than a minute, in contrast to some seconds in Goal-C. So I selected to go away the operate because it was. The answer was to place it right into a separate Goal-C goal and refer that to an inside dependency from the principle Swift goal.
The opposite good cause for a separate goal and product was to have some frequent knowledge mannequin code that might be utilized by inside targets and in addition by way of import in an app consuming my library. In locations the place the shopper would solely want the shared definitions he would import the particular module for that. Elsewhere he would import different targets which in flip might additionally make use of these definitions internally.
Every product turns into its personal module.
Resourcefulness
I discussed above which you can let SPM do its personal factor on the subject of customary useful resource varieties, like localised strings, XIBs, storyboards and asset catalogs. In case you use string localisation although, it’s important to specify the challenge’s default language.
Different varieties it’s important to both particularly exclude or specify what ought to be finished for it. You’ll be able to both specify a .copy
for every particular person useful resource or additionally for all the Sources
folder. Since I’ve solely two check information and that’s not going to alter, it wasn’t an excessive amount of work so as to add these individually.
SPM expects assets in the identical folder {that a} goal’s supply information reside in (or a sub-folder thereof). The rationale for that’s once more that there isn’t a Xcode challenge file the place you may specify membership of sure information to particular targets. You specify what belongs the place by how it’s specified by the file system together of the package deal definition.
Say you’ve got a single place the place you’ve got localised strings information downloaded from a translation website like POEditor however you need them to be included in several targets. A way to realize that’s to create soft-links contained in the goal’s useful resource folders to the information. I wrote this shell script to create the lproj folders for all languages after which create the hyperlinks.
#!/bin/sh
echo "Eradicating current strings"
rm -rf ../TFMViews/Sources/*.lproj
rm -rf ../TFMExtension/Sources/*.lproj
PWD=`pwd`
for entry in *.lproj
do
echo "Linking $entry..."
mkdir ../TFMViews/Sources/$entry
ln -s ../../../Strings/$entry/TFMViews.stringsdict
../TFMViews/Sources/$entry
ln -s ../../../Strings/$entry/TFMViews.strings
../TFMViews/Sources/$entry
mkdir ../TFMExtension/Sources/$entry
ln -s ../../../Strings/$entry/TFMExtension.stringsdict
../TFMExtension/Sources/$entry
ln -s ../../../Strings/$entry/TFMExtension.strings
../TFMExtension/Sources/$entry
finished
The identical strategy of soft-links will also be employed for Goal-C primarily based packages the place you may hyperlink to all related public headers in an embrace folder.
Platform-specific Code
Because the package deal has no facility for limiting particular supply code to particular platforms or OS variations, you’ll face the state of affairs that sure code received’t compile for different platforms. A workaround for this limitation is using conditional compilation directives.
For instance, all the things that references UIKit can’t be compiled for macOS or watchOS, so I’ve a couple of locations in DTCoreText or DTFoundation (each written in Goal-C) the place all the implementation is enclosed in:
#import
#if TARGET_OS_IPHONE && !TARGET_OS_WATCH
...
#endif
I additionally discovered that generally I needed to additionally import the TargetConditionals header for the defines to work. Specifically sure Goal-C class extensions in DTCoreText wouldn’t be seen within the public interface if I didn’t import this header. I’ve no rationalization as to why, however including the import for the header fastened it.
Contained in the Xcode Undertaking
The adjustments for conditional compilation apart, there’s nothing you have to change in your Xcode challenge – except you wish to. The principal setup for the package deal occurs in Bundle.swift
. You’ll be able to construct the package deal with issuing swift construct
.
I discovered it handy so as to add a reference to the package deal contained in the Xcode challenge as a result of this lets you debug your code within the context of being compiled for a package deal. In case you drag any folder (containing a package deal definition) into the challenge navigator pane, Xcode will add an area package deal reference for you, with a logo of a cute little field.
In Xcode 12 there’s a bug that when you do this for the challenge folder itself, it appears to work, however when you shut the challenge and reopen it once more, the reference turns into defunct. The way in which to repair it’s to alter the reference to “Relative to Undertaking” and open the folder selector by way of the folder button and re-select the challenge root folder.
This additionally creates a scheme for constructing the package deal and the package deal’s merchandise turn into out there to hyperlink/embed to your app. Bundle merchandise have an icon of a greek temple. If they’re static libraries then they’ll get merged into the app binary, dynamic frameworks can be added to the app’s Frameworks
folder.
Xcode additionally creates a scheme for the package deal, putting it in .swiftpm/xcode/xcshareddata/xcschemes/
. I moved it into the shared schemes folder of the xcodeproj and renamed it to Kvitto-Bundle.xcscheme
.
I had the watchOS platform builds on Swift Bundle Index fail as a result of xcodebuild insists on constructing all targets, together with the check goal. This fails as a result of unit exams require XCTest which doesn’t excite for watchOS.
By offering an aptly named shared scheme it should solely construct the principle goal and I achieved inexperienced checkmarks for watchOS on SPI.
Library Unit Assessments
To run the unit exams contained within the check goal, all you have to do is to run swift check
on the command line, from the repository root folder.
Some magic was required to get that to work as a result of check information required by the unit exams usually are not bundled within the .xctest
bundle. For normal packages a useful resource bundle accessor is being routinely generated, which you need to use with Bundle.module
.
The accessor works by figuring out the trail of the executable and developing a bundle title from names of package deal and goal. Within the case of unit exams the executable is xcrun
contained within the Xcode.app
bundle the place it has no likelihood of discovering the Kvitto_KittoTests.bundle
.
My ugly, however practical, workaround for that is as follows:
func urlForTestResource(title: String, ofType ext: String?) -> URL?
{
let bundle = Bundle(for: sort(of: self))
#if SWIFT_PACKAGE
// there's a bug the place Bundle.module factors to the trail of xcrun contained in the Xcode.app bundle, as an alternative of the check bundle
// that aborts unit exams with message:
// Deadly error: couldn't load useful resource bundle: /Functions/Xcode.app/Contents/Developer/usr/bin/Kvitto_KvittoTests.bundle: file KvittoTests/resource_bundle_accessor.swift, line 7
// workaround: attempt to discover the useful resource bundle on the construct path
let buildPathURL = bundle.bundleURL.deletingLastPathComponent()
guard let resourceBundle = Bundle(url: buildPathURL.appendingPathComponent("Kvitto_KvittoTests.bundle")),
let path = resourceBundle.path(forResource: title, ofType: ext) else
{
return nil
}
return URL(fileURLWithPath: path)
#else
guard let path = bundle.path(forResource: title, ofType: ext) else
{
return nil
}
return URL(fileURLWithPath: path)
#endif
}
This depends on the truth that the useful resource bundle can be created parallel to the xctest bundle, in the identical construct folder. The #if SWIFT_PACKAGE
conditional compilation will solely be added if this code is constructed as a part of a swift package deal. With this workaround, the earlier mechanisms of operating the unit check scheme by way of Xcode continues to work.
The wonderful thing about Swift being open supply, is that we will additionally examine the code for the useful resource accessor on GitHub. It seems that the talked about bug has already been addressed there. The repair was made too late to make it into Swift 5.3 in Xcode 12 however has been confirmed to be current in Xcode 12.2.
Conclusion
I discover that the evolution of Swift Bundle Supervisor as progressed sufficiently to begin including assist for it to my libraries. It’s doable and advisable to take action along with different methods of integration, like Xcode subproject, Cocoapods or Carthage.
Essentially the most annoying limitation remaining is that you simply can’t restrict targets to sure platforms or specify a variety of supported OS variations per goal. However these can simply be labored round with conditional compilation directives.
The standard standards partially enforced by the Swift Bundle Index coupled with the discoverability of elements additionally make it enticing for library distributors to think about supporting Swift Bundle Supervisor. Having the dependency administration taken care of by Xcode is the best characteristic of all.
Additionally revealed on Medium.
Associated
Classes: Administrative