Hive Hive
Sign in

fix(cli): surface SwiftPM C-target headers as project headers to avoid breaking sibling shim modules

GitHub issue · Closed

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

What changed

PackageInfoMapper.discoveredHeaders now classifies a C-family SwiftPM target’s headers by how the module is consumed:

  • .custom / .header module-map targets (consumed as a Clang/Swift module via import Module) surface all headers as project headers — never Public.
  • .directory targets (no umbrella header; consumed via <Module/Header.h>, e.g. ObjC frameworks) keep their public-directory headers Public so they’re copied into the framework bundle.

Why

#11617 added header discovery for C-family SwiftPM targets and tagged public-directory headers as Public for all module-map kinds. That shipped in 4.202.0-canary.29 and broke the canary auto-bump PR (#11579): the CLI’s own swift-nio-ssl build failed to compile.

Root cause

Tuist generates these SPM C targets as frameworks. A header tagged Public is copied (CpHeader) into CNIOBoringSSL.framework/Versions/A/Headers/. Populating the framework’s Headers/ directory flips the #if __has_include(<CNIOBoringSSL/CNIOBoringSSL.h>) guard inside CNIOBoringSSLShims.h from false to true. The shim then pulls BoringSSL’s headers in through the framework path and re-homes SSL_PRIVATE_KEY_METHOD into the CNIOBoringSSLShims module. Because swift-nio-ssl 2.37 compiles with -enable-upcoming-feature MemberImportVisibility, NIOSSL/CustomPrivateKey.swift — which imports only CNIOBoringSSL — can no longer use that struct’s synthesized memberwise initializer:

CustomPrivateKey.swift:237: initializer 'init(sign:decrypt:complete:)' is not
available due to missing import of defining module 'CNIOBoringSSLShims'

Why the classification is per module-map kind

The first cut of this fix made all discovered headers project-only. That fixed swift-nio-ssl but broke GenerateAcceptanceTestiOSAppWithObjCStaticFrameworkPackage: its MediaFeatureKit.m does #import <ObjCPlayerSupport/PlayerView.h>, and ObjCPlayerSupport is a .directory target (publicHeadersPath: ".", no umbrella header). Dropping Public stopped PlayerView.h from being copied into the framework, so the archive failed with 'ObjCPlayerSupport/PlayerView.h' file not found.

This mirrors the pre-#11617 behavior, which was actually correct: .directory targets got public headers (needed for <Module/Header.h> consumers), while .custom/.header targets got none (which kept swift-nio-ssl working). #11617’s regression was specifically adding Public headers to .custom/.header targets. The fix restores that split, and additionally surfaces .custom/.header headers as project headers so they still appear in the generated project (the feature’s goal) without being copied into the framework.

Validation

Built a patched tuist from this branch and verified all three cases:

  • swift-nio-ssl (.custom + .header): a Tuist app depending on .external(name: "NIOSSL")4.202.0-canary.29BUILD FAILED; patched → all C headers are project (0 Public) → BUILD SUCCEEDED.
  • ObjC static framework (.directory): the generated_ios_app_with_objc_static_framework_package fixture — PlayerView.h stays Publicxcodebuild archiveARCHIVE SUCCEEDED.
  • generated_app_with_spm_c_target_headers (.custom): CLib.h, Deep.h, CLibInternal.h all present as project headers (0 Public) — matches the updated acceptance assertion.

The spmTarget unit-test helper is project-only, which matches every unit test (all exercise .custom/.header targets).

Note on #11579

That PR pins the immutable 4.202.0-canary.29 binary, which contains the bug, so it cannot be made green by a source change. Once this lands on main, a clean canary is cut and the update-tuist-cli automation re-bumps the pin. The Tuist project self-hosts on swift-nio-ssl, so that next bump is itself end-to-end coverage for this regression.

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 88 % 197 0 197 8de943fbe
TuistUnitTests 89 % 3033 6 3027 8de943fbe
Flaky Tests ⚠️
  • TuistUnitTests: 3 flaky tests (View all)
Test case Module Suite
parseTestStatuses_returnsCorrectStatuses() TuistXCResultServiceTests XCResultServiceTests
parseTestStatuses_extractsModuleAndSuiteNames() TuistXCResultServiceTests XCResultServiceTests
parseTestWithCustomLabelXCResult() TuistXCResultServiceTests XCResultServiceTests
Builds 🔨
Scheme Status Duration Commit
TuistAcceptanceTests 2m 32s 8de943fbe
TuistUnitTests 3m 45s 8de943fbe