Hive Hive
Sign in

.gitkeep / .DS_Store inside a buildable folder are bundled and can’t be excluded

GitHub issue · Open

Metadata
Source
tuist/tuist #11534
Updated
Jun 26, 2026
Details

What happened?

FileSystem.glob(directory:include:) unconditionally drops **/.gitkeep and **/.DS_Store from its results (Sources/FileSystem/FileSystem.swift, added in tuist/FileSystem#115 for #7491). This has an unintended consequence for buildable folders (synchronized root groups):

  • A .gitkeep in a subfolder of a buildable folder (e.g. Sources/Generated/.gitkeep) is bundled into the built product — Xcode enumerates the synchronized folder itself and copies it. With several such files the build fails with Multiple commands produce '.../X.framework/.gitkeep' (same symptom as #7491, but via buildable folders).
  • It can’t be excluded with BuildableFolderException:
    .folder("Sources", exceptions: [.exception(excluded: ["**/.gitkeep"])])
    has no effect. Tuist resolves excluded through FileSystem.glob, which strips .gitkeep, so the pattern matches nothing and no membership exception is emitted to the .pbxproj.

.DS_Store in a subfolder behaves identically (also stripped by the glob). A .gitignore (not stripped) in the same place is correctly excluded by excluded: ["**/.gitignore"] and is not bundled — so behavior is inconsistent across dotfiles.

How do we reproduce it?

  1. A framework target backed by a buildable folder (e.g. Sources).
  2. Add an empty .gitkeep in a subfolder: Sources/Generated/.gitkeep.
  3. tuist generate, then build → the produced .framework contains .gitkeep. A second .gitkeep in another subfolder → “Multiple commands produce”.
  4. Add .folder("Sources", exceptions: [.exception(excluded: ["**/.gitkeep"])]) → no effect, still bundled, no membership exception in the .pbxproj.
  5. Replace with .gitignore + excluded: ["**/.gitignore"] → correctly excluded, not bundled.

Error log

Files that FileSystem.glob strips by default (**/.gitkeep, **/.DS_Store) should also not be bundled from buildable folders (consistent with the intent of #115), or BuildableFolderException.excluded should be able to target them.

macOS version

26.5

Tuist version

4.200.5 (FileSystem 0.18.0)

Xcode version

26.5

Comments
D
dosubot[bot] Jun 26, 2026

Thanks for the detailed report, @amarcadet. I can confirm the analysis lines up with the code.

The root cause is that FileSystem.glob unconditionally excludes **/.gitkeep and **/.DS_Store at the lowest level [1]. The BuildableFolderException.excluded patterns are resolved by calling that same glob [2], so patterns like **/.gitkeep match nothing and no membershipExceptions are emitted to the .pbxproj [3]. Meanwhile, Xcode’s PBXFileSystemSynchronizedRootGroup reads the directory directly from disk — it doesn’t consult Tuist’s resolved file list — so .gitkeep / .DS_Store are still bundled.

In short: Tuist’s file resolution pipeline can’t “see” these files, but Xcode’s synchronized group can, creating the inconsistency you described.

A fix would likely need to either:

  1. Unconditionally emit .gitkeep and .DS_Store as membership exceptions on every synchronized root group (matching the original intent of the FileSystem filter), or
  2. Bypass the glob exclusion when resolving BuildableFolderException.excluded patterns, so users can target these files explicitly.

If you’d like to dig deeper or work on a fix, I’d suggest installing this skill following the steps in this repo and using a coding agent (Claude, Codex, etc.) to prototype the change. If that doesn’t yield a solution, a minimal reproducible project attached here would help the maintainers prioritize it.

To reply, just mention @dosu.


Docs are dead. Just use Dosu.

Leave Feedback Ask Dosu about tuist Share Dosu with your team