Hive Hive
Sign in

swift-syntax prebuilts are not used when a macro package declares a test target

GitHub issue · Open

Metadata
Source
tuist/tuist #14216
Updated
Oct 15, 2026
Details

What happened?

Tuist builds swift-syntax from source even though SwiftPM downloaded the swift-syntax MacroSupport prebuilt and recorded it in workspace-state.json. This happens whenever the package that declares the macro also has a test target that reaches the macro through its runtime library. That’s the usual shape for macro packages, for example swift-composable-architecture, swift-dependencies, swift-case-paths and swift-perception.

The test target alone is enough, even though Tuist never generates test targets for remote packages.

Expected: the macro target links the prebuilt (-lMacroSupport) and no swift-syntax targets are compiled.

Actual: the macro target depends on the swift-syntax source targets and 17 of them are compiled. Removing the test target from the package manifest restores the prebuilt.

Cause

PackageInfoMapper.prebuiltEligibleTargets(packageInfo:packageType:) (source) adds every test target whose dependency closure reaches a macro to hostRoots:

let hostRoots = macroTargets.union(macroTestTargets)
let hostReachableTargets = dependencyClosure(from: hostRoots, includeHostOnlyTargets: true)
let nonHostReachableTargets = dependencyClosure(
from: allTargetNames.subtracting(hostRoots),
includeHostOnlyTargets: false
)
guard hostReachableTargets.isDisjoint(with: nonHostReachableTargets) else {
return []
}

A test like MacroDependencyTests → MacroDependency → MacroDependencyMacros pulls the runtime library MacroDependency into hostReachableTargets. MacroDependency is also a non-host root, so the sets overlap and prebuilts are disabled for the whole package.

Two things contribute:

  1. Tests that are never generated are still considered. map(target:…) drops .test targets unless packageType.includesTestTargets(includeLocalPackageTestTargets:) is true (source). prebuiltEligibleTargets doesn’t apply the same check.
  2. The overlap check is broader than it needs to be. The overlapping runtime library doesn’t depend on swift-syntax, so it can’t conflict with the prebuilt.

Since an ineligible macro target keeps its swift-syntax source dependencies, one affected package anywhere in the graph means swift-syntax is compiled from source.

Possible fix

Only add macroTestTargets to hostRoots when packageType.includesTestTargets(includeLocalPackageTestTargets:) is true. With that change, the reproduction links -lMacroSupport, compiles no swift-syntax targets, and builds and runs. A narrower overlap check (only failing on overlapping targets that themselves depend on a prebuilt product) would also cover packages whose tests are generated.

The generated_app_with_swiftpm_prebuilt_macro_dependency fixture has no test target, which is why BuildAcceptanceTestSwiftPMPrebuiltMacro doesn’t catch this.

Disclosure: AI was used to investigate this problem and work up the example and write the report, though I have reviewed it thoroughly and am creating this issue by hand.

How do we reproduce it?

Reproduction project attached: tuist-prebuilt-repro.zip

It’s the generated_app_with_swiftpm_prebuilt_macro_dependency fixture plus one test target in MacroDependency/Package.swift:

.testTarget(
name: "MacroDependencyTests",
dependencies: ["MacroDependency"]
),

Tested with Tuist 4.212.0, Xcode 27.1 (Swift 6.4), Apple silicon.

unzip tuist-prebuilt-repro.zip && cd tuist-prebuilt-repro
mise trust && mise install # pins tuist 4.212.0 and sets TUIST_USE_SWIFTERPM=0
tuist install
tuist generate --no-open
xcodebuild build -workspace App.xcworkspace -scheme App CODE_SIGNING_ALLOWED=NO
  1. Tuist/.build/workspace-state.json lists the swift-syntax MacroSupport prebuilt.
  2. grep -rl --include=project.pbxproj -e -lMacroSupport . finds nothing.
  3. The build log shows targets from the swift-syntax project being compiled.
  4. Remove the .testTarget, then rerun tuist generate and xcodebuild. -lMacroSupport is now linked and no swift-syntax targets are compiled.

TUIST_USE_SWIFTERPM=0 makes the prebuilt show up in workspace-state.json every time. With SwifterPM and a warm cache, the prebuilts array can be empty, which hides this issue. That’s a separate problem.

tuist-prebuilt-repro.zip

Error log

There’s no error. Tuist falls back to building swift-syntax from source without any message.

Tuist/.build/workspace-state.json (the prebuilt is available):

{
"identity": "swift-syntax",
"version": "602.0.0",
"libraryName": "MacroSupport",
"path": ".../Tuist/.build/prebuilts/swift-syntax/602.0.0/swiftlang-6.4.0.34.1-macosx27.0-MacroSupport"
}

Targets built by xcodebuild with the test target present:

(in target 'App' from project 'App')
(in target 'MacroDependency' from project 'MacroDependency')
(in target 'MacroDependencyMacros' from project 'MacroDependency')
(in target '_SwiftSyntaxCShims' from project 'swift-syntax')
(in target 'SwiftBasicFormat' from project 'swift-syntax')
(in target 'SwiftCompilerPlugin' from project 'swift-syntax')
(in target 'SwiftCompilerPluginMessageHandling' from project 'swift-syntax')
(in target 'SwiftDiagnostics' from project 'swift-syntax')
(in target 'SwiftOperators' from project 'swift-syntax')
(in target 'SwiftParser' from project 'swift-syntax')
(in target 'SwiftParserDiagnostics' from project 'swift-syntax')
(in target 'SwiftSyntax' from project 'swift-syntax')
(in target 'SwiftSyntax509' from project 'swift-syntax')
(in target 'SwiftSyntax510' from project 'swift-syntax')
(in target 'SwiftSyntax600' from project 'swift-syntax')
(in target 'SwiftSyntax601' from project 'swift-syntax')
(in target 'SwiftSyntax602' from project 'swift-syntax')
(in target 'SwiftSyntaxBuilder' from project 'swift-syntax')
(in target 'SwiftSyntaxMacroExpansion' from project 'swift-syntax')
(in target 'SwiftSyntaxMacros' from project 'swift-syntax')

Targets built with the test target removed:

(in target 'App' from project 'App')
(in target 'MacroDependency' from project 'MacroDependency')
(in target 'MacroDependencyMacros' from project 'MacroDependency')

macOS version

26.7.1

Tuist version

4.212.0

Xcode version

27.1

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments

No GitHub comments yet.