WWDC2019 Session 410

Transcript

[ Music ]
[ Applause ]
>> 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.
[ Applause ]
>> 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 ]