The Xcode 11 and Swift Package Combo Platter

September 12, 2019

Since the Swift Package Manager (Swift PM or SPM) was introduced in late 2015 it has seemed like a natural fit for Xcode and SPM packages to work well together at some point in the future. At WWDC 2019, Apple announced that future was arriving in Xcode 11.

Icons for Xcode, a Swift Package Manager package, and an Xcode project

Xcode 11 provides two new ways to work with Swift Package Manager packages:

  • Open an SPM package in Xcode
  • Add SPM package dependencies to an Xcode project

These two new capabilities join a long-standing SPM feature:

  • Generate an Xcode project for an SPM package

Each of the these approaches are different both conceptually and in the nitty gritty details of what files they put where.

Open an SPM Package in Xcode

In Xcode 11, you can open a Swift Package Manager package directly in Xcode without needing a .xcodeproj Xcode project file.

Conceptually, you are working in a Swift Package Manager-centric world with the added ability of using Xcode to edit, build, and test your Swift package.

Note that this does not give you all of the settings and flexibility of an Xcode project. You cannot specify build phases, include resources, do code signing for an app, or any other functionality that is not currently supported by Swift Package Manager.

But being an Xcode project is not the purpose of this particular feature. This feature is all about using Xcode as an IDE for Swift PM packages.

What happens on disk?

Opening a package in Xcode adds a hidden .swiftpm/xcode directory to the package.

As you might guess, the xcode directory contains files Xcode uses such as a package.workspace file containing information about the workspace and xcuserdata and xcshareddata directories.

The .gitignore file generated when creating a new package includes xcuserdata as ignored. You will likely not want to add these files to your git repository. The files in xcshareddata on the other hand include shared schemes generated by Xcode which you may want to check into git to share among team members.

You will probably want to use the same policy of which Xcode-centric files to check in or ignore that you are using for Xcode projects.

The checked-out dependencies and build products are stored in the hidden .build directory, just like building the Swift package using swift build on the command line.

So, with a fairly small footprint of additional files, you can now use Xcode for Swift PM package development.

Add SPM Package Dependencies to an Xcode project

Also in Xcode 11, you can add Swift Package Manager packages as dependencies of an Xcode project.

Conceptually, you are working in an Xcode project-centric world with the added ability of having Xcode manage dependencies on Swift packages.

You specify the source URL and version of the package you want to use in your project. Xcode will automatically download the correct package version and any dependencies of that package. The dependencies are recorded in your Xcode project file, similar to dependencies on frameworks and libraries.

This is a significant addition. For the first time, Xcode has a native mechanism for managing and updating dependencies.

Developers in the Xcode ecosystem have traditionally turned to third-party solutions such as CocoaPods or Carthage for this functionality, or used git submodules in projects to combine code from different git repositories.

Xcode 11 opens the door to dependency-handling workflows that are actively supported by Apple and its tools.

But I don’t use Swift yet!

Even if your projects are not in Swift yet, you can create SPM packages with targets and products of C-based languages including Objective-C. The only Swift that must be in a package is the declarative Package.swift manifest file.

What happens on disk?

Adding SPM package dependencies to an Xcode project will add a Package.resolved file to the project’s workspace at xcshareddata/swiftpm. This file contains the resolved versions of each dependency. This file is intended to be included in source control so team members and continuous integration systems can use the same dependency versions.

As you might expect, the package dependencies will be referenced in the project’s project.pbxproj file.

There are no additional changes to the Xcode project and no hidden directories added. Everything else, including the downloaded package sources and build artifacts are located in the Xcode derived data directory for that project. The SourcePackages directory in the project’s derived data contains the checkouts and repositories for the Swift packages.

In addition to adding remote package dependencies to the project, Xcode will also recognize local package dependencies. This is useful, for example, if you are developing an app and a supporting package at the same time.

The addition of Swift package dependency management in Xcode gives you a powerful new option in structuring your projects’ dependencies.

Old Original SPM-Generated Xcode Project

Since the early days of the Swift Package Manager, you could generate an Xcode project for a package with the command swift package generate-xcodeproj.

This feature has always had limitations and drawbacks. With the new capability of opening a package directly in Xcode, you should never need to use this feature. But, it is still a temping option when looking at swift package help, so it might be helpful understanding why you don’t want to use this.

The SPM-generated Xcode project is intended as a convenience to use Xcode to view and edit your package. It is not meant to be a long-lasting resource.

If you make changes to the package’s manifest, add or remove dependencies, or make any significant changes, you need to regenerate the Xcode project. It is intended to be a transient resource, a convenient throwaway project to allow you to use Xcode to edit and build the package.

Because of this, the generated Xcode project was not intended to be shared or checked-in to source control. This is confirmed by looking in the .gitignore file automatically generated by SPM where /*.xcodeproj is on the list of files to be ignored.

The generated Xcode project stores some items within the xcodeproj directory including an Info.plist file per target in the package.

If your package has any C-based library targets, (for instance if you are exposing an existing C library to Swift), then the generated Xcode project contains a directory GeneratedModuleMap which contains a module map for each C-based target. The module map file contains a hard-coded path which includes the current user’s home directory. This makes the generated Xcode project decidedly un-shareable.

I am unable to think of a good use case for a generate Xcode project file now that Xcode 11 has the ability to open Swift packages directly. This functionality now seems obsolete.

Summary

With the introduction of Xcode 11, there are now three main ways to work with Swift Package Manager packages in Xcode:

Swift Package Manager-centric
Use Xcode as an IDE for Swift PM packages. No Xcode project required.

Xcode project-centric
Add Swift PM packages to an Xcode project. Xcode manages the dependencies.

Swift Package Manager generated Xcode project
Don’t use any more. Just open the package directly in Xcode 11.

The WWDC 2019 session Adopting Swift Packages in Xcode contains a number of demos showing how to use Xcode with Swift PM packages and is a good resource for getting started.

Xcode 11 makes great strides in working with Swift PM packages in two complimentary ways. •


Categories: Apple, Software Development, Swift, iOS, macOS, tvOS, watchOS

Tags: Carthage, CocoaPods, Objective-C, SPM, Software Development, Swift, Swift PM, Swift Package Manager, WWDC 2019, Xcode, Xcode 11, git submodule, iOS, macOS, tvOS, watchOS