Hive Hive
Sign in

feat(cli): include SwiftPM C-family target headers in generated projects

GitHub issue · Closed

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

What changed

Generated projects now surface a C-family SwiftPM target’s headers the way SwiftPM itself classifies them:

  • Every header under the target’s public headers directory (default include, matched recursively) becomes a public header.
  • Every other header in the target becomes a project (private) header.
  • The recognized header extensions match SwiftPM’s own FileRuleDescription.header (h, hh, hpp, h++, hp, hxx, H, ipp, def).

Concretely, Headers.from(moduleMap:) is replaced by PackageInfoMapper.discoveredHeaders, which globs the public and project headers and leans on the Headers exclusion rule (.projectExcludesPrivateAndPublic) to de-duplicate so a header is never both public and project.

Addresses the community request: https://community.tuist.dev/t/feature-request-include-swiftpm-target-headers-in-generated-xcode-projects/988

Why

When a SwiftPM C-family target is integrated into a generated Xcode project, its headers need to be present in the target so downstream code (and the module) can #include them. Without them, consumers of the package see missing headers in the generated project even though the sources compile under swift build. This is one of the Tuist gaps blocking OpenSwiftUI from generating its project with Tuist.

Root cause

The previous implementation (Headers.from(moduleMap:)) only produced headers for a single, narrow case: the generated umbrella-directory module map, and only for top-level include/*.h. That means:

  • Targets with a custom module.modulemap produced no headers at all.
  • Targets with an umbrella header produced no headers.
  • Nested headers (e.g. include/Sub/Deep.h) were dropped.
  • Non-.h headers (.hpp, .hxx, etc.) were dropped.
  • Private/project headers (those outside include) were never emitted.

So the generated project silently diverged from what SwiftPM classifies for the same target.

Why this approach

Rather than extend the special-cased umbrella-directory branch with more special cases, the fix mirrors SwiftPM’s own file-discovery model directly: public = everything under the public headers directory (recursively), project = everything else in the target, with the recognized header extension set. The exclusion rule keeps a public header from also being listed as a project header, so the classification stays a single source of truth instead of two hand-maintained branches. This generalizes across the custom-module-map, umbrella-header, and generated-module-map cases uniformly.

Impact

  • Generated projects that consume C-family SwiftPM targets now include the target’s headers with correct public/project visibility, including nested and non-.h headers.
  • No change for Swift-only targets: they resolve to ModuleMap.none and keep nil headers.

Validation

  • Acceptance test (red -> green): new generated_app_with_spm_c_target_headers fixture and GenerateAcceptanceTestAppWithSPMCTargetHeaders. The fixture is a C target (CLib) with a custom module.modulemap, a nested public header (include/Sub/Deep.h), and a private header (CLibInternal.h). The test generates the project and asserts the header build phase contains CLib.h, Deep.h, and CLibInternal.h, with CLib.h/Deep.h marked Public and CLibInternal.h not. Without the fix the target has no headers, so the assertion fails.
  • Unit tests: PackageInfoMapperTests updated to assert the SwiftPM-matching headers via a Headers.spmTarget(...) helper across the existing C-target scenarios (custom module map, umbrella header, dashed target name, transitive/external dependencies, etc.).

Notes

Opening as a draft for early review/direction from the Tuist team, per the discussion on 988.

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
T
tuist[bot] Jul 2, 2026

🛠️ Tuist Run Report 🛠️

Tests 🧪
Scheme Status Cache hit rate Tests Skipped Ran Commit
TuistAcceptanceTests 91 % 196 0 196 f624fc9f3
TuistUnitTests 99 % 459 39 420 bffe40cc3
Failed Tests ❌
Flaky Tests ⚠️
  • TuistAcceptanceTests: 1 flaky test (View all)
Test case Module Suite
app_with_plugins() TuistKitAcceptanceTests PluginAcceptanceTests
Builds 🔨
Scheme Status Duration Commit
TuistAcceptanceTests 2m 9s f624fc9f3
TuistUnitTests 22.3s bffe40cc3