Whether you want to publish code to share with the community, or you just want a convenient way to organize the code in your apps, Swift packages are here to help. Learn how to create local packages for your own development, how to customize your package via the manifest file, and how to go about publishing a package for others to use.
Hi. My name is Boris and I'm a member of the Xcode team. Welcome to the session Creating Swift Packages. Likely you have already heard about the Xcode support for packages, but today you'll learn how to create your own.
We'll talk about five main things today. We learn about how to create your own local package.
We'll learn about how to publish it.
We'll tell you a little bit more about the package manifest API. And how to edit packages. And finally, I'll tell you about the Swift package manager open source project. We had another session about packages already.
It was called adopting Swift packages.
You should watch that as well and because there's other relevant information there such as how to resolve package resolution conflicts and also it goes into some details about the basics of packages if you haven't heard anything about them yet.
Packages are a great way to share code, both within your workspace with your team, or with a wider open source community.
Let's first take a look at how to create our own local packages. You can think of local packages as being similar to subprojects in a workspace. They are platform-independent in nature so that you can use your code across all of Apple's platforms in a straightforward way.
They're great for refractoring out reusable code.
They're not versioned but once you're ready, you can publish them in just a few steps. Let's take a look at how to create our own local packages in our first demo. For this demo, we're going to use an app that shows the lunch menu of a couple of cafes near where I work. We have both an iOS and a watchOS version of the app. And right now we have this data model here that is shared between the two, just using target membership. As we evolve our app, that actually gets a little bit cumbersome. So I want to refractor it into a local package. To get started with this, we go to File, New, Swift Package. We'll call the package food and stuff. We'll add it to our existing project and to its root group and just click Create. Now, Xcode creates the basic structure of the package for us including Readme, the Package that's with Manifest File, Sources, and Tests.
We take our data model code from our app and just drag it into the package. Now, next we want to link this code with our app. Let's take a quick look at the manifest file which describes how the package is being built, and there's a section called Products where we define a library. This library we can link with our app. We'll go into more details about the manifest and the Products section later in the talk. But for now, we can go over to the project editor, open the target for our iOS app, and we go to the Frameworks, Libraries, and Embedded Content section. We click Plus here and then we select the food and stuff library product from the list.
We want to perform these same steps for the watch app. So we go to that target. Go to the same section. Click plus again, and also link this with our package product.
So, packages also bring one or more modules. So, we have to import those modules into our app. In this case, we have just one. We go to the code of iOS app.
Import the module here.
And we'll do the same for our watch app.
Now, since we have made some larger changes to our workspace, the preview was paused. So let's press command + option + P to resume it. And there we have our app working as it did before. So in just a few steps, we refractored our reusable code into our own local package.
And you might've also noticed that we did not have to configure anything explicitly about platforms. This is because packages are platform-independent in nature. So, they build for whatever the client needs. In this case, our scheme does both an iOS and a watch app. So, the package product gets built twice, once for iOS and once for watchOS. And this is all automatically handled by Xcode. And finally, this gives a good first step for publishing a package. But before we do that, let's head back to the slides to learn a bit more about this.
You just learned about local packages. Now let's take a look at how you would publish a package in order to share it with a broad audience. Before we look at the concrete steps for publishing a package, we have to learn about versioning.
More specifically, about semantic versioning with Swift packages utilized.
To make sure you can benefit from bug fixes of your dependencies without constant churn, Swift packages adhere to semantic versioning. This is a wide-view standard which assigns specific semantic meaning to each of the components of a version number. The major version signifies breaking changes to the API which require updating existing clients. For example, this would be renaming an existing type, removing a method, or changing a method's signature. But this also may include any backwards incompatible bug fixes or major behavior changes to existing API.
Update the minor version, the functional ID is added in a backwards compatible manner. For example, adding a new method or type. Increase the patch version when you're making backwards compatible bug fixes. This allows clients to benefit from bug fixes to your packages without incurring any maintenance burden. Major version zero is a special case which you can use during initial development.
Changes to minor and patch versions can also break API. This simplifies work during bringup, but you should be shipping a 1.0 release as people start adopting your package. Once you've been publishing your package for awhile, your clients will expect stable APIs. You can use prerelease versions to ask your clients to test APIs before you make the final release for a version.
You can opt in to prerelease versions by adding a prerelease identifier to your version rules. In this example, I used beta 1 in the lower bound.
Note that this opts you in to resolving prerelease versions but you're still getting updates. For example, here I've got beta 6. Once the stable version 5 is released, package resolution will automatically pick that, but you should remove the prerelease identifier once you're done with testing. Let's take a look at the concrete steps for publishing a package in the next demo.
We go back right where we left off from the first demo. And to get started, I will drag the package out of the project. Now, you want to hold option while dropping, so you make a copy. You close the project, open up this in Finder and double-click the package that's with File.
This opens up the package standalone, similar to how a project would.
And if we take a look at the Run destinations, we see that we also get Mac and tvOS, even though we were developing our package for iOS and watch before.
This again underlines the packages are platform independent in nature. No special configuration is needed in order to build them for all of Apple's platforms in Xcode.
Now, since you're publishing the package, let's flesh out the Readme a little bit. Let's say this package provides data models for representing a food menu and loading it from JSON. Now, for a real package you want to include more information in the Readme, such as how to use it, any platform restrictions, if you use platform-specific API such as UIKit, and also information about licensing. But since this is just a demo, this will suffice for now.
Another thing you want to do is add tests. Xcode created an example test case for us earlier but we want to actually test our data model here. Let's create a food item and we call it chicken with the price of $42. And let's make an assertion on the item's price to be exactly that value. Now, if we press Command + U, our package builds and the tests run, just as they would in a project. The tests are passing, so we can move on. Let's first create a repository for our package. To do that, we can open up the source control menu and select the Create Repositories option. Xcode preselected a package for us, so we can just click Create. This creates a repository locally and commits our current state.
But we also need a repository in GitHub. We can create that right from within Xcode as well. Let's go to the SEM navigator. Open up the context menu in the repository, and select the Create Remote option. Since I already configured my GitHub account in Xcode preferences, it is automatically preselected here. We could change the repository name but we'll leave it as is and we'll also leave out the optional description for now. We'll set the visibility to private though because I want to just share this package with my team for now, not with a broad audience. Let's click Create.
Xcode creates the package, creates the repository on GitHub and pushes our current state there, just in one step. Now we have published our package but we also want to publish our first version. For this, we go back to the context menu here and select the Tag Master option. We want to release version 1.0.0 and we leave the message blank for now. This creates a tag locally, so we still have to push it to GitHub. To do that, we go back to the source control menu, select the Push option. To check the checkbox to include tags so they're actually pushing tags, and click push.
Now that we have published our package on GitHub, let's take a look at it. Select the View in GitHub option here from the context menu, and this opens it up right there.
So, we could end the demo here, but as a final step, I want to reintegrate the remote version of the package into our lunch app from earlier. So, I click the clone or download button and copy the URL from here. We close Safari, Xcode, as well as Finder.
And we go back to Xcode's welcome window and open up the lunch project again.
Here we open the file menu and this new Swift package's submenu.
This menu contains a couple of options for working with packages but I want to add a package dependency. Let's copy the URL here.
The default version rules that Xcode recommends for us includes the 1.0.0 version that we just published. So, we can click Next here. The package was resolved, and now we see the selection of products. We want to link the library product against our iOS app. So, we click Finish here. Now I actually forgot to do one thing just now, which is deleting the local package that we had from earlier. So, I will do that now and move it to the trash.
And now we're actually fetching the remote version. Let's take a look at the this Swift package dependency section in the Project Navigator. This shows all your package dependencies.
So, because we linked the product as part of the assistant workflow, it's already linked against our iOS app. But we also have to add it to the watch app. To do that, we go back to the frameworks, libraries, and embedded content phase.
Hit plus here, and select the package product. Now we can go back to our preview.
Resume it. And we can see that works as it did before.
So, with just a few steps, we were able to publish a package. Let's go back to the slides.
Next, I'd like to invite my colleague, Ankit, onto the stage, to tell you a little bit more about the package manifest API.
Thanks, Boris. Boris has showed you how you can use a local package in your Xcode project and how you can publish it to share it with an even wider audience. In this section, we will learn more about the package manifest APIs that are used to configure packages. A Swift package is a directory that contains package.swift manifest file.
The first line of the manifest is always the Swift tools version. This is a minimum version of Swift Compiler that is required to build your package. We will learn more about this later in the talk. Then we have the package description import statement. This is a library provided by Xcode that contains the APIs using the manifest file.
Finally, we have the package initializer statement. The entire package is configured using the single package initializer statement. In this case, I just have the name of the package so that it's at a target. Swift packages have a standard layout for targets.
Library targets are under a directory called Sources and each target should have its own subdirectory. They are then declared in the Target section of the package initializer.
The standard layout is really powerful because you don't need to individually list your source files in the manifest. You just drop them in the right directory and Xcode will automatically pick them up.
If I wanted to add another target, I would create a new subdirectory and then declare the target in the manifest file. Test targets are under a directory called Tests, and they also have their own subdirectory. They are declared using the test target API and since test targets are usually testing another target, you need to declare our dependency on the target it is testing.
This is done using the dependencies parameter of the test target API. As a final step, we need to declare our product for our package. Products are used to export target from a package so other packages can use them. In this case, I have a library product that is exporting my single library target. We just saw how a basic Swift package is configured. Now let's see how we can add support for our Swift packages in the existing Xcode project. I have a project called Menu Downloader that I've been using with other package managers like CocoParts and Carthage. It has a Swift target, some legacy C code, Xcode project file, and a PartSpec file that is used by the CoCoParts package manager. To begin, all you need to do is add the package.swift manifest file, and now you are ready to configure its targets. Starting with the legacy C code, we first give it a name and then a custom path. We have to do this because this target is not under the standard sources directory.
I also found out that there's a macro in the C code that downloads a secret lunch menu if defined. So, I define that using the C settings API. Similarly, we can configure our Swift target by giving it a custom path and then declaring a dependency on the legacy C target. This package has two products. The first product exports Swift target, and the second product exports our C target. We are exporting our C target separately because some of our users might be using the C target directly and they don't need the Swift bridge in that case.
It is also marked because I know some of our users load this library at some time. Now, let's see how package dependencies are configured in a package.
Package dependencies are in a section called Dependencies and they have two components - a source URL and a version requirement.
In this case, I'm using uptoNextMajor version requirement. This means my package needs a version of yams starting from major version 2 and going up to the next major version which will be 3, according to semantic versioning.
uptoNextMajor is the recommended way to declare version requirements.
This is because it allows you to specify a minimum version as explicit often for the next major version. And it is flexible enough to avoid potential conflicts in your package craft. This also has a shorthand, which is just called from.
There are some other types of version-based requirements. We just saw from and uptoNextMajor from.
There's uptoNextMinor, which allows you to declare version requirement on the next minor component of a version.
This is useful if you want to be conservative about the changes you take.
Then we have exact version requirement. This allows us to pin our dependency to a specific version. We do not recommend using this unless you really need it because it can lead to more conflicts in your package craft.
There are also some nonversion-based requirements. There's branch-based dependency which is useful if you want to develop multiple packages and you want to keep them in sync. And there's revision-based requirement, which is useful to pin our dependency to our specific revision.
Note that both branch and revision-based requirements are not allowed in published packages.
You must remove all branch and revision-based requirements before you publish a package. Now, after selecting our package dependency, we need to declare dependency on one or more of its products. In this case, I'm declaring dependency on yams product in our Swift target. Now let's come back to Swift Tools version.
As I mentioned earlier, the Swift Tools version is always the first line of the manifest.
And like every other API, the package description API also evolves over time and the version of the library you get comes from the Tools version.
It also participates in the dependency resolution process. And Xcode ensures that the Tools version of all of your package dependencies are always compatible with Tools version of your package. Finally, it declares the minimum version of Swift compiler that is required to build your package.
This is useful for producing good diagnostics in case somebody tries to use your package with an older incompatible version of Xcode. As Boris mentioned earlier, Swift packages are always platform independent.
If your package supports multiple platforms and you have some platform-specific code, we can use Swift's conditional compilation features. And for platform's start support availability, Xcode assigns our default deployment target for each of them. You can customize the deployment target in the platform section of the package initializer.
Note that this does not restrict the platforms on which this package can build. It only customizes it for the listed platforms. In this case, I'm customizing Mapworks to 10.15 and iOS to 13.
If your current Tools version does not have the deployment target API you want, you can use the string-based API. We just went through a lot of APIs. All package manifest APIs are documented and you can view the documentation in the generated module interface.
You can get to the generated module interface by command clicking package description import statement in any manifest file.
With that, back to Boris for talking about how to edit Swift packages. Thank you.
Thanks, Ankit. Once you have been publishing your package for awhile, to share it with your team or the open source community, you will likely need to work on it in the context of your app. So, let's talk about editing packages.
In the earlier demos, I edited packages in place. One of them was a local package that was built as part of a workspace. The other one was open standalone by double clicking package.swift. Both are always editable. Package dependencies, however, are locked for editing, because they're automatically managed by Xcode. If you look at the app from earlier, our dependency on the food and stuff package from GitHub. If we now check out the package standalone and add it to our project as a local package, it will override existing dependency without removing it.
The override is based on the last path component. So since both of these have the same, the local one will override the remote dependency. Since local packages are always editable, you can edit the app and the package together in this way. Let's take a look at editing packages in a demo. Again, we go back right where we left off in our earlier demo.
And if you remember, there's the Swift Package Dependency section, which shows the package dependency that we added.
Now, since we already have a checkout of the package separately from earlier, we can just drag it into our project. And now the Swift Package Dependency section disappears because we are no longer using the remote dependency. We're using the local one.
So, our users have asked us for a new feature for the lunch app. They want to see which dishes they can eat. So, we want to mark explicitly which dish is vegetarian or not. Thankfully, our data model already includes that information.
Not our data model, our underlying data. We have to change the data model to actually expose that. Let's go to the package.
Open up the food item type.
I'll add a new property here called vegetarian of type Bool. Copy this part. Add it as an argument to the initializer. And then finally we'll set the property to the initializer argument. So now we have the information on whether this is vegetarian or not in the data model. Let's add something to our UI to indicate this to our users.
Go to the code for our iOS app. Let's hide the project navigator so that we have a little more space, and resume the preview so that we see what we're dealing with. We can use the jump bar to go to the food item row view type. And if we go to our UI code in editor, we see highlighted that this is representing each of the table view cells. So, I've prepared a snippet to compute a label that uses the food item's name but adds emoji that represents whether a dish is vegetarian or not. And we can go to the text field and use that label here. And let's resume the preview. And then we can see that now for each dish it's clearly marked if it's vegetarian or not. So, with these steps you can edit your app and your package together. Let's go back to the slides.
This overriding behavior can also be used to edit the packages you don't own, if you need to fix bugs or make a small change of the bug in your problem.
To wrap things up, let's look into the Swift package manager's open source project, which we'll abbreviate as Swift PM. Swift PM has been out for a couple of years and Xcode support of Swift packages is built on top of it. Swift is a cross-platform language and Swift PM is a cross-platform built system for Swift packages. Using it, you can share code between your client and server side apps. Swift PM consists of four command line tools under the top level Swift command. There's Swift build, to build a package. Swift run, to run the executable products. Swift test, to run tests. And finally, Swift package, to perform various nonbuild operations on the package. These command line tools can be used to build packages for both macOS and Linux.
To learn more about the Swift PM command line tool and future ideas for its evolution, you can check out the session, "Getting to Know Swift Package Manager" from WWDC 2018.
Of course, you can also use Xcode Build to build packages on the command line or on CI. This is also a way to build packages for iOS, watchOS and tvOS on the command line. Swift package support in Xcode is built on top of the libSwiftPM library, which is part of the open source project.
libSwiftPM can be used to support Swift packages and other developer tools for IDs. We're excited to work with the community towards a stable API. An example for this is SourceKit-LSP, which is the implementation of the language server protocol - or LSP for short - for Swift and C-based languages. The LSP defines the protocol used between the editor or IDE and a language server that provides language features like autocomplete, jump to definition, or find our references. Using SourceKit-LSP, IDEs are editors which support the language server protocol, get these features for Swift packages. And this was built on top of the open source libSwiftPM. Swift package manager is part of the larger Swift open source project. The Swift.org website is a great place where you can learn about the community and process.
Package manager follows the Swift evolution process, just as the rest of the Swift project. Anyone can develop and ultimately submit features or major changes.
Before you spend your time drafting a proposal, drop by the package manager section of the forums, start a conversation, and find others that have useful thoughts or feedback. Swift packages currently only supports source code and unit tests.
We're looking forward to work with the community to add support for resources such as images, text files, or other data files. We already have a draft proposal for package resources. You can follow along the process and influence how this feature will look like.
Also on Swift.org, we have regularly updated tool change so you can try out the latest changes yourself.
Changes to the open source project will also be part of future versions of Xcode. As a final takeaway, packages are supported on Apple platforms and Xcode now.
Look for reusable code in your project and refractor it into a Swift package. We're excited about the expanding Swift package ecosystem. If you have any further questions about adopting or creating packages, we have two labs upcoming. One is today at 12 and the other one is tomorrow at the same time. Thank you very much for coming and enjoy the rest of your week.
[ Applause ]
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.