Summary
When using defaultSourceDirectoryType: syncedFolder with multi-target projects, two issues prevent proper cross-target file membership:
includes is silently ignored for synced folders — only excludes is implemented
- No deduplication of synced folders across targets — same path in multiple targets creates duplicate
PBXFileSystemSynchronizedRootGroup entries
Environment
- XcodeGen: 2.45.2
- Xcode: 16+
- macOS: Sequoia
Minimal reproduction
Directory structure
DummyApp/
├── project.yml
├── App/
│ └── Source/
│ ├── Feature/
│ │ └── FeatureView.swift
│ └── Shared/
│ ├── SharedModel.swift
│ └── AnotherModel.swift
├── AppTests/
│ └── AppTests.swift
└── AppExtension/
└── ExtensionFile.swift
project.yml
name: DummyApp
options:
defaultSourceDirectoryType: syncedFolder
createIntermediateGroups: true
deploymentTarget:
iOS: 17.0
targets:
DummyApp:
type: application
platform: iOS
sources: [App]
settings:
PRODUCT_BUNDLE_IDENTIFIER: com.example.dummy
DummyTests:
type: bundle.ui-testing
platform: iOS
sources:
- AppTests
- path: App
includes:
- Source/Shared/SharedModel.swift
dependencies:
- target: DummyApp
DummyExtension:
type: app-extension
platform: iOS
sources:
- AppExtension
- path: App
includes:
- Source/Shared/AnotherModel.swift
settings:
PRODUCT_BUNDLE_IDENTIFIER: com.example.dummy.extension
Result
Running xcodegen generate produces:
- 2 "App" entries in the project navigator (1
PBXFileSystemSynchronizedRootGroup per target referencing App/)
includes silently ignored — all files in App/ are included in DummyTests and DummyExtension instead of just the specified files
- No
membershipExceptions generated in the pbxproj
Issue 1: includes silently ignored for synced folders
Expected: Only SharedModel.swift is included in DummyTests target.
Actual: ALL files in App/ are included (the includes is silently ignored).
Root cause
In SourceGenerator.getSourceFiles(), includePaths is computed but only used in the .group case. The .syncedFolder case ignores it entirely:
// includePaths is computed here...
let includePaths = targetSource.includes.isEmpty ? nil :
getSourceMatches(targetSource: targetSource, patterns: targetSource.includes)
// ...but only used in .group case
case .group:
let (groupSourceFiles, groups) = try getGroupSources(
...
includePaths: includePaths.flatMap(SortedArray.init(_:)),
...
)
// .syncedFolder case doesn't reference includePaths at all
case .syncedFolder:
// creates PBXFileSystemSynchronizedRootGroup without any includes handling
Similarly, configureMembershipExceptions() in PBXProjGenerator.swift only processes excludes (via expandedExcludes) and Info.plist files, with zero references to includes.
Issue 2: No deduplication across targets
Expected: One PBXFileSystemSynchronizedRootGroup for App/ with separate PBXFileSystemSynchronizedBuildFileExceptionSet entries per target.
Actual: Two separate PBXFileSystemSynchronizedRootGroup objects → duplicate "App" folders in the project navigator.
Root cause
In SourceGenerator.getSourceFiles(), the .syncedFolder case always creates a new PBXFileSystemSynchronizedRootGroup without checking if one already exists for the same path. There is no syncedGroupsByPath cache (unlike fileReferencesByPath for file references).
Expected behavior
Ideally, XcodeGen should:
- Support
includes for synced folders — translate them to membershipExceptions in PBXFileSystemSynchronizedBuildFileExceptionSet (the included files become membership exceptions for that target, meaning "include these files in this target's build phase")
- Deduplicate synced folders — when multiple targets reference the same directory path as a synced folder, reuse the same
PBXFileSystemSynchronizedRootGroup and attach separate PBXFileSystemSynchronizedBuildFileExceptionSet entries per target
This would match how Xcode natively handles synced folders with multi-target membership (one synced folder in the navigator, with per-target membership checkboxes in File Inspector).
Workaround
Use type: group for cross-target entries:
- path: App
type: group
includes:
- Source/Shared/SharedModel.swift
This works for includes but results in 2 entries in the navigator (1 synced folder + 1 group for the same directory).
Summary
When using
defaultSourceDirectoryType: syncedFolderwith multi-target projects, two issues prevent proper cross-target file membership:includesis silently ignored for synced folders — onlyexcludesis implementedPBXFileSystemSynchronizedRootGroupentriesEnvironment
Minimal reproduction
Directory structure
project.yml
Result
Running
xcodegen generateproduces:PBXFileSystemSynchronizedRootGroupper target referencingApp/)includessilently ignored — all files inApp/are included in DummyTests and DummyExtension instead of just the specified filesmembershipExceptionsgenerated in the pbxprojIssue 1:
includessilently ignored for synced foldersExpected: Only
SharedModel.swiftis included in DummyTests target.Actual: ALL files in
App/are included (theincludesis silently ignored).Root cause
In
SourceGenerator.getSourceFiles(),includePathsis computed but only used in the.groupcase. The.syncedFoldercase ignores it entirely:Similarly,
configureMembershipExceptions()inPBXProjGenerator.swiftonly processesexcludes(viaexpandedExcludes) and Info.plist files, with zero references toincludes.Issue 2: No deduplication across targets
Expected: One
PBXFileSystemSynchronizedRootGroupforApp/with separatePBXFileSystemSynchronizedBuildFileExceptionSetentries per target.Actual: Two separate
PBXFileSystemSynchronizedRootGroupobjects → duplicate "App" folders in the project navigator.Root cause
In
SourceGenerator.getSourceFiles(), the.syncedFoldercase always creates a newPBXFileSystemSynchronizedRootGroupwithout checking if one already exists for the same path. There is nosyncedGroupsByPathcache (unlikefileReferencesByPathfor file references).Expected behavior
Ideally, XcodeGen should:
includesfor synced folders — translate them tomembershipExceptionsinPBXFileSystemSynchronizedBuildFileExceptionSet(the included files become membership exceptions for that target, meaning "include these files in this target's build phase")PBXFileSystemSynchronizedRootGroupand attach separatePBXFileSystemSynchronizedBuildFileExceptionSetentries per targetThis would match how Xcode natively handles synced folders with multi-target membership (one synced folder in the navigator, with per-target membership checkboxes in File Inspector).
Workaround
Use
type: groupfor cross-target entries:This works for
includesbut results in 2 entries in the navigator (1 synced folder + 1 group for the same directory).