Package.resolved Stability#
Package.resolved stability refers to a set of mechanisms in Tuist's swifterpm module that ensure Swift Package Manager's lockfile remains consistent across machines, CI environments, and developers β regardless of how a dependency's URL is declared. Instability in this file causes unnecessary diffs, broken caches, and CI failures.
Three distinct problems are addressed: URL canonicalization (normalizing lockfile locations so equivalent spellings produce the same output), HTTPS fallback (allowing SSH-declared dependencies to authenticate via token in CI), and subprocess-based resolution (decoupling Tuist from SwiftPM internals to reduce churn).
URL Canonicalization#
The core normalization entry point is SourceControlLocations.canonicalResolvedFileLocation(_:), called during every Package.resolved write via ResolvedPin.normalizedRemoteSourceControlLocation(_:).
The rules are provider-aware :
- GitHub and known GitLab hosts: scheme is lowercased, host is lowercased, path is lowercased, and the
.gitsuffix is stripped. For example,https://github.com/CombineCommunity/CombineExt.gitβhttps://github.com/combinecommunity/combineext. - Generic/private hosts: only the scheme and host are lowercased; the path and
.gitsuffix are left unchanged to avoid breaking path-sensitive Git servers . - SSH colon-separated form (e.g.
git@github.com:owner/repo.git): parsed byColonSeparatedGitLocation, with host lowercased and path normalized per the same provider-aware rules.
The host-based gate is canonicalizesProviderPath(host:), which returns true only for github.com and known GitLab hosts.
Pins are also sorted (by lowercased identity, then lowercased location) and deduplicated before writing, ensuring pin order is deterministic .
HTTPS Fetch Fallback#
SSH-declared private dependencies fail on CI runners without SSH keys. SourceControlLocations.fetchCandidates(_:) generates a prioritized list of URL alternatives for any fetch attempt :
- The original location is always tried first.
- For GitHub/GitLab: both the HTTPS
.gitand SSH.gitforms are appended .
This means an SSH-declared dependency like git@github.com:acme/private-lib automatically gets https://github.com/acme/private-lib.git as a fallback .
Authentication for the HTTPS fallback is handled by GitTransportAuth.configArguments(for:), which injects a http.<base>.extraheader=Authorization: Basic β¦ git config argument (via -c) using GITHUB_TOKEN/GH_TOKEN or the gh CLI token. This mirrors what actions/checkout does and keeps the token out of on-disk git config . SSH locations return no extra arguments, leaving ssh-agent in charge .
Subprocess-Based Resolution#
Prior to PR #11258, resolution ran in-process against SwiftPM's internal symbols. The bump to swifterpm 0.8.9 moved resolution into a SwiftPM subprocess boundary, decoupling Tuist from SwiftPM's internal API churn. This change also fixed binary artifact downloads and eliminated a category of spurious Package.resolved diffs that appeared between runs .
Key Files#
| File | Purpose |
|---|---|
swifterpm/Sources/swifterpm/GitHub.swift | URL canonicalization, fetch candidates, HTTPS transport auth for GitHub & GitLab |
swifterpm/Sources/swifterpm/Models.swift | ResolvedPins / ResolvedPin models; normalizedForResolvedFile() write path; ResolvedFile.write() with idempotent write guard |
swifterpm/Tests/swifterpmTests/GitHubTests.swift | Tests covering canonicalization, fetch candidates, and transport auth |