Hive Hive
Sign in

fix(cli): avoid shadowing SwiftPM public headers

GitHub issue · Closed

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

What changed

  • Keeps SwiftPM C directory-target public headers de-duplicated for framework copy destinations while also excluding the shadowed duplicate headers from the generated project header glob.
  • Extends the existing nanopb-style acceptance fixture with a sibling C package that includes <nanopb/pb.h> from a generated header, matching the include pattern that exposed the nested-include regression.
  • Tightens the acceptance assertion so the generated nanopb target may only keep the real framework public headers plus the package-root project headers. Shadowed wrapper headers must not remain in the headers phase.
  • Updates the package mapper unit expectation so top-level wrapper headers such as spm_headers/pb.h and spm_headers/pb_common.h are not emitted as project headers once their nested module equivalents are selected as framework public headers.

Why it changed

The previous duplicate-header fix stopped copying both nanopb wrapper headers and their nested module headers into the framework Headers directory, which resolved the Multiple commands produce ... nanopb.framework/Headers/pb.h failure. However, the top-level wrapper headers were still available through the generated project headers.

That meant projects could still expose the shadowed wrapper headers alongside the nested real public headers. In configurations where module-qualified includes resolve through framework/header-map paths, <nanopb/pb.h> can land on the top-level wrapper spm_headers/pb.h; that wrapper includes "nanopb/pb.h", so resolution can recurse and fail with #include nested too deeply.

Approach

For .directory SwiftPM C targets, Tuist now tracks both:

  • the headers selected as framework public headers after destination de-duplication
  • the public-directory headers shadowed by that selection

The shadowed headers are appended to the project-header glob exclusions. This preserves the real nested public headers for framework consumers while preventing wrapper headers with the same basename from shadowing them through project headers.

Impact

SwiftPM C targets with nanopb-style wrapper headers should avoid both failure modes:

  • duplicate public framework header copy commands
  • recursive wrapper-header resolution when consumers include module-qualified public headers

Validation

  • Red: temporarily reverted only the mapper implementation while keeping the new test/fixture. The focused acceptance command failed with nonPublicHeaderNames equal to ["pb.h", "pb.h", "pb_common.h", "pb_common.h"] instead of ["pb.h", "pb_common.h"], proving the old behavior still emitted the shadowed wrappers.
  • Green: restored the mapper fix and reran the same focused acceptance command. It passed and built the generated app fixture.
  • xcodebuild test -workspace Tuist.xcworkspace -scheme Tuist-Workspace -only-testing TuistGeneratorAcceptanceTests/GenerateAcceptanceTestAppWithSPMCTargetDuplicatePublicHeaders -destination platform=macOS -derivedDataPath /private/tmp/tuist-nested-headers-acceptance CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" COMPILATION_CACHE_ENABLE_CACHING=NO
  • xcodebuild test -workspace Tuist.xcworkspace -scheme Tuist-Workspace -only-testing TuistLoaderTests/PackageInfoMapperTests -destination platform=macOS -derivedDataPath /private/tmp/tuist-nested-headers-loader-red CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" COMPILATION_CACHE_ENABLE_CACHING=NO
  • 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