What happened?
Summary
When an external SPM dependency declares a build-tool plugin product in one of its target dependencies: (rather than plugins:), tuist generate fails with:
Could not locate project at path: {project-path}/Tuist/.build/checkouts/<PluginPackage>
Tuist already ignores build-tool plugins during project generation (a plugin-only package yields no project), but it still emits an external-dependency edge pointing at that non-existent project. The two paths are asymmetric, so a plugin pulled in transitively (by a package we consume via .external, not by us directly) crashes generation. This is distinct from #11358 (directly consuming a plugin via .external): here we never reference the plugin - a dependency does - and we cannot change that dependency’s manifest.
Note: If LibPkg instead attaches the plugin the canonical way - plugins: [.plugin(name: "MyBuildToolPlugin", package: "PluginPkg")] - generation succeeds. Only the plugin-product-in-dependencies: form crashes.
Environment
- Tuist
4.201.0-rc.1 (also reproduced on 4.200.5)
- macOS 26.5.1
- Xcode 26.5.0
- SPM integration via
Tuist/Package.swift + .external(name:)
Root cause
cli/Sources/TuistLoader/SwiftPackageManager/PackageInfoMapper.swift (source @ tag 4.201.0-rc.1, commit 2f2ee30):
- Project generation filters plugins:
map(target:) switch (~L715-732) sends .plugin (and .binary) target types to default → return nil; map(packageInfo:) then hits guard !targets.isEmpty else { return nil } (L420), so a plugin-only package produces no project (correctly absent from externalProjects).
- Dependency-edge generation does NOT filter plugins:
resolveExternalDependencies (L240-276) iterates the plugin product‘s targets; ResolvedDependency.fromTarget (L1438) returns .target(name:) (a plugin target is not a known framework), rewritten to .project(target:, path: <checkout>) - an edge to the project that was never generated. The transitive consumer’s dependencies entry resolves via mapDependency (L1127 → else L1194-1200) to .external(name:), which links to that broken edge.
TuistCore.GraphLoader (GraphLoader.swift:77/:99) then throws GraphLoadingError.missingProject (message defined in GraphLoadingError.swift:25).
Asymmetry: plugins are filtered out of project generation but not out of external-dependency-edge generation.
Possible fix (suggestion; needs verification)
In resolveExternalDependencies / ResolvedDependency.fromTarget, skip products whose only targets are .plugin (mirror the per-target plugin filter already used in map(target:)), and/or tolerate a missing project when an .external resolves to a plugin-only product. This matches Tuist’s documented behavior of ignoring SPM build-tool plugins.
How do we reproduce it?
- Use the attached sample project (minimal, self-contained).
- Run:
mise install
mise x -- tuist install
mise x -- tuist generate --no-open
- Expected result:
- Generation succeeds.
- The build-tool plugin is ignored for an externally-integrated package (consistent with how Tuist already drops plugin targets during project generation).
- Actual result:
- Tuist fails to generate the project with error “Could not locate project at path: {project-path}/PluginPkg”
Example project: TUIST-buildtool-plugin-bug-report.zip
Error log
$ mise x -- tuist generate --no-open
Loading and constructing the graph
It might take a while if the cache is empty
✖ Error
Could not locate project at path: {project-path}/PluginPkg
Sorry this didn’t work. Here’s what to try next:
▸ If the error is actionable, address it
▸ If the error is not actionable, let's discuss it in the Troubleshooting & how to
▸ If you are very certain it's a bug, file an issue
▸ Check out the logs at {user-home}/.local/state/tuist/sessions/8F6505BD-2251-403D-B0C2-8309E0D55938/logs.txt
{user-home}/.local/state/tuist/sessions/8F6505BD-2251-403D-B0C2-8309E0D55938/logs.txt:
[2026-06-23T14:47:43Z] [debug] [TuistCacheEE] Using the concurrency limit of 100 for the cache's HTTP connections.
[2026-06-23T14:47:43Z] [debug] [TuistKit] Using cache profile onlyExternal
[2026-06-23T14:47:43Z] [notice] [TuistKit] Loading and constructing the graph ["is": section]
[2026-06-23T14:47:43Z] [notice] [TuistKit] It might take a while if the cache is empty
[2026-06-23T14:47:44Z] [debug] [TuistSupport] Waiting for Swift Package Manager lock at [project-path]/Tuist/.build
[2026-06-23T14:47:44Z] [debug] [TuistSupport] Acquired Swift Package Manager lock at [project-path]/Tuist/.build
[2026-06-23T14:47:44Z] [debug] [TuistLoader] Target MyBuildToolPlugin of type plugin ignored
[2026-06-23T14:47:44Z] [debug] [Noora] Error alert: ✖ Error
- Message: Could not locate project at path: <project-path>/PluginPkg
- Takeaways:
- If the error is actionable, address it
- If the error is not actionable, let's discuss it in the (Troubleshooting & how to)
- If you are very certain it's a bug, (file an issue)
- Check out the logs at {user-home}/.local/state/tuist/sessions/8F6505BD-2251-403D-B0C2-8309E0D55938/logs.txt
macOS version
26.5.1
Tuist version
4.201.0-rc.1
Xcode version
26.5.0