Hive Hive
Sign in

fix(cli): avoid duplicate SwiftPM C target public headers

GitHub issue · Closed

Metadata
Source
tuist/tuist #11638
Updated
Jul 5, 2026
Domains
Generated projects
Details

What changed

  • Changed SwiftPM .directory C-target header mapping so public headers are enumerated explicitly instead of as one recursive public glob.
  • Deduplicated public headers by the destination framework header basename, preferring headers nested under the sanitized module directory such as spm_headers/nanopb/pb.h.
  • Preserved the broader target header glob as project headers so non-public and wrapper headers remain visible to the generated project without being copied into the framework bundle.
  • Added mapper unit coverage and a fixture-level generator acceptance test that installs, generates, inspects the generated package project, and builds the app through Xcode.

Why

PR #11622 intentionally surfaced SwiftPM C-target headers so sibling shim modules would not break when imported. Directory-style module maps still need public headers because consumers import them as <Module/Header.h>, but the recursive public glob also copied every matching public header into Framework.framework/Headers after Xcode flattened the copy destination.

Packages shaped like firebase/nanopb contain top-level wrapper headers and nested module headers with the same basenames, for example spm_headers/pb.h and spm_headers/nanopb/pb.h. Marking both as Public makes Xcode schedule two copy commands for nanopb.framework/Headers/pb.h, which fails the build with a “Multiple commands produce” error.

Approach

The mapper now resolves the module name alongside module-map generation and uses it when classifying .directory public headers. Public headers are globbed from the SwiftPM public headers directory, filtered with SwiftPM excludes, grouped by basename, and reduced to one framework destination header. When a duplicate exists, the nested module directory wins first, then deeper paths, then lexical ordering for determinism.

This keeps the SwiftPM import surface that directory module maps require while avoiding duplicate framework header copy outputs.

Validation

  • xcodebuild test -workspace Tuist.xcworkspace -scheme TuistUnitTests -destination platform=macOS,arch=arm64 -only-testing:TuistLoaderTests/PackageInfoMapperTests -parallel-testing-enabled NO -quiet CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= COMPILATION_CACHE_ENABLE_CACHING=NO
  • xcodebuild test -workspace Tuist.xcworkspace -scheme TuistAcceptanceTests -destination platform=macOS,arch=arm64 -only-testing:TuistGeneratorAcceptanceTests/GenerateAcceptanceTestAppWithSPMCTargetDuplicatePublicHeaders -parallel-testing-enabled NO -quiet CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= COMPILATION_CACHE_ENABLE_CACHING=NO
  • Red check: temporarily reverted the mapper fix and reran the new acceptance suite; it failed with xcodebuild exit 65.
  • Green check: restored the fix and reran the same acceptance suite; it passed.
  • git diff --check
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