diff --git a/Sources/XcodeProj/Utils/ReferenceGenerator.swift b/Sources/XcodeProj/Utils/ReferenceGenerator.swift index 01f0267e9..21df39128 100644 --- a/Sources/XcodeProj/Utils/ReferenceGenerator.swift +++ b/Sources/XcodeProj/Utils/ReferenceGenerator.swift @@ -176,6 +176,35 @@ final class ReferenceGenerator: ReferenceGenerating { } fixReference(for: synchronizedRootGroup, identifiers: identifiers) + + // Generate references for exception sets + if let exceptions = synchronizedRootGroup.exceptions { + try exceptions.forEach { exception in + try generateExceptionSetReferences(exception, identifiers: identifiers) + } + } + } + + /// Generates the reference for an exception set object. + /// + /// - Parameters: + /// - exceptionSet: exception set instance. + /// - identifiers: list of identifiers. + private func generateExceptionSetReferences(_ exceptionSet: PBXFileSystemSynchronizedExceptionSet, + identifiers: [String]) throws { + var identifiers = identifiers + + if let buildFileException = exceptionSet as? PBXFileSystemSynchronizedBuildFileExceptionSet { + if let target = buildFileException.target { + identifiers.append(target.reference.value) + } + fixReference(for: buildFileException, identifiers: identifiers) + } else if let buildPhaseException = exceptionSet as? PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet { + if let buildPhase = buildPhaseException.buildPhase { + identifiers.append(buildPhase.reference.value) + } + fixReference(for: buildPhaseException, identifiers: identifiers) + } } /// Generates the reference for a configuration list object. diff --git a/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift b/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift index af3a2f1b1..de9efd080 100644 --- a/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift +++ b/Tests/XcodeProjTests/Utils/ReferenceGeneratorTests.swift @@ -98,6 +98,63 @@ class ReferenceGeneratorTests: XCTestCase { XCTAssert(!syncedGroup.reference.temporary) } + + func test_projectWithFilesystemSynchronizedRootGroupWithExceptions_convertsReferencesToPermanent() throws { + let project = PBXProj(rootObject: nil, objectVersion: 0, archiveVersion: 0, classes: [:], objects: []) + let pbxProject = project.makeProject() + + let target = project.makeTarget() + pbxProject.targets.append(target) + + let buildFileException = PBXFileSystemSynchronizedBuildFileExceptionSet( + target: target, + membershipExceptions: ["Info.plist"], + publicHeaders: nil, + privateHeaders: nil, + additionalCompilerFlagsByRelativePath: nil, + attributesByRelativePath: nil + ) + project.add(object: buildFileException) + + let syncedGroup = project.makeSynchronizedRootGroup(exceptions: [buildFileException]) + target.fileSystemSynchronizedGroups = [syncedGroup] + + let referenceGenerator = ReferenceGenerator(outputSettings: PBXOutputSettings()) + try referenceGenerator.generateReferences(proj: project) + + XCTAssert(!syncedGroup.reference.temporary, "Synced group reference should not be temporary") + XCTAssert(!buildFileException.reference.temporary, "Build file exception reference should not be temporary") + XCTAssertFalse(buildFileException.reference.value.hasPrefix("TEMP_"), "Build file exception reference should not have TEMP_ prefix") + } + + func test_projectWithFilesystemSynchronizedRootGroupWithBuildPhaseException_convertsReferencesToPermanent() throws { + let project = PBXProj(rootObject: nil, objectVersion: 0, archiveVersion: 0, classes: [:], objects: []) + let pbxProject = project.makeProject() + + let target = project.makeTarget() + pbxProject.targets.append(target) + + let buildPhase = PBXSourcesBuildPhase() + project.add(object: buildPhase) + target.buildPhases.append(buildPhase) + + let buildPhaseException = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet( + buildPhase: buildPhase, + membershipExceptions: ["ExcludedFile.swift"], + attributesByRelativePath: nil + ) + project.add(object: buildPhaseException) + + let syncedGroup = project.makeSynchronizedRootGroup(exceptions: [buildPhaseException]) + target.fileSystemSynchronizedGroups = [syncedGroup] + + let referenceGenerator = ReferenceGenerator(outputSettings: PBXOutputSettings()) + try referenceGenerator.generateReferences(proj: project) + + XCTAssert(!syncedGroup.reference.temporary, "Synced group reference should not be temporary") + XCTAssert(!buildPhaseException.reference.temporary, "Build phase exception reference should not be temporary") + XCTAssertFalse(buildPhaseException.reference.value.hasPrefix("TEMP_"), "Build phase exception reference should not have TEMP_ prefix") + } } private extension PBXProj { @@ -192,11 +249,12 @@ private extension PBXProj { return target } - func makeSynchronizedRootGroup() -> PBXFileSystemSynchronizedRootGroup { + func makeSynchronizedRootGroup(exceptions: [PBXFileSystemSynchronizedExceptionSet] = []) -> PBXFileSystemSynchronizedRootGroup { let syncedGroup = PBXFileSystemSynchronizedRootGroup( sourceTree: .group, path: "SyncedPath", - name: "SyncedGroup" + name: "SyncedGroup", + exceptions: exceptions ) add(object: syncedGroup) rootObject!.mainGroup.children.append(syncedGroup)