Package.resolved should go in the .gitignore

it is still extremely difficult to develop a project and its dependencies in tandem

You're not wrong, there. What I've wound up doing is having lines in my main Package.swift that list the dependencies on my local system, alongside the same dependency listed with its git URL, and I comment out the repo dependency until I'm actually ready to release that library. It's manual, it's ugly, and it sometimes leads to successive commits with only whitespace changes just to get the build chain to recompile stuff, but it works.

2 Likes

As an aside (and perhaps this should be a separate post), I do wish that Package.resolved was called something else.

If it's really intended as a machine-readable file, it would be better off with a hidden-by-default name such as .Package.resolved.

If it must stay visible, it would be a major quality-of-life improvement if it didn't come before Package.swift when sorted alphabetically! Having both causes filename completion in shells to have to ask which you want, and/or pick a default - and the first choice will be .resolved, which is invariably the one you didn't want.

7 Likes

The Package.resolved is a main source of conflicts for us in our main Xcode project, on a semi-large team since we maintain a few in-house packages which are versioned multiple times a day.

All our dependencies are always pinned to exact versions, is there an issue adding the package.resolved to the gitignore if only to remove the need to resolve conflicts on the file?

Is it still possible with exact versions that having the file gitignored could result in team members working against different versions of the packages?

It depends on if you can guarantee the version will not be reused perhaps you are able to do exact pinning on the exact version of the module as a substitute of including the packages resolved file. I think it is one of those things where there is no perfect frictionless path but just alternatives each with their own pros and cons.

if you commit the file instead of gitignore you avoid a lot of "it builds on my machine" discussions. In many use cases you want the build machine to resolve with exact same swift code as your machine. You can always put your own gitignore rule, it is not that hard.

4 Likes

If you are planning to use Xclod cloud you must include it in git.

Apple doc say

If you use Swift package dependencies in your project, make sure to include the Package.resolved file in your Git repository and commit any changes to it

source: Making dependencies available to Xcode Cloud | Apple Developer Documentation

3 Likes

Just to clarify some things.

The title is missing one important context info. Whether you are speaking in a xcodeproj/xcworkspace context (thus located in your .xcworkspace/spm or something like that), or in a Package only context (thus at the root of your package).

In the case of a project:
this is keeping track of all your dependencies version (i.e. == your podlock file). So unless you want to keep exact version (or commit hash to be more secured) of each dependency in your Package.swift, you should indeed keep track of that file in git.

In the case of a Package:
I imagine that if your lib dependecies respect correctly the principles behind the major.minor.patch (sementic versionning), you should be ok to ignore this Package.resolved file because your lib should be working with all the versions that you are considering in the i.e. upToNextMinor (or major if you dare be confident enough about your dependencies).

This is the number one reason for the pins file. You choose a major version and define it in the manifest as supporting from the lowest minor version possible. Then you pin at that lowest version. Since your pins are checked in and your package is consistently resolved at its lower bound whenever it is the root package, you can develop safely without worrying about violating the contract you made in the manifest. As you tinker, build and test, anything that works now is guaranteed to work with future versions, which covers your entire declared window.

If you do not do that, and you pin high or do not check pins at all, then the reverse is not true. What works now may not have worked in the past (because you may be using a feature that was added in a minor version), and your window contains a lot of the past. Some projects attempt to evade the risk by declaring upToNextMinor so that the past window is restricted to bug fixes. But (1) that still does not guarantee you are not relying on a fixed bug and (2) narrow windows lead to incompatibilities between dependencies much faster. (For example, the Swift toolchain is built from branches instead, so its teams do not care, but if you try to use all its various component packages in the same dependency graph, you often cannot due to several of them unnecessarily requiring mismatched minor versions in their manifests.)

1 Like

Apple recommend committing the Package.resolved. This is covered in the section Coordinate package versions across your team.

When collaborating on a project, make sure everyone uses the same version of a package dependency. When you add a package dependency to a project, Xcode creates the Package.resolved file. It lists the specific Git commits to which each package dependency resolves and the checksum of each binary dependency. Commit this file in Git to ensure that everyone is using the same version of a package dependency.

2 Likes

Would you still recommend checking in Package.resolved if the remote package dependencies are specified with "exact" rather than "from"?

When would you prefer one over the other?

Yes, in that it's trivial to keep resolved in git, and that it's generally bad practice to exactly lock all of your dependencies, as you lose any bug fixes you may want. Locking to exact versions should be the exception, not the rule.

2 Likes

Based on everything I've read here I agree it makes sense to commit Package.resolved.

But I thought I'd try to see what other large projects are doing. Interestingly, Apple's own open source swift projects (swift-algorithms, swift-async-algorithms, swift-nio) put it into .gitignore. So that seems confusing.

It is typical for leaf projects such as apps to want to commit the resolved file, but it isn't as interesting for libraries. Some still do it to get 100% consistency on CI. In any case, I think that's where the mismatch comes from as most of Apple's packages are libraries.

1 Like

@NeoNacho Okay, that makes sense, thanks. I was unsure in this post when people were discussing apps vs libraries.

If you include a Package.resolved file in a library, will SwiftPM ignore it when a client adds that library as a dependency?

Yep, only one Package.resolved file will be considered. That can be the one in your root (top-level) package or one provided by the Xcode project/workspace you're opening.

:+1: Got it, thanks for the help!

That sounds backwards.

The leaves I deal with benefit little from pins. They have no interest in old versions, so they bump the manifest entries as soon a dependency version is released. The only thing they gain from pins is historical accuracy when it comes to checking out old states to triage a bug.

It is the libraries for whom pins are vital. These need to continually test at the lowest declared compatible version. If they do not, then they soon end up accidentally using something new so that it’s declared compatibilities are inaccurate and do not build. The library itself does not notice until some client’s other dependencies narrow the graph into the broken zone, at which point he reports it back, angry at the library for lying.

Apple’s “packages” abandon the pins because they eschew semantic versioning altogether (at least the toolchain components). They are not really packages, in that nothing outside the toolchain is intended to ever depend on them, and doing so is not safe. Since the components are in different repositories and they do not want stable versioning of individual components, they just point at each other’s branches in an eternal state of flux. Since the many “packages” are really one product, they want each to test with the cutting‐edge state of the rest of the toolchain, not with some stale state. Hence, for them, the pins are a nuisance and their effect is reduced by having git ignore them.

5 Likes

It feels like NeoNacho and you are saying the same things?
A lib only needs the Package.swift file to pin the ranges it accepts for its dependencies, hence doesn't really need the Package.resolved for pinning (appart from some CI consistancies as mentionned by NeoNacho). + the Package.resolved of the dependencies are ignored by spm anyway.

As opposed to "final" products where you'd need to keep track of what was the exact versions of each lib (Package.resolved) you delivered inside your product (mendatory for medical devices for example), right?

@MaxMacleod this is why I feel like it's important to give the context in which you are along with giving an answer/quote (in this one case indeed the Package.resolved file is important and should be tracked, but it's not always the case)

No. Package.swift declares to clients what the package is compatible with, whereas Package.resolved declares to contributors what the package should be developed with.

For a root application, the difference is irrelevant, since only one resolved graph is ever relevant at a time. Specifying exact dependencies in Package.swift best represents your intentions for your code. At that point, Package.resolved is entirely redundant and ignorable.

For a library, the difference is extremely important, because narrowing the ranges in Package.swift increases the odds of ruling out a sister library from being used alongside yours due to conflicting constraints. To be most useful, you generally want your Package.swift to declare ranges that are as wide as possible. When your Package.swift is full of ranges, then you need a separate mechanism to ensure that your testing is consistent and actually tests what you think it tests. That is what Package.resolved is for. Due to the meaning conveyed by semantic versioning, if you pin at the bottom of a range and it works, you know that it also works with later versions. The reverse is not true; if you pin at the top, you do not know if it actually works with earlier versions. So the precise pin you choose makes a big difference. As a library developer, you need to prevent inadvertent updates during development (.resolved), while permitting newer versions when used by clients (.swift). So you need to consciously craft, audit and track each file separately.

With regards to whether or not one ought to explain all this to clarify the context when recommending committing Package.resolved, it is usually more words than it is worth. For an application, the pins file is irrelevant, not counterproductive. So any effort put into learning the pattern or into registering the file as ignored in certain cases is wasted effort that gains you nothing. And the extra complexity in your recommendation might confuse the beginner. If you just commit the pins file all the time (as I do), you get all the benefits with no down sides. This is why SwiftPM’s templates have it this way.

1 Like