From e0939cd1b1af7321eba2f172fa8b56640fbbe1bf Mon Sep 17 00:00:00 2001 From: Mirko Tomic Date: Fri, 21 Nov 2025 00:03:52 +0100 Subject: [PATCH] Handle membership exceptions for synchronized root groups Adds logic to detect and register membership exceptions for PBXFileSystemSynchronizedRootGroup objects, specifically excluding Info.plist files from group membership when necessary. Also ensures resources build phase is added if synchronized root groups are present. --- Sources/XcodeGenKit/PBXProjGenerator.swift | 42 +++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 8dcad5af..ccecfdfb 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -1139,7 +1139,8 @@ public class PBXProjGenerator { func addResourcesBuildPhase() { let resourcesBuildPhaseFiles = getBuildFilesForPhase(.resources) + copyResourcesReferences - if !resourcesBuildPhaseFiles.isEmpty { + let hasSynchronizedRootGroups = sourceFiles.contains { $0.fileReference is PBXFileSystemSynchronizedRootGroup } + if !resourcesBuildPhaseFiles.isEmpty || hasSynchronizedRootGroups { let resourcesBuildPhase = addObject(PBXResourcesBuildPhase(files: resourcesBuildPhaseFiles)) buildPhases.append(resourcesBuildPhase) } @@ -1458,6 +1459,45 @@ public class PBXProjGenerator { // add fileSystemSynchronizedGroups let synchronizedRootGroups = sourceFiles.compactMap { $0.fileReference as? PBXFileSystemSynchronizedRootGroup } if !synchronizedRootGroups.isEmpty { + for syncedGroup in synchronizedRootGroups { + guard let syncedGroupPath = syncedGroup.path else { continue } + let syncedPath = (project.basePath + Path(syncedGroupPath)).normalize() + + if let targetSource = target.sources.first(where: { + let sourcePath = (project.basePath + $0.path).normalize() + return sourcePath == syncedPath + }) { + var membershipExceptions = targetSource.excludes + + for infoPlistPath in infoPlistFiles.values { + let infoPlistFullPath = (project.basePath + infoPlistPath).normalize() + if infoPlistFullPath.string.hasPrefix(syncedPath.string + "/") { + if let relativePath = try? infoPlistFullPath.relativePath(from: syncedPath) { + let relativePathString = relativePath.string + if !membershipExceptions.contains(relativePathString) { + membershipExceptions.append(relativePathString) + } + } + } + } + + if !membershipExceptions.isEmpty { + let exceptionSet = PBXFileSystemSynchronizedBuildFileExceptionSet( + target: targetObject, + membershipExceptions: membershipExceptions, + publicHeaders: nil, + privateHeaders: nil, + additionalCompilerFlagsByRelativePath: nil, + attributesByRelativePath: nil + ) + addObject(exceptionSet) + if syncedGroup.exceptions == nil { + syncedGroup.exceptions = [] + } + syncedGroup.exceptions?.append(exceptionSet) + } + } + } targetObject.fileSystemSynchronizedGroups = synchronizedRootGroups } }