WWDC2019 Session 408

Transcript

[ Music ]
[ Applause ]
>> Good afternoon.
My name is Anders, and I work on
Xcode.
In this session, I and my
colleague, Balraj, will be
talking about how to use Swift
Packages from Xcode Projects.
The Swift Package Manager is
part of the Open Source Swift
tool chain.
It was introduced in Swift 3.
And since then, a lot of Swift
Packages have been created.
Also, a lot of Open Source
libraries that were written for
other package managers have been
adapted to be compatible with
the Swift Package Manager.
Swift Packages let you manage
your versions of your
dependencies, making sure that
you get bug fixes without
subjecting your code to
source-breaking changes as the
packages you depend on update.
Swift Packages are also a great
way for your own -- to share
code among your own apps,
whether that's within a small
team, a large organization, or
maybe just among apps that
you're working on by yourself.
And now in Xcode 11, you can
access Swift Packages directly
from Xcode projects.
[ Applause ]
We're excited too.
So in this session, we're going
to start by talking about how to
use a package, how to extend the
functionality of an app by using
a package.
We're then going to talk a bit
about what exactly is in a
package, how they construct it,
what's in the data store -- in
the file format of a package.
When we talk about package
resolution, how Xcode fetches
the right versions of packages
and incorporates them into your
app.
We're going to talk then a
little bit about updating
packages, what happens when a
new version of a package's
update is published, and how you
can take advantage of that.
And we're going to talk about
resolving any version conflicts
that might happen as you update
the packages.
So let's get started by
extending NAP functionality to
take advantage of packages.
Here, we have a little iPhone
app that simply shows the, some
of the lunch menu offerings of
some of the cafes around where I
work.
We see that there are two here,
entries.
It's a SwiftUI app.
So I can see the preview and
Xcode without having to run my
app.
So we see that the lunch menu
from two different places are
showing up.
In the real version of this app,
we would fetch this data over
the network.
But in this demo app here, I
just have them as local files
among my source.
So you could say that this menu
is locally sourced.
[ Laughter ]
So these two JSON files are
showing up just fine.
But this cafe here is a bit more
modern and upscale.
And so they have a YAML menu,
right?
So we're not able to parse that.
And we're not seeing that.
So let's take a look at the
source code that loads this
data.
We see here that we handle JSON,
but we don't handle YAML.
So now fortunately, I know of a
library that can parse YAML
great.
And it has a nice Swift
interface.
So it's called Yams.
And I'm going to go and use
that.
And to do that I bring down to
file menu.
I go to this new Swift Packages
submenu, and Add Package
Dependency.
Now this submenu has a couple of
other menu commands to deal with
packages once they're in your
app.
But you said Package Dependency.
And here, I see, because I added
my GitHub accounts to Swift --
to Xcode's preferences, I see
here all of the package
repositories in that account.
And I also see any other
repositories that I've starred.
In this case, I've starred Yams
here.
But I could also, if I had a URL
for a package, I could enter it
here.
In this case, I'm going to click
on the readme link here and go
to the homepage for the Yams
projects.
That that looks good.
I'm going to go take a look here
at the API.
That looks like what I need.
Now, of course, when you're
using an Open Source library,
you're bringing somebody else's
code into your app.
So there are a lot of things you
want to be careful with.
You want to make sure you trust
the source of that package.
You want to make sure that you
know exactly what this library
is doing, so you don't have any
surprises.
And you want to make sure that
the license for this Open Source
library is compatible with your
app's license.
So I've done all that.
And so I'm going to go back to
Xcode.
And I'm going to just click Next
here.
And we're going to add a
reference, a dependency on yams.
Now Xcode shows me the versions
that are available.
And it automatically sets me up
to use the latest version of the
package.
We're going to talk a little bit
more about the details of these
options later on in the talk.
But most often, the default
option here is the one you want.
It's using version 2.0 anyway,
up to, but not including, the
next major version.
I'm going to click Next.
Now Xcode is fetching the
contents of the Yams package.
And it preselects for me the
single product here.
Some packages can have more than
one product.
In this case, there's one.
It's a library.
Same name as the package.
And if you have more than one
app in your project, you can
choose where you want to link
it.
And in this case, it's just the
one, so I'm going to link it to
the Lunch app.
And I hit finish.
Now we see a couple of things
here.
For one, Xcode has added a
reference to the Yams Package
Dependency to the new Swift
Packages tab in my Project
Editor.
We also see that the Yams
Package has shown up down here
in Swift Package Dependency
section.
We're not going to look in that
package right -- just now.
We're going to take a look in a
moment.
What we're going to do now,
though, is to go into the food
menu again.
And now, we're going to make use
of this from within our code.
So I'm going to type import Yams
here.
And we see we have code
completion against the import
name.
And I can also Command-Click on
this import statement here.
I can jump to the definition.
And here I see the rendered
version of the documentation for
the interface for the Yams
Project.
And all of this comes from the
documentation comments in the
source that was included in the
package.
So I go back to my code.
I'm going to add another case
here.
Case, YAML.
I'm going to type -- just type
YAML decoder.
I have code completion for all
of the library methods.
And I have Quick Help, because
the package includes that.
So this looks and feels just
like the built-in APIs in terms
of the quality of the support
you get.
I'm going to use this method.
I'm going to be using the same
first parameters for the JSON
case.
Now this API happens to take a
string instead of data.
So I'm going to be using that.
And I don't need the third
parameter here, because I'll
just use the default value.
So now, we can go back again to
the list view.
And because this is a pretty big
change, importing a new module,
I'm going to hit resume.
Xcode is going to rebuild the
application in the background.
And I'm going to see the preview
here.
And now we see that I see the
contents from the YAML data file
as well.
[ Applause ]
All right, so now I can commit
this to my repository.
And we see here what we expect.
We see the source changes.
Let me make that a bit bigger.
We see the source changes that I
made, of course and it's part of
the commit sheets.
We also see that the project
file has changed, because I've
added the reference to the Yams
Package.
And we see one more thing here,
which is Xcode has created a
directory called SwiftPM, that's
for the Swift Package Manager,
underneath the shared data,
underneath the workspace.
And you want to check that in,
because we'll talk about what
exactly is in there later on in
the talk.
But Xcode stores information
about the package versions that
resolved.
And you want to check that in so
that everybody on your team gets
the same versions.
All right, so now I can check
this in and say useYAML.
And use. All right, go back to
slides.
All right, so we saw quickly how
to use an Open Source Package
from within a project.
Let's take a closer look at
what's in the YAML Package.
The Package is a directory that
contains a Swift Package
manifest.
The manifest is a file called
Package.swift, and it identifies
that directory as a Swift
Package.
It also contains sources and of
course, it contains unit tests
to make sure that those sources
continue functioning well.
Underneath the sources is a
subdirectory for each of the
separate targets in the package.
These are the separately
buildable components of the
package.
And similarly, under the test
directory, there's a separate
subdirectory for every test
suite.
So let's take a closer look at
what's in one of the target
directories.
Each of the targets can have
implementation that's either one
of the C-based languages or
Swift.
So in the case of YAML, there's
a core CYAML parser that's
written in C.
It could also contain
Objective-C++ files.
And then there's a Swift
interface in a separate target
on -- in addition to that, that
calls down into the CYAML code.
And then the unit tests here are
written in Swift.
So if we look at the contents of
the Swift Package Manifests, the
first line here is a declaration
of what the tool version the
package needs.
So this says the minimum version
that can parse this package
manifest at all.
The package description API is a
declarative API that is provided
by the Package Manager's package
description library.
And by importing that, then the
rest of the contents of this
file can declare the
characteristics of the package.
That includes the name of the
package.
It also includes a section that
lists the products that the
package vends to the clients.
So the Package can control which
parts of its code can be
directly imported by the client.
And in this case, there is one
library with the same name as
the package, so called Yams.
We'll talk in a moment about the
targets part there.
It basically says that this
library publishes the Yams
target to clients as a library.
The target section lists the
individually buildable parts of
the package.
And as we see here, there is a
one-to-one correspondence
between the source folders and
the targets.
Each of those source folders can
have other subfolders for
organizational purposes, but the
top level under sources is one
folder per packet, per target.
In this case, we see the CYAML
target listed without any
dependencies.
And the Yams target is listed as
depending on the CYAML target.
This means that when the product
refers to Yams, that will, in
turn, indirectly bring in CYAML
as well.
And then there's one test target
here for the unit tests.
This code won't actually be
linked into the client.
But it's necessary to make sure
your library keeps running
properly.
And in this case, the Yams
Package Manifest also lists some
older Swift versions that the
code is compatible with.
There are also other
characteristics that you can
specify and in this declarative
language, and we'll get to some
of those later.
So how is this then linked into
your app when you actually build
and run your app?
So your project consists of
source files.
So it could be Swift.
It could be other languages.
And the packages you depend on,
they're also source files.
And so what Xcode does is it
takes all of these source files,
and it compiles them and
particularly compiles the
package code in a way that is
compatible with the app code in
your project.
So this includes architectures,
platforms, those things.
It will recompile it multiple
times, if needed, depending on
what your apps need.
Then it links it in and combines
all of that into the
application.
Package libraries are static by
default.
And so all the code is linked
together.
And this is repeated for the
various apps in your project
that use the same package.
So if you have an iOS app and
WatchOS app, they use the same
package.
Xcode might build the code
multiple times as needed for
each of those apps.
Now, we saw a case where a
project can depend on a package.
And we saw that is shown in the
Package Dependencies part of the
target editor.
But a package can also depend on
other packages.
And this is done through the
package manifest.
So one of the sections of the
package manifest the Yams didn't
have was a Dependencies section.
It didn't have that because it
doesn't actually depend on other
packages.
But some Packages that you have
might.
And so the Package Dependency
graph can include both direct
and indirect dependencies.
Now, I mentioned before that you
can manage your versions with
pack -- the Package Manager.
And this uses something called
semantic versioning.
And that's a fairly widely used
strategy that assigns semantic
meaning to each of the
components of a three-part
version.
So in this case, for example,
the major version is incremented
whenever there are breaking
changes to the API.
So this is anything that would
cause the clients to have to be
modified.
For example, if you rename a
method or remove a method or if
a package makes a met -- a
semantically meaningful change
that will cause existing clients
to have to adapt.
This is the reason why the
initial version of restriction
goes up to, but not including,
the next major version number.
The minor version number of a
package is incremented when
functionality is added in a way
that doesn't break existing
clients.
So this could be adding a
method, for example.
And finally, the patch version
here is when there are bug fixes
that don't have any semantic
changes, semantic meaning
changes.
And packages can be safely
updated to bug fixes, to
incorporate bug fixes without
changing the semantics of the
app.
Okay, so we've seen how to use
the package.
And we've taken a closer look at
that package.
And now I'd like to invite my
colleague, Balraj, on the stage
to talk about Package Resolution
in more detail.
[ Applause ]
>> Thank you, Anders.
Package resolution is the
process Xcode goes through when
selecting the versions of
Packages to use inside of your
workspace.
Let's go into a little bit more
detail in how that was working
with -- and how that was working
in the Lunch project Anders was
demoing earlier.
So here in the Project Editor in
the Swift Packages tab, we can
see our dependency on -- our
Lunch's dependency on Yams.
Using the Version Rule 2 to up
to the next major version,
meaning any version of Yams from
2 up to, but not including 3.
Then, in the Swift Package
Dependencies section of the
Project Navigator, we can see
Yams at version 2.0.
Let's look a little deeper into
this.
Lunch is selecting Yams at
Version 2, because of its
version requirement, 2 up to the
next major version.
If a version 2.1 existed, Xcode
would have selected that
instead, as 2.1 is the latest
version matching our version
requirement.
However, if a version 3 existed,
Xcode would not have selected
it, as it doesn't fit into the
constraints that we specified.
In this case 2.1 and 3 are
hypothetical examples.
And you'll see Yams continue to
resolve at Version 2 throughout
the rest of this presentation.
In this example, there's one
package with one version to
choose from.
So let's go into a few more
interesting cases, where package
resolution can get a little bit
more complicated.
So here is the Lunch app that we
have today.
It has a very basic UI and is
only using one package, as we
just said.
My team, over time, adds more
packages to our application and
they use these packages to show
common design themes across all
the applications that my team
owns.
So when we go back to the Lunch
application, after a few weeks,
we can see that the UI has been
updated, and there are three
additional packages inside of
our workspace.
These three packages are
DesignFont, DesignTheme, and
DesignColor.
All resolving at their own
respective versions.
So Xcode did a lot of the work
for us in terms of selecting the
versions of these packages.
But I want to understand why
these packages are resolving at
these versions.
So in order to do so, I go back
to the Project Editor.
Here in the Swift Package
Dependency section, we can see
our new dependency on
DesignTheme, with a Version Rule
1 one up to the next major
version.
Xcode, in this case, selects
DesignTheme at Version 1,
because it's resolving from 1 to
the next major version.
And so we also see our
dependency on Yams is still the
same.
And you might be wondering.
Where are DesignFont and
DesignColor in this case?
We're looking in the Project
Editor, but we don't see them
inside of there.
Well, the reason for this is the
Project Editor shows us all of
the direct dependencies between
the Lunch application and its
direct packages.
So in order to look at our
package's dependencies, we want
to go and look at the
DesignTheme package.
This is because it's the newest
package that was added to our
workspace.
And it's safe to assume that
they're not coming from Yams.
So in order to do so, we go back
to the high-level view of Xcode,
and look under the Swift Package
Dependencies section.
Here we see our package
DesignTheme.
We disclose the package and see
all the content available inside
of there.
And in this case, we want to
look at the Package.swift
manifest file.
This is because it is where we
will find all of our dependency
information for this package.
So we go to this file.
And in the dependencies array,
we see DesignFont and
DesignColor, and their version
requirements.
DesignFont is being resolved how
we've seen before, 1 up to the
next major version.
And so Xcode will select version
1.2 as it's the latest version
of the package.
DesignColor is resolved a little
bit differently.
This is using 1 up to the next
minor version, meaning any
version of designs color from 1,
and up to, but not including,
1.1.
This is typically used when
packages want to be a little bit
more conservative with the new
versions that they take during
updates.
And so Xcode selects Version
1.0.1 of the DesignColor
package.
So now this is the full view of
the new packages that have been
added and why they're being
resolved at the versions they
are.
You'll remember the last step
Anders did in the previous demo
was importing Yams and then
using its APIs.
Let's talk about how this works
and how it relates to package
resolution.
All of our packages are
producing libraries of the same
name.
And we can see that Lunch is
importing content from
DesignTheme and it's also
importing content from Yams.
And then the DesignTheme library
is also importing content from
DesignFont and DesignColor.
When we look at the full graph
here, we can see this is very
similar to how package
resolution is working.
And that is intentional.
When we added our direct
dependencies on our packages, we
also ended up importing their
content and actually using the
APIs in our application.
But what happens when I want to
import content from a sub
dependency into our project?
So let's talk about how we would
do this.
We have our Lunch project, which
is depending on DesignTheme.
And DesignTheme is depending on
the DesignFont package.
The DesignFont package is
producing a library of the same
name.
In this context, we don't want
to immediately import content
from DesignFont into Lunch,
because if DesignTheme loses its
dependency on DesignFont and
Update, Xcode will lose its
reference to DesignFont.
And now, we won't be able to use
the DesignFont library.
So a better approach of doing
this, and we'll go back to the
beginning of our example, is to
create a direct depend -- direct
package dependency between the
Lunch project and the DesignFont
package.
And then we can import content
from DesignFont into Lunch,
because if DesignTheme loses its
dependency on DesignFont in an
update, we still retain the
reference to that library inside
of Xcode.
So that is how Xcode selects
different versions of your
packages.
Let's go into an -- into how you
can go about getting new
versions of those packages,
which provide API improvements
and bug fixes.
So one day, I'm at team with --
I'm at lunch with the team
maintaining DesignFont.
And they tell me about a new
version of the DesignFont
package, which has a few small
bug fixes, Version 1.2.1.
When I get back to my desk, I
see DesignFont is using version
1.2.
The Lunch project is using
DesignFont at Version 1.2.
And so I want to update this
package.
In order to do so, I click File,
Swift Packages.
And here, I'm brought with
multiple options regarding Swift
Packages.
In this case, I want to update
to the latest package versions.
So go ahead and click that, and
the update operation occurs.
We're now using DesignFont
Version 1.2.1.
So what exactly does updating
package version do?
What happened during that update
operation?
So there exists a file called
Package.resolved, which is
central to this.
Package.resolved records version
information about all the
packages inside of your
workspace.
And when you go through the
update operation, this file gets
updated, and then Xcode will
select -- pull down the new
versions for you.
This file exists inside of
xcsharedata, which is typically
shared with your team and source
control.
So an important thing to note
here is that that update
operation we just did is a local
operation.
In order to share this update
across my team, I have to commit
and push my changes to the
Package.resolved file.
And then if you want to look for
all of this yourself, it's
inside of the Xcode Project
file.
But please note that you don't
actually have to edit the
Package.resolved file yourself.
Xcode should be doing all of the
work for you.
So as I said before, we actually
haven't shared this update with
our team.
So let's do that.
We can do all of this within
Xcode by going to the Source
Control Menu and clicking
Commit.
Inside of here, we'll see all
the changes that were made to
the Package.resolved file and
how it updated from Version 1.2,
what we were previously using,
to 1.2.1, the new version.
Because I want to push, I go
ahead and click the lower-left
checkbox, push to remote.
And then I can commit and push
my changes.
We've now successfully shared
this update across our team.
So all of this begs the
question, why do we need a
Package.resolved file?
This file exists to make sure
that when people are using --
people across my team are using
the Lunch project at the same
commit, that they're all getting
the same version of DesignFont.
If this file -- if the
Package.resolved file didn't
exist, you might end up in a
situation where multiple people
on your team are using the same
version of your project, but
without consistency across the
versions of packages that you're
using.
So an incredibly important thing
to note is to check in your
Package.resolved file.
Otherwise, you might end up
using inconsistent versions of
packages across your team.
So it's incredibly important
that this file is checked in.
And then be sure to look out for
new versions of your packages.
And then, update with intent.
Otherwise, you might miss out on
a few key bug fixes and API
improvements that your packages
are providing for you.
So that's how you update
packages.
Let's go into a little bit more
of an advanced example on how to
resolve package conflict, which
will bring together a lot of the
different topics we've talked
about today.
So I launch a project to change
the font that we're using for
our price inside of the Lunch
application.
The system font that I want to
use is available in DesignFont
at Version 2, and this helps
keep a consistent version of the
fonts we're using consistent
across the applications our team
owns.
So let's do this.
In order to use the DesignFont
package, we need to create a
direct dependency between Lunch
and DesignFont.
And then, because DesignFont at
Version 2 is what released this
new font, we want to use that
version specifically.
So we go to Xcode, and we go
through the Add Package
workflow.
We click DesignFont as it's in
our favorite accounts.
And we say Next.
We choose to up to the next
major version, because we want
to be open to any updates or new
versions that DesignFont
releases.
And then we click Next.
Here, we run into a package
resolution error, because of our
new dependency requirement on
DesignFont from 2 to the next
major version.
So let's look into what's
happening here and debug what's
going on.
One thing that's really
important to remember when
debugging package resolution is
to look at the full picture.
We want to think of every
requirement that is that is
affecting our packages and not
just narrow in on what we've
just added.
So we take a step back to how
this was working before.
Lunch had a great dependency on
DesignTheme at Version 1.
And DesignTheme had a direct
dependency on DesignFont at
Version 1.2.1, using the version
requirement 1 to the next major
version.
And then when we added our
direct dependency between Lunch
and DesignFont from 2 up to the
next major version, we ran into
the package resolution conflict.
You'll notice that DesignTheme's
requirement on DesignFont 1, up
to, but not including, 2.
And Lunch's requirement on
DesignFont 2, but not in -- up
to, but not including 3, can't
select one version that matches
both of those requirements.
And so in SwiftPMs integration
into Xcode, you can only have
one version of a package in a
workspace.
And this is explaining why we're
seeing the package resolution
error here.
Xcode can't possibly pick one
version that satisfies both
version requirements.
Going about fixing this is
really situation specific.
But typically, when I run into
package resolution errors like
this, I want to look at the
newer versions of packages that
are available to me.
And then I can see if those
newer versions have provided any
updates to their sub
dependencies.
In this case, I noticed that
there's a Version 2 of
DesignTheme that we haven't
really looked at yet.
So let's go to GitHub and look
at our DesignTheme's version
requirements.
When we go to GitHub, we can
look in the dependencies array
of the Swift Package Manifest.
And here, we see that
DesignFont's version requirement
has updated from 2 -- from 1 to
the next major version to be 2
to the next major version.
This matches with the version
requirement that we were trying
to add between Lunch and
DesignFont.
So if we can update
DesignTheme's version
requirement to be resolving at
Version 2, we can now
satisfiably add a direct
dependency between Lunch and
DesignFont.
So let's go about doing that and
update the major version of
DesignTheme.
Previously, Lunch was using
DesignTheme up from 1 to the
next major version.
We want to change that so that
it's now using 2 up to the next
major version.
So we go back to Xcode.
And now, we just click on the
DesignTheme package inside of
the Project Editor.
And then we're back to an edit
version rule sheet.
Here, we were previously
specifying 1 up to the next
major version.
But now we want to change that
to be 2 to the next major
version.
So we simply change our 1 to a 2
and then click Done.
And now, the update operation
occurs.
And we can see that DesignTheme
is at Version 2, and DesignFont
is at Version 2.
But one important thing happens
here.
When we update from one major
version to another, we run into
a build failure.
And this is because when going
from one major version of a
package to another, there can be
API changes.
And this can potentially cause
breaking changes inside of your
project.
This means that when you update
from one major version to
another, you should be prepared
to change some APIs, or change
however the APIs are working
inside of the new version of the
package.
This can be very small changes,
or it can be a little bit more
involved.
In this case, we've done all the
work for you to spend more time
on packages.
So when we go back to the Lunch
application, our build errors
are resolved.
And we're using the DesignTheme
at Version 2 successfully.
So our build is now succeeding.
And we want to add a direct
dependency between Lunch and
DesignFont.
Because now that we're at
Version 2 of DesignTheme, we can
add the version requirement we
wanted to.
So we go back to Xcode, and we
go through Add Package workflow.
We pick DesignFont.
We select 2 to the next major
version.
And now, we can link the
DesignFont library with our
application.
And now inside of the Project
Editor, we can see that we're
successfully using DesignFont
from 2 to the next major
version.
Now that we've done this, we can
now import the content inside of
DesignFont, successfully use its
library, and then with a few
small code changes, to update
the font of the price.
So we've now successfully done
that and resolved our package
conflict.
We've updated our package
versions, and we've successfully
went over how to debug package
resolution inside of Xcode.
So we went over a lot of things
today.
We talked about how you can
start using Open Source packages
inside of your projects and
quickly start using its APIs.
We took a closer look at what a
package is, what -- and how the
Package.swift manifest brings it
all together.
We went over how Xcode selects
the versions of packages to use
inside of your project.
And then we went over how you
can keep those versions
up-to-date and keep getting new
updates for your packages.
We then took a look at a more
advanced example of how to
resolve package conflicts, which
taught us how to debug packages,
and how to update package
versions.
There are a ton of packages
already available on GitHub
today.
And we recommend that you will
look through these packages and
see where you can incorporate
them in your existing
applications.
But we're not done there.
There's a session tomorrow that
my colleagues, Ankit and Boris,
are presenting on, on how to
create Swift Packages.
This session will go into even
more detail about what a package
is, how to edit packages, the
SwiftPM Open Source tool, and
much, much more.
This session will help you
become even more of an expert on
how packages work inside of
Xcode, and you won't want to
miss it.
And then if you have any more
questions for the SwiftPM team,
we'll be at the Swift Open Hours
Lab right after this session.
And then also, there are two
more sessions throughout the
week, one on Thursday at 12:00
in the Swift Packages Lab, and
another of the same name at
Friday at 12:00.
Thank you so much for coming
today, and we hope you have a
great rest of your week.
Thank you.
[ Applause ]