What happened?
With Tuist 4.201.0, tuist install produces different Package.resolved files depending on whether TUIST_USE_SWIFTERPM is disabled or enabled.
The minimal DemoKit project below intentionally declares:
https://github.com/apple/swift-atomics.git to test preservation of the .git suffix
https://github.com/Kyle-Ye/ScreenShieldKit to test preservation of GitHub owner/repository casing
SwiftPM preserves the source-control location spelling from the manifest, including casing and whether the URL has a .git suffix. swifterpm instead lowercases the GitHub owner/repository path and removes the .git suffix.
| Dependency |
TUIST_USE_SWIFTERPM=0 |
TUIST_USE_SWIFTERPM=1 |
| ScreenShieldKit |
https://github.com/Kyle-Ye/ScreenShieldKit |
https://github.com/kyle-ye/screenshieldkit |
| swift-atomics |
https://github.com/apple/swift-atomics.git |
https://github.com/apple/swift-atomics |
Both resolver modes produce the same package identities, versions, revisions, and originHash. Both commands also complete successfully, but switching resolver modes dirties the tracked lockfile even though the dependency graph has not changed.
I also verified the same output with Tuist 4.202.2 by passing the SwiftPM-generated lockfile to TUIST_USE_SWIFTERPM=1 tuist install.
Expected behavior
swifterpm should preserve source-control location strings already present in Package.resolved, or serialize them in the same form as SwiftPM, so switching resolver modes does not create unrelated lockfile changes.
How do we reproduce it?
Create the following DemoKit project.
DemoKit
├── mise.toml
├── Project.swift
├── Sources
│ └── DemoKit.swift
└── Tuist
└── Package.swift
mise.toml:
[tools]
tuist = "4.201.0"
Project.swift:
import ProjectDescription
let project = Project(
name: "DemoKit",
targets: [
.target(
name: "DemoKit",
destinations: .macOS,
product: .framework,
bundleId: "dev.tuist.DemoKit",
sources: ["Sources/**"],
dependencies: [
.external(name: "Atomics"),
.external(name: "ScreenShieldKit"),
]
),
]
)
Sources/DemoKit.swift:
public enum DemoKit {}
Tuist/Package.swift:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "DemoKitDependencies",
dependencies: [
.package(
url: "https://github.com/apple/swift-atomics.git",
exact: "1.2.0"
),
.package(
url: "https://github.com/Kyle-Ye/ScreenShieldKit",
exact: "0.1.1"
),
]
)
#if TUIST
import ProjectDescription
let packageSettings = PackageSettings()
#endif
Run both resolver modes from clean project-local state:
mise trust
mise install
rm -rf Tuist/.build
rm -f Tuist/Package.resolved
TUIST_USE_SWIFTERPM=0 mise exec -- tuist install
cp Tuist/Package.resolved /tmp/Package.resolved.swiftpm
rm -rf Tuist/.build
rm -f Tuist/Package.resolved
TUIST_USE_SWIFTERPM=1 mise exec -- tuist install
diff -u /tmp/Package.resolved.swiftpm Tuist/Package.resolved
The resulting diff is:
"identity" : "screenshieldkit",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/Kyle-Ye/ScreenShieldKit",
+ "location" : "https://github.com/kyle-ye/screenshieldkit",
"state" : {
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-atomics.git",
+ "location" : "https://github.com/apple/swift-atomics",
"state" : {
Error log
There is no process error. Both tuist install commands exit successfully. The unexpected output is the Package.resolved diff shown above.
Environment
- macOS 26.5.2
- Xcode 26.3 (17C529)
- Tuist 4.201.0
- Also reproduced with Tuist 4.202.2
swifterpm 0.9.0