Hive Hive
Sign in

feat(cli): add opt-in SwifterPM install resolution

GitHub issue · Closed

Metadata
Source
tuist/tuist #11099
Updated
Jun 24, 2026
Domains
CLI Distribution Generated projects
Details

No linked issue.

Adds an opt-in SwifterPM-backed path for tuist install dependency resolution. When TUIST_USE_SWIFTERPM is present, Tuist shells out to SwifterPM for resolve and update, preferring a bundled vendor/swifterpm next to the Tuist executable and falling back to swifterpm on PATH. The default behavior continues to use swift package.

This also pins SwifterPM 0.5.1 in mise, installs it in the CLI release workflow, bundles it into tuist.zip, and codesigns/notarizes it with the rest of the CLI release payload.

tuist generate now reuses the package-info JSON cache that SwifterPM writes under .build/swifterpm/package-info when the cache is present and fresh. That avoids shelling out to load every package manifest after a SwifterPM-backed install. If the cache is missing, has an unsupported schema version, cannot be decoded, or an entry is older than its Package.swift, generation falls back to the existing manifest loader.

How to test locally

  • mise lock github:tuist/swifterpm --platform linux-x64,macos-arm64,macos-x64
  • swiftformat cli/Sources/TuistLoader/Loaders/SwiftPackageManagerGraphLoader.swift cli/Tests/TuistLoaderTests/Loaders/SwiftPackageManagerGraphLoaderTests.swift
  • PATH="$(dirname "$(mise which swifterpm)"):$PATH" TUIST_USE_SWIFTERPM=1 <built-tuist> install
  • PATH="$(dirname "$(mise which swifterpm)"):$PATH" TUIST_USE_SWIFTERPM=1 <built-tuist> generate --no-open
  • xcodebuild build -workspace Tuist.xcworkspace -scheme tuist CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
  • xcodebuild test -workspace Tuist.xcworkspace -scheme TuistUnitTests -only-testing TuistSupportTests/SwiftPackageManagerControllerTests -only-testing TuistLoaderTests/SwiftPackageManagerGraphLoaderTests CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""
Comments
F
fortmarek Jun 5, 2026

[P2] Consider linking SwifterPM as a library instead of bundling and shelling out to its CLI.

Because Tuist controls SwifterPM, packaging a second signed binary and invoking it through string arguments feels heavier than necessary for a supported integration. It also makes the contract harder to test and easier to drift: Tuist can pass flags that SwifterPM parses differently or ignores, errors are only process-level, and release packaging now has to keep Tuist and the bundled SwifterPM binary aligned across platforms. I’d consider exposing a SwifterPMCore library target, keeping the swifterpm executable as a thin wrapper around it, and having Tuist call the typed resolver/restorer APIs directly. Shelling out is fine for an opt-in spike, but I would not make that the long-term integration shape for a tool we own.

P
pepicrft Jun 5, 2026

@fortmarek I agree. I changed the integration to link SwifterPMCore directly and removed the vendor/swifterpm release packaging path. tuist install now stays on the existing swift package path unless TUIST_USE_SWIFTERPM is present, and the opt-in path builds a typed SwifterPMResolutionRequest instead of passing a CLI string contract through a subprocess.

While validating the generated Xcode project I hit a SwifterPMCore compile issue with swift-subprocess trait-gated .data(limit:) helpers in generated projects. I opened https://github.com/tuist/swifterpm/pull/13 to make the core target use the non-trait .bytes(limit:) API. Once that is released, I’ll move this PR to the new tag and finish the tuist install validation here.

P
pepicrft Jun 5, 2026

@fortmarek Follow-up on this: SwifterPM 0.6.1 is out and this PR now depends on SwifterPMCore from that release, so there is no bundled swifterpm binary and no subprocess boundary for the opt-in path.

I also validated the existing .build case in this repo. With an existing SwiftPM .build, TUIST_USE_SWIFTERPM=1 tuist install succeeded, SwifterPM reused .build as its scratch directory, and it replaced only resolved package checkout/download entries with symlinks into its cache. I then ran tuist generate tuist ProjectDescription --no-open and built the tuist scheme in Xcode successfully against those symlinks.

T
tuist[bot] Jun 5, 2026

🛠️ Tuist Run Report 🛠️

Previews 📦
App Commit Open on device
Tuist ae8183e59
Tests 🧪
Scheme Status Cache hit rate Tests Skipped Ran Commit
TuistAcceptanceTests 0 % 0 0 0 ae8183e59
TuistApp 0 % 28 0 28 ae8183e59
TuistUnitTests 0 % 2933 4 2929 ae8183e59
Flaky Tests ⚠️
  • TuistUnitTests: 3 flaky tests (View all)
Test case Module Suite
parseTestStatuses_extractsModuleAndSuiteNames() TuistXCResultServiceTests XCResultServiceTests
parseTestWithCustomLabelXCResult() TuistXCResultServiceTests XCResultServiceTests
parseTestXCResult() TuistXCResultServiceTests XCResultServiceTests
Builds 🔨
Scheme Status Duration Commit
TuistAcceptanceTests 6m 9s ae8183e59
TuistApp 13m 7s ae8183e59
TuistUnitTests 6m 54s ae8183e59
Bundles 🧰
Bundle Commit Install size Download size
Tuist ae8183e59 19.5 MB 14.7 MBΔ +216 B (+0.00%)
P
pepicrft Jun 7, 2026

@fortmarek I went through each requested item against the current Tuist PR and the released SwifterPM 0.6.1 tag:

  • Registry cache key and archive validation: fixed by tuist/swifterpm#12, which is included in 0.6.1. Registry source and archive paths now include registry URL plus checksum, and cached registry archives are SHA-256 validated before reuse.
  • --replace-scm-with-registry: fixed by tuist/swifterpm#12 and wired through this PR. Tuist maps --replace-scm-with-registry and --use-registry-identity-for-scm into SwifterPMResolutionRequest.scmToRegistryTransformation, and SwifterPM applies that transformation in PackageResolver.transformedDependencies.
  • mise and release assets: this PR no longer adds or bundles a swifterpm binary. Tuist links SwifterPMCore directly from Package.swift, and 0.6.1 also publishes the Linux asset, so the old mise binary asset mismatch no longer applies.
  • Manifest cache location: fixed by tuist/swifterpm#12, included in 0.6.1. The package-info cache is written under scratchDir/swifterpm/package-info, and raw manifest dumps use .build/swifterpm/manifests/package.json instead of .swifterpm-manifest.json next to Package.swift.
  • Transitive local package dependencies: fixed by tuist/swifterpm#12, included in 0.6.1. ManifestFileSystemDependencyGraph.collect now walks filesystem dependencies transitively and is used for resolution, package-info cache writing, binary artifact restore contexts, and workspace state.
  • Version metadata: the only remaining real gap was SwifterPM’s user-visible metadata still saying 0.5.1 or 0.3.0. I opened https://github.com/tuist/swifterpm/pull/14 to update the CLI version, Bazel module metadata, plist version, Bazel tool default, README example, e2e example, and the guard test to 0.6.1.

Validation I ran:

  • In SwifterPM: swift test --filter MainTests
  • In Tuist: xcodebuild test -workspace Tuist.xcworkspace -scheme Tuist-Workspace -only-testing TuistSupportTests/SwiftPackageManagerControllerTests CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY=""

The Tuist test passed 11 controller tests, including the opt-in SwifterPM request path, SCM registry transformation forwarding, conflict handling, and update path. While building that selected test target, Xcode compiled through the generated workspace using the existing .build/registry/downloads symlink paths backed by ~/.cache/swifterpm, so the existing .build scenario is still covered locally.