Swift Package Manager Logo

Implementing Swift Package Manager –Consuming Packages

This entry is part 5 of 14 in the series Swift Package Manager

No Manifest Destiny

The consumer of a package does not need to have a Package.swift file, unless they will be establishing themselves as a package to be consumed elsewhere, or will be building from the command line. The Swift build system takes care of establishing the linkage to included packages.

In this entry, I will only cover the GUI methods, expressed by Xcode, as that is my experience with the SPM, and, I suspect, it will be the manner that most developers will interact with the Swift Package Manager.

Adding Packages

The Main Package List

Packages are collected and displayed in a list that is visible if we select the main project in the Project Navigator, the Main Project in the Target List, and the “Swift Packages” Tab:

Fig. 1: The Main Package List

Simple Drag-And-Drop

The way that this works, is we just drag the entire package directory (the directory containing the Package.swift file) into the Xcode project of the consumer.

Fig. 2: We Are Preparing to Drag the Entire Package_A Directory Into the PackageConsumer Xcode Project
Fig. 3: The Drag Destination Is Under the Main Project
Fig. 4: The Package Is Now Embedded in the Project
Fig. 5: Note the Entire Repo Is There

This results in the package being embedded into the project. It hasn’t (yet) been linked to any targets, but it is now a project resource.

Additionally, we can see that the ENTIRE directory was added; not just the files referenced in the Package.swift file.

One feature of direct embedding is that we can actually edit package files, in place. We cannot do this for files that are fetched from remote repos.

Fig. 6: We Can Edit the Package Source

Note also, that the package is embedded as a simple project asset (no special section).

Drag-and-drop only affects the one package. It does not affect any packages that Package_A depends upon. Resolution of those dependencies is the responsibility of Package_A.

In fact, it is treated exactly like an embedded Xcode project. It is meant to support debugging and development. It isn’t really meant to be used for ongoing release and maintenance.

Embedding The Package Into the Target

Now that the package is a project asset, its product can be embedded into the project. This is done in the usual way:

Fig. 7: Before –The Package Product Is Not Embedded

If we hit the “+” button, the following menu appears:

Fig. 8: Selecting the Package Product for Embedding

We select the package product, and it ends up as an embedded asset:

Fig. 9: The Package Product Is Embedded

It should be noted that drag-and-drop packages don’t appear in the project’s Package List:

Fig. 10: Drag-And-Drop Package Is Not Listed In the Package List

That’s because drag-and-drop packages are treated like embedded Xcode projects; even if they use the package icon.

NOTE ABOUT EMBEDDING:

The default library type for packages is static. That means that the library simply needs to be linked in and not embedded. If the library is dynamic, however, it needs to be linked, signed and embedded. The name of the library is also not allowed to have underscores (If this were a dylib, the package would be named “Package-A“).

Adding From the File Menu

Another way to add a package, is via the File -> Swift Packages -> Add Package Dependency... menu item:

Fig. 11: The Add Package Dependency Menu Item

Adding A Git URI

In this case, we should supply a Git URI. If we have associated a Git account in our Xcode preferences, repositories visible to that account are shown. It can take a few seconds for all of them to appear, and we can simply select one of those:

Fig. 12: Selecting A Repo From The Account Visibility List

But it is more likely that we will enter an explicit Git URI:

Fig. 13: Entering An Explicit Git URI

Once we hit “NEXT,” the following screen will appear:

Fig. 14: The Version Range Selection Screen

If we entered an explicit URI, then it is likely that a version will already be entered. Otherwise, we can enter a version, manually.

The Git repository must have tags set, using simple semantic versioning, for this to work.

We can also set a branch (the HEAD revision will be used):

Fig. 15: The Branch Specification Field

Branches are not meant to be used for release builds, and cannot be mixed with version-based ranges.

We can also enter commit hashes, for a specific commit.

Once we hit “NEXT,” we will see a screen, similar to this (NOTE: If this is being accessed from a target, then it will look like Fig. 17 -No “Add To Target” Column):

Fig. 16: Attach To Target Screen (Coming From Main Project)

This allows us to immediately attach it to one of our Xcode targets. We will be required to embed it.

Fig. 17: If We Are Coming From A Target
Fig. 18: The Swift Package Dependencies

Once this is done, the package will be placed in a separate location in the Project Navigator:

Fig. 19: The Entire Repo Is Added

The entire repo is also added, like with the drag-and-drop addition, but, in this case, we will not be able to edit any of the package assets:

The package will now be visible in the Main Package List:

Fig. 20: The Package Is Visible In The Main Package List

If we want to add the package to other targets, we follow the same procedure as above (Embedding), as the package is now a project component.

Adding From The General and Build Phases Tabs

If we look at a specific target in the Target Navigator, and select the “General” tab, we can add the package, using the “Frameworks and Libraries” section:

Fig. 21: The General Tab
Fig. 22: The Add Framework Screen

If we hit the “+” button, we will get this screen:

At the bottom, is a popup menu. If we choose that, we will get a selection called “Add Package Dependency…”

If we select that, we will be taken to the same workflow as above (Adding A Git URI).

We can also do the same thing from the “Build Phases” Tab:

Fig. 23: The Build Phases Tab

In both of the above cases, Xcode will add an embed step, if necessary (if the library is dynamic).

Using The Package

Assuming the package was a library (either static or dynamic), we use it like we would any library, by importing it into Swift files that access the library contents:

import Package_A

Note that we don’t have special builds for each operating system (like “Package_A_iOS“, “Package_A_MacOS“, etc.). We simply use the name defined in the main Package.name property (Note that a dylib that uses dashes for its bundle ID will still use the underscore for the import).

Also, the package is resolved, loaded and built upon opening the project, so it may be a few seconds before the packages are ready for use.

Cache Issues

SPM is heavily cached, and ensuring that we have the latest version of a package can sometimes be a challenge, while developing (I have found it doesn’t really pose a problem, once the project has stabilized).

Xcode has a File -> Swift Packages submenu that allows us to flush the cache, and rebuild the packages. I will often use the Product -> Clean Build Folder menu, and also clear the “DerivedData” directory.

Fig. 24: The Swift Packages Menu

Version Conflicts

SPM will prevent depending on incorrect versions of nested dependencies (“Dependency Hell”).

Trying To Include A Dependency That Has An Incorrect Version Sub-Dependency

Look at Fig. 25:

Fig. 25: SPM Prevents Inclusion Of Improper Dependency

What happened here, was that I had already included a dependency on Package_A, for Version 2.0.0, then tried to add Package_B, which had a dependency on Package_A, but at Version 1.0.0.

SPM realized that these dependencies conflicted, and blocked me from including Package_B.

A Nasty “Gotcha”

Even if we are allowed to include the offending dependency (in the following example, I went and included Version 2.0.0 of Package_C, which depended upon Version 2.0.0 of Package_A), we may still have a dependency collision.

Look at Fig. 26:

Fig. 26: Both Dependencies Are Installed

The library won’t build, however, in Xcode (It will build in swift build, though. Go figure. I assume that swift build is smart enough to avoid loading the library twice).

That’s because we now have TWO copies of the Package_A static library in our app. One is loaded with the Package_C dylib, and the other is directly embedded as a static lib.

We will see the following error: "Swift package product 'Package-A' is linked as a static library by 'Package_D' and 'Package-C'. This will result in duplication of library code." in the Project Navigator Error Pane.

Fig. 27: The Library Collision Error

The solution to this, is to remove our dependency to Package_A, trusting Package_C to provide it:

Fig. 28: Note That We Still Have Two Dependencies Loaded In The Project Navigator

In Fig. 28, we can see that we have both libraries in the Project Navigator, even though we only have one listed in the Swift Packages tab.

It should be noted that, even though Package_C is providing access to Package_A, we don’t get Package_A “for free,” when we import Package_C. We still need to explicitly import Package_A:

import Foundation
import Package_A
import Package_C

public struct Package_D: PackageProtocol {
    public let indent: Int
    public let text: String
    public init(indent inIndent: Int = 0) {
        indent = inIndent
        let prefix = String(repeating: "\t", count: inIndent)
        text =  "\(prefix)Package_D, Version: 1.0.0\n" + Package_C(indent: inIndent + 1).text + "\n" + Package_A(indent: inIndent + 1).text
    }
}