Hive Hive
Sign in

Buildable folders on static framework with resources: sources leak into the resource bundle target, localized resources stay in the framework

GitHub issue · Open

Metadata
Source
tuist/tuist #13727
Updated
Sep 30, 2026
Details

What happened?

For a .staticFramework target whose buildableFolders contain both sources and localized resources, Tuist attaches the same synchronized group to both the framework (Kit) and the generated bundle target (Repro_Kit), then subtracts files via membership exceptions:

Kit: membershipExceptions = (ru.lproj/Localizable.strings, en.lproj/Localizable.strings)
Repro_Kit: membershipExceptions = (Greeting.swift)

This causes two problems:

  1. Localized resources stay in the framework. Kit.framework still contains en.lproj/ and ru.lproj/. Xcode doesn’t match per-locale paths in exceptions for localized files; it expects the variant-group form "/Localized: Localizable.strings" (the form Xcode itself writes when you uncheck target membership of a localized file).
  2. New sources leak into the bundle target. The bundle target’s exceptions are a snapshot of the .swift files at generation time. Any .swift file added afterwards (without regenerating) is compiled into Repro_Kit too. It either fails to build (cannot find '…' in scope) or silently produces an executable inside the bundle. That defeats the main benefit of buildable folders.

Expected: behaviour like SwiftPM: sources go only to the framework, resources only to the bundle, and new source files work without regenerating.

I verified the following by editing the generated project.pbxproj by hand: attach the group only to the framework target. For the bundle target, use membership exceptions as inclusions: for a target that doesn’t list the group in fileSystemSynchronizedGroups, Xcode treats membershipExceptions as files added to that target. Use the /Localized: form:

/* Kit exception set */
membershipExceptions = (
- ru.lproj/Localizable.strings,
- en.lproj/Localizable.strings,
+ "/Localized: Localizable.strings",
);
/* Repro_Kit exception set */
membershipExceptions = (
- Greeting.swift,
+ "/Localized: Localizable.strings",
);
/* Repro_Kit native target */
-fileSystemSynchronizedGroups = (
- 28740BEAE30BDFD6C23A822C /* Sources */,
-);

Result: Kit.framework has no .lproj; Repro_Kit.bundle contains only en.lproj, ru.lproj, Info.plist. A new .swift file compiles only into Kit without regenerating, and a new locale (de.lproj/Localizable.strings) is picked up into the bundle without regenerating. Remaining limitation: a brand-new non-localized resource needs tuist generate to be routed to the bundle.

Possibly related: #8547 / #8548.

How do we reproduce it?

Sample project attached (tuist-buildable-folders-repro.zip, steps also in its README).

  1. tuist generate --no-open
  2. xcodebuild build -workspace Repro.xcworkspace -scheme Kit -destination "generic/platform=iOS Simulator" -derivedDataPath .dd
  3. ls .dd/Build/Products/Debug-iphonesimulator/Kit.framework → contains en.lproj and ru.lproj
  4. Without regenerating: cp Extra/Added.swift Sources/
  5. Repeat step 2 → build fails, Added.swift is compiled with -module-name Repro_Kit

tuist-buildable-folders-repro.zip

Error log

Sources/Added.swift:2:24: error: cannot find 'Greeting' in scope
(compiled as part of target Repro_Kit, -module-name Repro_Kit)

No error for problem 1: Kit.framework silently contains en.lproj/ and ru.lproj/.

macOS version

26.3

Tuist version

4.210.0

Xcode version

26.3

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.