• GitHub
  • Bluesky
  • Mastodon
  • Twitter
  • LinkedIn
  • Stack Overflow
  • RSS
  • Pinning Swift Package Versions

    Predictable SPM Package Versions Across All Machines

    Author:

    The hero image for this article. It's a bunch of packages on a conveyor belt.

    Introduction

    Today I'm going discuss a small but important aspect of working on Swift projects. As soon as the project leaves your computer and will be built on other machines, you're going to have to deal with Swift Package versions in one form or another.

    Non-deterministic behavior always costs a lot of time. For example tests that work on your machine start failing on the CI, or new team mates cannot build the project. Because of this, we need to make sure that whenever a new developer or the CI pulls the project's code, it works exactly as when you pushed it from your machine.

    One of the issues that crops up often are discrepancies between package versions when using the Swift Package Manager for managing external dependencies.

    When you are dealing with Swift applications at even modest scale, you always should strive to minimize the amount of non-deterministic build behavior to the minimum. Just dealing with the odd curveball Xcode throws by itself from time to time is already enough.

    A Poll showing that 34% of all respondents pins all of their packages, 36% pins a few problematic ones. But 20% doesn't and 10% didn't even know it existed

    This might not be a new concept for all of you, according to this poll the majority of you already use version pinning. But you still might learn a new thing or two from this article even if you do.

    Swift Package Manager Does Not Really Lock Its Files

    What surprises most people coming from other platforms or CocoaPods is that Package.resolved is not really comparable to a .lock file. Whenever a teammate checks out the project and Xcode starts to look for the Swift Packages, it simply takes the most recent package version that fits the Dependency Rule in Xcode.

    This can lead to unexpected issues where your CI (Continuous Integration Server) or your new team mate gets a newer version of the package than you have cached on your local machine, and they might start getting issues you cannot reproduce on your local machine.

    An overview of Swift Packages in a project. Most of them are Up To Next Major Version but two are at an Exact Version Not all developers pin all of their package versions

    How To Pin a Swift Package Version

    Pinning a Swift Package version is very easy. From the same screen you add your dependencies, you'll find a Dependency Rule column. The same drop down menu exists when you add a new dependency:

    A drop-down menu showing all of the options for changing a dependency rule

    The last two options can be really handy whenever you have an issue that is fixed in a given branch or commit, but haven't been properly released yet. Picking a specific commit hash can also prevent against bad actors trying to inject code into compromised packages, because the version can be spoofed, but the commit hash can not.

    Advantages of Pinning a Swift Package Version

    1. Projects Work Reliably Across Machines

    Once you have your iOS, Xcode, Swift and dependency versions locked, you can have reliable builds across all machines. The only thing that can still wreak havoc are your project files. This issue can be solved using a project generation tool like Tuist.

    2. Less People Deal With Fixing Version Issues

    Instead of version issues popping up at random when somebody or some machine tries to build the project after a problematic new version of a package was released, issues now happening at predictable moments:

    Because these issues happen at predictable moments, developers realize much quicker the stems from a package version issue, instead of dismissing the issue as just an Xcode fluke at first.

    3. More Predictable Caching

    Since you won't be dealing with more than two versions between active branches, any machine trying to build the project typically already has exactly the dependency needed. This can speed up build times, especially when the CI verifies the package checksums and skips downloading when they already match what is cached.

    Issues When Pinning Versions

    New Xcode Versions

    Every time a new Xcode version reaches its beta stage, developers that publish popular packages will download them and fix any issues that might arise in the new version. This means that any project that updates its packages to accommodate the next major version typically includes those fixes by the time the final version of Xcode is released.

    This is not the case when you pin your dependencies. Every time a new Xcode version gets released any pinned version might break. However, in larger projects there is always a conservative approach towards new Xcode versions, and there will always be a specific green lighted version of Xcode to use. New Xcode versions have a green lighting procedure even without considering package versions breaking, so it's usually just a small extra burden for larger organizations.

    Missing Out on New Features

    Packages evolve, and some developers might find that one needed piece of functionality exists only in a newer version of the package. Instead of simply updating the package, this developer now needs to go through the procedure of getting approval to pin a higher version of the package before being able to use it.

    Security Issues

    This is a particular nasty one, because most other issues just announce themselves by builds failing or developers complaining. But a security issue can sit there for years. Exposing your user's secrets, compromising bank details or even downloading malware.

    There is no way around this besides manually checking all of your dependencies from time to time. One GitHub feature that can really help is Dependabot, that quite recently got Swift support. It will check all of your dependencies automatically, and send an alert automatically when there's a security issue.

    Increased Manual Maintenance

    Since your packages don't get updates by accident anymore, they will be stuck perennially on the same version. Given the risk of security issues, but also the chance that it's functionality slowly starts deteriorating against newer version of iOS and Xcode it's required to check every dependency at least every three months for updates, and evaluate if any new version is worth upgrading to.

    Conclusion

    Pinning the versions of your packages to an exact Version or Commit is a great practice, especially for larger teams. Dealing with the extra overhead to manually monitor and update the package is a fair exchange for taking this headache out of your development chain.

    Thank You for Contributing

    I want to thank everybody that voted and commented on my poll about pinning package version on LinkedIn, especially:

    See You After WWDC 2024

    I'm going to take a slight break from writing until WWDC 2024, to study other topics that do not result into blog posts and take a slight break, so I'm fresh again for the onslaught of new information. I sure hope we can find lots of new interesting stuff around Swift and developing for Apple platforms in general.

    An image of the author
    is an Apple platforms developer that likes to write and discuss about technical subjects.

    Do you:

    Drop me a line!

    Contact Me