From ac46689961c93347a8ed72f0b094e9f630ad84ce Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 20 Jul 2024 11:07:28 +0200
Subject: [PATCH 01/12] Add PBXFileSystemSynchronizedRootGroup and property to
 PBXTarget

---
 .../PBXFileSystemSynchronizedRootGroup.swift  |  8 +++++
 .../XcodeProj/Objects/Targets/PBXTarget.swift | 29 +++++++++++++++++--
 2 files changed, 35 insertions(+), 2 deletions(-)
 create mode 100644 Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
new file mode 100644
index 000000000..a07dd934c
--- /dev/null
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -0,0 +1,8 @@
+import Foundation
+import PathKit
+
+public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {}
+
+/**         6CEC6EEA2C4BAE2D005DD0F8 /* Test */ = {isa =
+ PBXFileSystemSynchronizedRootGroup;
+ explicitFileTypes = {}; explicitFolders = (); path = Test; sourceTree = "<group>"; }; */
diff --git a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
index 86646cfd8..16c9c9f94 100644
--- a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
+++ b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
@@ -88,6 +88,19 @@ public class PBXTarget: PBXContainerItem {
         }
     }
 
+    // File system synchronized groups references.
+    var fileSystemSynchronizedGroupsReferences: [PBXObjectReference]?
+
+    // File system synchronized groups.
+    public var fileSystemSynchronizedGroups: [PBXFileSystemSynchronizedRootGroup]? {
+        set {
+            fileSystemSynchronizedGroupsReferences = newValue?.references()
+        }
+        get {
+            fileSystemSynchronizedGroupsReferences?.objects()
+        }
+    }
+
     /// Target product type.
     public var productType: PBXProductType?
 
@@ -110,12 +123,15 @@ public class PBXTarget: PBXContainerItem {
                 packageProductDependencies: [XCSwiftPackageProductDependency] = [],
                 productName: String? = nil,
                 product: PBXFileReference? = nil,
-                productType: PBXProductType? = nil) {
+                productType: PBXProductType? = nil,
+                fileSystemSynchronizedGroups: [PBXFileSystemSynchronizedRootGroup]? = nil)
+    {
         buildConfigurationListReference = buildConfigurationList?.reference
         buildPhaseReferences = buildPhases.references()
         buildRuleReferences = buildRules.references()
         dependencyReferences = dependencies.references()
         packageProductDependencyReferences = packageProductDependencies.references()
+        fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroups?.references()
         self.name = name
         self.productName = productName
         productReference = product?.reference
@@ -135,6 +151,7 @@ public class PBXTarget: PBXContainerItem {
         case productReference
         case productType
         case packageProductDependencies
+        case fileSystemSynchronizedGroups
     }
 
     public required init(from decoder: Decoder) throws {
@@ -160,11 +177,12 @@ public class PBXTarget: PBXContainerItem {
         } else {
             productReference = nil
         }
-
         let packageProductDependencyReferenceStrings: [String] = try container.decodeIfPresent(.packageProductDependencies) ?? []
         packageProductDependencyReferences = packageProductDependencyReferenceStrings.map {
             objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
         }
+        let fileSystemSynchronizedGroupsReferences: [String] = try container.decodeIfPresent(.fileSystemSynchronizedGroups) ?? []
+        self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
 
         productType = try container.decodeIfPresent(.productType)
         try super.init(from: decoder)
@@ -190,6 +208,13 @@ public class PBXTarget: PBXContainerItem {
         }
 
         dictionary["dependencies"] = .array(dependencyReferences.map { .string(CommentedString($0.value, comment: PBXTargetDependency.isa)) })
+        if let fileSystemSynchronizedGroupsReferences {
+            dictionary["fileSystemSynchronizedGroups"] = .array(fileSystemSynchronizedGroupsReferences.map { fileSystemSynchronizedGroupReference in
+                let fileSystemSynchronizedGroup: PBXFileSystemSynchronizedRootGroup? = fileSystemSynchronizedGroupReference.getObject()
+                return .string(CommentedString(fileSystemSynchronizedGroupReference.value, comment: fileSystemSynchronizedGroup?.name))
+            })
+        }
+
         dictionary["name"] = .string(CommentedString(name))
         if let productName {
             dictionary["productName"] = .string(CommentedString(productName))

From 8fe5e51f995a15d7679c796f0686e04735a1b77b Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 20 Jul 2024 11:57:32 +0200
Subject: [PATCH 02/12] Implement the new models

---
 .mise.toml                                    |   2 +
 ...temSynchronizedBuildFileExceptionSet.swift |  55 +++++++++
 .../PBXFileSystemSynchronizedRootGroup.swift  | 106 +++++++++++++++++-
 .../Objects/Sourcery/Equality.generated.swift |  20 ++++
 4 files changed, 179 insertions(+), 4 deletions(-)
 create mode 100644 Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift

diff --git a/.mise.toml b/.mise.toml
index 317af7dd7..cf6bcf300 100644
--- a/.mise.toml
+++ b/.mise.toml
@@ -1,3 +1,5 @@
 [tools]
 swiftlint = "0.54.0"
 swiftformat = "0.54.3"
+tuist = "4.21.2"
+
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
new file mode 100644
index 000000000..7b62cc585
--- /dev/null
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -0,0 +1,55 @@
+import Foundation
+
+
+/// Class representing an element that may contain other elements.
+public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
+    
+    // MARK: - Attributes
+    
+    /// A list of relative paths to children subfolders for which exceptions are applied.
+    public var membershipExceptions: [String]?
+    
+    var targetReference: PBXObjectReference
+    
+    public var target: PBXTarget! {
+        get {
+            targetReference.getObject() as? PBXTarget
+        }
+        set {
+            targetReference = newValue.reference
+        }
+    }
+    
+    // MARK: - Init
+    
+    public init(target: PBXTarget, membershipExceptions: [String]) {
+        self.targetReference = target.reference
+        self.membershipExceptions = membershipExceptions
+        super.init()
+    }
+    
+    // MARK: - Decodable
+
+    fileprivate enum CodingKeys: String, CodingKey {
+        case target
+        case membershipExceptions
+    }
+
+    public required init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let referenceRepository = decoder.context.objectReferenceRepository
+        let objects = decoder.context.objects
+        let targetReference: String = try container.decode(.target)
+        self.targetReference = referenceRepository.getOrCreate(reference: targetReference, objects: objects)
+        self.membershipExceptions = try container.decodeIfPresent(.membershipExceptions)
+        try super.init(from: decoder)
+    }
+    
+    // MARK: - Equatable
+
+    override func isEqual(to object: Any?) -> Bool {
+        guard let rhs = object as? PBXFrameworksBuildPhase else { return false }
+        return isEqual(to: rhs)
+    }
+    
+}
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index a07dd934c..0d8196837 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -1,8 +1,106 @@
 import Foundation
 import PathKit
 
-public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {}
+public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
+    
+    /// It maps relative paths inside the synchronized root group to a particular file type.
+    /// If a path doesn't have a particular file type specified, Xcode defaults to the default file type
+    /// based on the extension of the file.
+    public var explicitFileTypes: [String: String]
+    
+    /// Returns the references of the exceptions.
+    var exceptionsReferences: [PBXObjectReference]
 
-/**         6CEC6EEA2C4BAE2D005DD0F8 /* Test */ = {isa =
- PBXFileSystemSynchronizedRootGroup;
- explicitFileTypes = {}; explicitFolders = (); path = Test; sourceTree = "<group>"; }; */
+    /// It returns a list of exception objects that override the configuration for some children
+    /// in the synchronized root group.
+    public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] {
+        set {
+            exceptionsReferences = newValue.references()
+        }
+        get {
+            exceptionsReferences.objects()
+        }
+    }
+    
+    /// A list of relative paths to children folder whose configuration is overriden.
+    public var explicitFolders: [String]
+    
+    /// Initializes the file element with its properties.
+    ///
+    /// - Parameters:
+    ///   - sourceTree: file source tree.
+    ///   - path: object relative path from `sourceTree`, if different than `name`.
+    ///   - name: object name.
+    ///   - includeInIndex: should the IDE index the object?
+    ///   - usesTabs: object uses tabs.
+    ///   - indentWidth: the number of positions to indent blocks of code
+    ///   - tabWidth: the visual width of tab characters
+    ///   - wrapsLines: should the IDE wrap lines when editing the object?
+    ///   - explicitFileTypes: It maps relative paths inside the synchronized root group to a particular file type.
+    ///   - exceptions: It returns a list of exception objects that override the configuration for some children in the synchronized root group.
+    ///   - explicitFolders: A list of relative paths to children folder whose configuration is overriden.
+    public init(sourceTree: PBXSourceTree? = nil,
+                path: String? = nil,
+                name: String? = nil,
+                includeInIndex: Bool? = nil,
+                usesTabs: Bool? = nil,
+                indentWidth: UInt? = nil,
+                tabWidth: UInt? = nil,
+                wrapsLines: Bool? = nil,
+                explicitFileTypes: [String: String] = [:],
+                exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [],
+                explicitFolders: [String] = []) {
+        self.explicitFileTypes = explicitFileTypes
+        self.exceptionsReferences = exceptions.references()
+        self.explicitFolders = explicitFolders
+        super.init(sourceTree: sourceTree,
+                   path: path,
+                   name: name,
+                   includeInIndex: includeInIndex,
+                   usesTabs: usesTabs,
+                   indentWidth: indentWidth,
+                   tabWidth: tabWidth,
+                   wrapsLines: wrapsLines)
+    }
+    
+    // MARK: - Decodable
+    
+    fileprivate enum CodingKeys: String, CodingKey {
+        case explicitFileTypes
+        case exceptions
+        case explicitFolders
+    }
+    
+    public required init(from decoder: Decoder) throws {
+        let objects = decoder.context.objects
+        let objectReferenceRepository = decoder.context.objectReferenceRepository
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        self.explicitFileTypes = (try container.decodeIfPresent(.explicitFileTypes)) ?? [:]
+        let exceptionsReferences: [String] = (try container.decodeIfPresent(.exceptions)) ?? []
+        self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
+        self.explicitFolders = (try container.decodeIfPresent(.explicitFolders)) ?? []
+        try super.init(from: decoder)
+    }
+    
+    // MARK: - PlistSerializable
+
+    override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
+        var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
+        dictionary["isa"] = .string(CommentedString(type(of: self).isa))
+        dictionary["exceptions"] = .array(self.exceptionsReferences.map({ exceptionReference in
+            return .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
+        }))
+        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: self.explicitFileTypes.map({ (relativePath, fileType) in
+            return (CommentedString(relativePath), .string(CommentedString(fileType)))
+        })))
+        dictionary["explicitFolders"] = .array(explicitFolders.map({ .string(CommentedString($0)) }))
+        return (key: CommentedString(reference,
+                                     comment: name ?? path),
+                value: .dictionary(dictionary))
+    }
+
+    override func isEqual(to object: Any?) -> Bool {
+        guard let rhs = object as? PBXFrameworksBuildPhase else { return false }
+        return isEqual(to: rhs)
+    }
+}
diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
index c20d93c20..7ea7358fd 100644
--- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
+++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
@@ -301,3 +301,23 @@ extension XCVersionGroup {
         return super.isEqual(to: rhs)
     }
 }
+
+
+extension PBXFileSystemSynchronizedRootGroup {
+    /// :nodoc:
+    func isEqual(to rhs: PBXFileSystemSynchronizedRootGroup) -> Bool {
+        if explicitFileTypes != rhs.explicitFileTypes { return false }
+        if exceptionsReferences != rhs.exceptionsReferences { return false }
+        if explicitFolders != rhs.explicitFolders { return false }
+        return super.isEqual(to: rhs)
+    }
+}
+
+extension PBXFileSystemSynchronizedBuildFileExceptionSet {
+    /// :nodoc:
+    func isEqual(to rhs: PBXFileSystemSynchronizedBuildFileExceptionSet) -> Bool {
+        if membershipExceptions != rhs.membershipExceptions { return false }
+        if targetReference != rhs.targetReference { return false }
+        return super.isEqual(to: rhs)
+    }
+}

From f00f493f25523fdb9cbf718aabdb72c5bfc91351 Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 20 Jul 2024 12:10:56 +0200
Subject: [PATCH 03/12] Add publicHeaders and privateHeaders

---
 ...SystemSynchronizedBuildFileExceptionSet.swift | 16 +++++++++++++++-
 Sources/XcodeProj/Objects/Files/PBXGroup.swift   | 10 ++++++++++
 .../Objects/Sourcery/Equality.generated.swift    |  2 ++
 3 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
index 7b62cc585..04923f3e5 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -9,6 +9,14 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
     /// A list of relative paths to children subfolders for which exceptions are applied.
     public var membershipExceptions: [String]?
     
+    /// Changes the default header visibility (project) to public for the following headers.
+    /// Every item in the list is the relative path inside the root synchronized group.
+    public var publicHeaders: [String]?
+    
+    /// Changes the default header visibility (project) to private for the following headers.
+    /// Every item in the list is the relative path inside the root synchronized group.
+    public var privateHeaders: [String]?
+    
     var targetReference: PBXObjectReference
     
     public var target: PBXTarget! {
@@ -22,9 +30,11 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
     
     // MARK: - Init
     
-    public init(target: PBXTarget, membershipExceptions: [String]) {
+    public init(target: PBXTarget, membershipExceptions: [String]?, publicHeaders: [String]?, privateHeaders: [String]?) {
         self.targetReference = target.reference
         self.membershipExceptions = membershipExceptions
+        self.publicHeaders = publicHeaders
+        self.privateHeaders = privateHeaders
         super.init()
     }
     
@@ -33,6 +43,8 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
     fileprivate enum CodingKeys: String, CodingKey {
         case target
         case membershipExceptions
+        case publicHeaders
+        case privateHeaders
     }
 
     public required init(from decoder: Decoder) throws {
@@ -42,6 +54,8 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
         let targetReference: String = try container.decode(.target)
         self.targetReference = referenceRepository.getOrCreate(reference: targetReference, objects: objects)
         self.membershipExceptions = try container.decodeIfPresent(.membershipExceptions)
+        self.publicHeaders = try container.decodeIfPresent(.publicHeaders)
+        self.privateHeaders = try container.decodeIfPresent(.privateHeaders)
         try super.init(from: decoder)
     }
     
diff --git a/Sources/XcodeProj/Objects/Files/PBXGroup.swift b/Sources/XcodeProj/Objects/Files/PBXGroup.swift
index 05cece653..c3c913d5b 100644
--- a/Sources/XcodeProj/Objects/Files/PBXGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXGroup.swift
@@ -115,6 +115,16 @@ public extension PBXGroup {
             .objects()
             .first(where: { $0.name == name })
     }
+    
+    /// Returns the synchronized root group with the given name contained in the given parent group.
+    ///
+    /// - Parameter groupName: group name.
+    /// - Returns: the synchronized root group with the given name contained in the given parent group.
+    func synchronizedRootGroup(named name: String) -> PBXFileSystemSynchronizedRootGroup? {
+        childrenReferences
+            .objects()
+            .first(where: { $0.name == name })
+    }
 
     /// Returns the file in the group with the given name.
     ///
diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
index 7ea7358fd..1003db40b 100644
--- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
+++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
@@ -318,6 +318,8 @@ extension PBXFileSystemSynchronizedBuildFileExceptionSet {
     func isEqual(to rhs: PBXFileSystemSynchronizedBuildFileExceptionSet) -> Bool {
         if membershipExceptions != rhs.membershipExceptions { return false }
         if targetReference != rhs.targetReference { return false }
+        if publicHeaders != rhs.publicHeaders { return false }
+        if privateHeaders != rhs.privateHeaders { return false }
         return super.isEqual(to: rhs)
     }
 }

From 637fa44c5f3677134fb82fdaedfb0d2d4ffe6461 Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 20 Jul 2024 18:58:09 +0200
Subject: [PATCH 04/12] Fix some bugs and add some tests

---
 ...temSynchronizedBuildFileExceptionSet.swift |  2 +-
 .../PBXFileSystemSynchronizedRootGroup.swift  |  2 +-
 .../XcodeProj/Objects/Targets/PBXTarget.swift |  7 +--
 ...onizedBuildFileExceptionSet+Fixtures.swift | 13 +++++
 ...nchronizedBuildFileExceptionSetTests.swift | 29 +++++++++++
 ...SystemSynchronizedRootGroup+Fixtures.swift | 27 ++++++++++
 ...FileSystemSynchronizedRootGroupTests.swift | 51 +++++++++++++++++++
 7 files changed, 126 insertions(+), 5 deletions(-)
 create mode 100644 Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
 create mode 100644 Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
 create mode 100644 Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift
 create mode 100644 Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
index 04923f3e5..d2f6336a2 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -62,7 +62,7 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
     // MARK: - Equatable
 
     override func isEqual(to object: Any?) -> Bool {
-        guard let rhs = object as? PBXFrameworksBuildPhase else { return false }
+        guard let rhs = object as? PBXFileSystemSynchronizedBuildFileExceptionSet else { return false }
         return isEqual(to: rhs)
     }
     
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index 0d8196837..23da01498 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -100,7 +100,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
     }
 
     override func isEqual(to object: Any?) -> Bool {
-        guard let rhs = object as? PBXFrameworksBuildPhase else { return false }
+        guard let rhs = object as? PBXFileSystemSynchronizedRootGroup else { return false }
         return isEqual(to: rhs)
     }
 }
diff --git a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
index 16c9c9f94..93f102283 100644
--- a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
+++ b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
@@ -181,9 +181,10 @@ public class PBXTarget: PBXContainerItem {
         packageProductDependencyReferences = packageProductDependencyReferenceStrings.map {
             objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
         }
-        let fileSystemSynchronizedGroupsReferences: [String] = try container.decodeIfPresent(.fileSystemSynchronizedGroups) ?? []
-        self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
-
+        let fileSystemSynchronizedGroupsReferences: [String]? = try container.decodeIfPresent(.fileSystemSynchronizedGroups)
+        if let fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences {
+            self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
+        }
         productType = try container.decodeIfPresent(.productType)
         try super.init(from: decoder)
     }
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
new file mode 100644
index 000000000..d49b85791
--- /dev/null
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
@@ -0,0 +1,13 @@
+import XcodeProj
+
+extension PBXFileSystemSynchronizedBuildFileExceptionSet {
+    static func fixture(target: PBXTarget = .fixture(),
+                        membershipExceptions: [String]? = [],
+                        publicHeaders: [String]? = [],
+                        privateHeaders: [String]? = []) -> PBXFileSystemSynchronizedBuildFileExceptionSet {
+        return PBXFileSystemSynchronizedBuildFileExceptionSet(target: target,
+                                                              membershipExceptions: membershipExceptions,
+                                                              publicHeaders: publicHeaders,
+                                                              privateHeaders: privateHeaders)
+    }
+}
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
new file mode 100644
index 000000000..799edaee3
--- /dev/null
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
@@ -0,0 +1,29 @@
+import Foundation
+import XCTest
+@testable import XcodeProj
+
+final class PBXFileSystemSynchronizedBuildFileExceptionSetTests: XCTestCase {
+    var target: PBXTarget!
+    var subject: PBXFileSystemSynchronizedBuildFileExceptionSet!
+    
+    override func setUp() {
+        super.setUp()
+        target = PBXTarget.fixture()
+        subject = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target)
+    }
+    
+    override func tearDown() {
+        target = nil
+        subject = nil
+        super.tearDown()
+    }
+    
+    func test_itHasTheCorrectIsa() {
+        XCTAssertEqual(PBXFileSystemSynchronizedBuildFileExceptionSet.isa, "PBXFileSystemSynchronizedBuildFileExceptionSet")
+    }
+    
+    func test_equal_returnsTheCorrectValue() {
+        let another = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target)
+        XCTAssertEqual(subject, another)
+    }
+}
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift
new file mode 100644
index 000000000..7a14963af
--- /dev/null
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift
@@ -0,0 +1,27 @@
+import XcodeProj
+
+extension PBXFileSystemSynchronizedRootGroup {
+    static func fixture(sourceTree: PBXSourceTree? = nil,
+                            path: String? = nil,
+                            name: String? = nil,
+                            includeInIndex: Bool? = nil,
+                            usesTabs: Bool? = nil,
+                            indentWidth: UInt? = nil,
+                            tabWidth: UInt? = nil,
+                            wrapsLines: Bool? = nil,
+                            explicitFileTypes: [String: String] = [:],
+                            exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [],
+                        explicitFolders: [String] = []) -> PBXFileSystemSynchronizedRootGroup {
+        return PBXFileSystemSynchronizedRootGroup(sourceTree: sourceTree,
+                                                  path: path,
+                                                  name: name,
+                                                  includeInIndex: includeInIndex,
+                                                  usesTabs: usesTabs,
+                                                  indentWidth: indentWidth,
+                                                  tabWidth: tabWidth,
+                                                  wrapsLines: wrapsLines,
+                                                  explicitFileTypes: explicitFileTypes,
+                                                  exceptions: exceptions, 
+                                                  explicitFolders: explicitFolders)
+    }
+}
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift
new file mode 100644
index 000000000..21894e0e0
--- /dev/null
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift
@@ -0,0 +1,51 @@
+import Foundation
+import XCTest
+@testable import XcodeProj
+
+final class PBXFileSystemSynchronizedRootGroupTests: XCTestCase {
+    var subject: PBXFileSystemSynchronizedRootGroup!
+    var target: PBXTarget!
+    var exception: PBXFileSystemSynchronizedBuildFileExceptionSet!
+    
+    override func setUp() {
+        super.setUp()
+        target = PBXTarget.fixture()
+        exception = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target)
+        subject = PBXFileSystemSynchronizedRootGroup(sourceTree: .group,
+                                                     path: "synchronized",
+                                                     name: "synchronized",
+                                                     includeInIndex: true,
+                                                     usesTabs: true,
+                                                     indentWidth: 2,
+                                                     tabWidth: 2,
+                                                     wrapsLines: true,
+                                                     explicitFileTypes: ["Source.m": "sourcecode.c.objc"],
+                                                     exceptions: [exception],
+                                                     explicitFolders: [])
+    }
+    
+    override func tearDown() {
+        exception = nil
+        subject = nil
+        super.tearDown()
+    }
+    
+    func test_itHasTheCorrectIsa() {
+        XCTAssertEqual(PBXFileSystemSynchronizedRootGroup.isa, "PBXFileSystemSynchronizedRootGroup")
+    }
+    
+    func test_equal_returnsTheCorrectValue() {
+        let another = PBXFileSystemSynchronizedRootGroup(sourceTree: .group,
+                                                         path: "synchronized",
+                                                         name: "synchronized",
+                                                         includeInIndex: true,
+                                                         usesTabs: true,
+                                                         indentWidth: 2,
+                                                         tabWidth: 2,
+                                                         wrapsLines: true,
+                                                         explicitFileTypes: ["Source.m": "sourcecode.c.objc"],
+                                                         exceptions: [exception],
+                                                         explicitFolders: [])
+        XCTAssertEqual(subject, another)
+    }
+}

From e4f9029b37aaf2cc76361452d0d654d760ab8d13 Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 20 Jul 2024 19:05:23 +0200
Subject: [PATCH 05/12] Fix some linting issues

---
 .github/workflows/xcodeproj.yml                        | 10 ++++++++++
 .mise.toml                                             |  3 +--
 .mise/tasks/lint                                       |  7 +++++++
 .mise/tasks/lint-fix                                   |  7 +++++++
 ...BXFileSystemSynchronizedBuildFileExceptionSet.swift |  1 -
 .../Objects/Sourcery/Equality.generated.swift          |  1 -
 Sources/XcodeProj/Objects/Targets/PBXTarget.swift      |  7 ++++---
 Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift  |  4 ++++
 Sources/XcodeProj/Utils/XCConfig.swift                 |  4 ++++
 9 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml
index 7d921922f..b297a1641 100644
--- a/.github/workflows/xcodeproj.yml
+++ b/.github/workflows/xcodeproj.yml
@@ -8,7 +8,11 @@ concurrency:
   cancel-in-progress: true
 
 env:
+<<<<<<< HEAD
   MISE_EXPERIMENTAL: 1
+=======
+  MISE_EXPERIMENTAL: "1"
+>>>>>>> be7969e7 (Fix some linting issues)
 
 jobs:
   build:
@@ -52,9 +56,15 @@ jobs:
           git config --global init.defaultBranch main
       - name: Build and run tests
         run: swift test --enable-test-discovery
+<<<<<<< HEAD
   lint:
     name: Lint
     runs-on: macos-latest
+=======
+  swiftlint:
+    name: Lint
+    runs-on: ubuntu-latest
+>>>>>>> be7969e7 (Fix some linting issues)
     steps:
       - uses: actions/checkout@v3
       - uses: jdx/mise-action@v2
diff --git a/.mise.toml b/.mise.toml
index cf6bcf300..ec954e154 100644
--- a/.mise.toml
+++ b/.mise.toml
@@ -1,5 +1,4 @@
 [tools]
-swiftlint = "0.54.0"
 swiftformat = "0.54.3"
 tuist = "4.21.2"
-
+swiftlint = "0.55.1"
diff --git a/.mise/tasks/lint b/.mise/tasks/lint
index d1a523f1d..66ff8c388 100755
--- a/.mise/tasks/lint
+++ b/.mise/tasks/lint
@@ -1,6 +1,13 @@
+<<<<<<< HEAD
 #!/bin/bash
 # mise description="Lint the workspace"
 set -euo pipefail
 
 swiftformat $MISE_PROJECT_ROOT --lint
 swiftlint lint --quiet --config $MISE_PROJECT_ROOT/.swiftlint.yml $MISE_PROJECT_ROOT/Sources
+=======
+#!/usr/bin/env bash
+# mise description="Lints the project"
+
+swiftlint $MISE_PROJECT_ROOT
+>>>>>>> be7969e7 (Fix some linting issues)
diff --git a/.mise/tasks/lint-fix b/.mise/tasks/lint-fix
index cfc035959..df75b136f 100755
--- a/.mise/tasks/lint-fix
+++ b/.mise/tasks/lint-fix
@@ -1,6 +1,13 @@
+<<<<<<< HEAD
 #!/bin/bash
 # mise description="Lint the workspace fixing issues"
 set -euo pipefail
 
 swiftformat $MISE_PROJECT_ROOT
 swiftlint lint --fix --quiet --config $MISE_PROJECT_ROOT/.swiftlint.yml $MISE_PROJECT_ROOT/Sources
+=======
+#!/usr/bin/env bash
+# mise description="Lints the project"
+
+swiftlint --fix $MISE_PROJECT_ROOT
+>>>>>>> be7969e7 (Fix some linting issues)
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
index d2f6336a2..6d7a12156 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -1,6 +1,5 @@
 import Foundation
 
-
 /// Class representing an element that may contain other elements.
 public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
     
diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
index 1003db40b..4c9a1d98c 100644
--- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
+++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
@@ -302,7 +302,6 @@ extension XCVersionGroup {
     }
 }
 
-
 extension PBXFileSystemSynchronizedRootGroup {
     /// :nodoc:
     func isEqual(to rhs: PBXFileSystemSynchronizedRootGroup) -> Bool {
diff --git a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
index 93f102283..518b26736 100644
--- a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
+++ b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
@@ -124,8 +124,7 @@ public class PBXTarget: PBXContainerItem {
                 productName: String? = nil,
                 product: PBXFileReference? = nil,
                 productType: PBXProductType? = nil,
-                fileSystemSynchronizedGroups: [PBXFileSystemSynchronizedRootGroup]? = nil)
-    {
+                fileSystemSynchronizedGroups: [PBXFileSystemSynchronizedRootGroup]? = nil) {
         buildConfigurationListReference = buildConfigurationList?.reference
         buildPhaseReferences = buildPhases.references()
         buildRuleReferences = buildRules.references()
@@ -183,7 +182,9 @@ public class PBXTarget: PBXContainerItem {
         }
         let fileSystemSynchronizedGroupsReferences: [String]? = try container.decodeIfPresent(.fileSystemSynchronizedGroups)
         if let fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences {
-            self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
+            self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map {
+              objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
+            }
         }
         productType = try container.decodeIfPresent(.productType)
         try super.init(from: decoder)
diff --git a/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift b/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift
index 6c4864996..b3ccc4cf0 100644
--- a/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift
+++ b/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift
@@ -81,8 +81,12 @@ public extension XCScheme {
             commandlineArguments: CommandLineArguments? = nil,
             environmentVariables: [EnvironmentVariable]? = nil,
             enableTestabilityWhenProfilingTests: Bool = true,
+<<<<<<< HEAD
             launchAutomaticallySubstyle: String? = nil
         ) {
+=======
+            launchAutomaticallySubstyle: String? = nil) {
+>>>>>>> be7969e7 (Fix some linting issues)
             self.init(
                 runnable: buildableProductRunnable,
                 buildConfiguration: buildConfiguration,
diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift
index e1e585ea6..cd1846ce5 100644
--- a/Sources/XcodeProj/Utils/XCConfig.swift
+++ b/Sources/XcodeProj/Utils/XCConfig.swift
@@ -68,7 +68,11 @@ enum XCConfigParser {
                     do {
                         // first try to load the included xcconfig relative to the current xcconfig
                         config = try XCConfig(path: path.parent() + includePath, projectPath: projectPath)
+<<<<<<< HEAD
                     } catch XCConfigError.notFound(_) where projectPath != nil {
+=======
+                    } catch XCConfigError.notFound(_)where projectPath != nil {
+>>>>>>> be7969e7 (Fix some linting issues)
                         // if that fails, try to load the included xcconfig relative to the project
                         config = try? XCConfig(path: projectPath!.parent() + includePath, projectPath: projectPath)
                     } catch {

From b297994a601577371f7d63e3adc5ef8759df8c25 Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 20 Jul 2024 19:09:47 +0200
Subject: [PATCH 06/12] Fix the pipeline to not run twice on PRs

---
 .github/workflows/xcodeproj.yml | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml
index b297a1641..5d1e3b5f7 100644
--- a/.github/workflows/xcodeproj.yml
+++ b/.github/workflows/xcodeproj.yml
@@ -1,18 +1,18 @@
 # https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idname
 name: XcodeProj
 
-on: [push, pull_request]
+on:
+  push:
+    branches:
+      - main
+  pull_request: {}
 
 concurrency:
   group: xcodeproj-${{ github.head_ref }}
   cancel-in-progress: true
 
 env:
-<<<<<<< HEAD
   MISE_EXPERIMENTAL: 1
-=======
-  MISE_EXPERIMENTAL: "1"
->>>>>>> be7969e7 (Fix some linting issues)
 
 jobs:
   build:
@@ -56,15 +56,10 @@ jobs:
           git config --global init.defaultBranch main
       - name: Build and run tests
         run: swift test --enable-test-discovery
-<<<<<<< HEAD
+
   lint:
     name: Lint
     runs-on: macos-latest
-=======
-  swiftlint:
-    name: Lint
-    runs-on: ubuntu-latest
->>>>>>> be7969e7 (Fix some linting issues)
     steps:
       - uses: actions/checkout@v3
       - uses: jdx/mise-action@v2

From 697d42d445d9ff5b9a42d9473c8625ec2cc0aaa4 Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Fri, 26 Jul 2024 17:47:50 +0200
Subject: [PATCH 07/12] Add tests

---
 .gitignore                                    |   3 +
 .mise/tasks/lint                              |   7 -
 .mise/tasks/lint-fix                          |   7 -
 .../project.pbxproj                           | 359 ++++++++++++++++++
 .../xcschemes/xcschememanagement.plist        |  14 +
 .../Exception/Exception.swift                 |   6 +
 .../SynchronizedRootGroups.swift              |   6 +
 ...temSynchronizedBuildFileExceptionSet.swift |  48 ++-
 .../PBXFileSystemSynchronizedRootGroup.swift  |  37 +-
 .../XcodeProj/Objects/Files/PBXGroup.swift    |   2 +-
 .../Objects/Project/PBXObjectParser.swift     |   4 +
 .../Objects/Project/PBXObjects.swift          |  12 +
 .../Objects/Project/PBXProjEncoder.swift      |  12 +
 .../XcodeProj/Objects/Targets/PBXTarget.swift |   4 +-
 .../Scheme/XCScheme+ProfileAction.swift       |   4 -
 Sources/XcodeProj/Utils/XCConfig.swift        |   4 -
 ...onizedBuildFileExceptionSet+Fixtures.swift |   8 +-
 ...nchronizedBuildFileExceptionSetTests.swift |   8 +-
 ...SystemSynchronizedRootGroup+Fixtures.swift |  40 +-
 ...FileSystemSynchronizedRootGroupTests.swift |   8 +-
 .../Objects/Project/PBXProjEncoderTests.swift |  36 ++
 Tests/XcodeProjTests/Tests/Fixtures.swift     |   5 +
 22 files changed, 543 insertions(+), 91 deletions(-)
 create mode 100644 Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj
 create mode 100644 Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist
 create mode 100644 Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/Exception/Exception.swift
 create mode 100644 Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/SynchronizedRootGroups.swift

diff --git a/.gitignore b/.gitignore
index 97f05bc98..a53f04dd9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -190,3 +190,6 @@ XcodeProj.framework.zip
 Derived/
 *.xcworkspace/
 *.xcodeproj/
+
+!Fixtures/**/*.xcodeproj/
+
diff --git a/.mise/tasks/lint b/.mise/tasks/lint
index 66ff8c388..d1a523f1d 100755
--- a/.mise/tasks/lint
+++ b/.mise/tasks/lint
@@ -1,13 +1,6 @@
-<<<<<<< HEAD
 #!/bin/bash
 # mise description="Lint the workspace"
 set -euo pipefail
 
 swiftformat $MISE_PROJECT_ROOT --lint
 swiftlint lint --quiet --config $MISE_PROJECT_ROOT/.swiftlint.yml $MISE_PROJECT_ROOT/Sources
-=======
-#!/usr/bin/env bash
-# mise description="Lints the project"
-
-swiftlint $MISE_PROJECT_ROOT
->>>>>>> be7969e7 (Fix some linting issues)
diff --git a/.mise/tasks/lint-fix b/.mise/tasks/lint-fix
index df75b136f..cfc035959 100755
--- a/.mise/tasks/lint-fix
+++ b/.mise/tasks/lint-fix
@@ -1,13 +1,6 @@
-<<<<<<< HEAD
 #!/bin/bash
 # mise description="Lint the workspace fixing issues"
 set -euo pipefail
 
 swiftformat $MISE_PROJECT_ROOT
 swiftlint lint --fix --quiet --config $MISE_PROJECT_ROOT/.swiftlint.yml $MISE_PROJECT_ROOT/Sources
-=======
-#!/usr/bin/env bash
-# mise description="Lints the project"
-
-swiftlint --fix $MISE_PROJECT_ROOT
->>>>>>> be7969e7 (Fix some linting issues)
diff --git a/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..5db92414b
--- /dev/null
+++ b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj
@@ -0,0 +1,359 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 70;
+	objects = {
+
+/* Begin PBXFileReference section */
+		6CF05B8C2C53F5F200EF267F /* SynchronizedRootGroups.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SynchronizedRootGroups.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+		6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
+			isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+			membershipExceptions = (
+				Exception/Exception.swift,
+			);
+			target = 6CF05B8B2C53F5F200EF267F /* SynchronizedRootGroups */;
+		};
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
+/* Begin PBXFileSystemSynchronizedRootGroup section */
+		6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = "<group>"; };
+/* End PBXFileSystemSynchronizedRootGroup section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		6CF05B892C53F5F200EF267F /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		6CF05B822C53F5F200EF267F = {
+			isa = PBXGroup;
+			children = (
+				6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */,
+				6CF05B8D2C53F5F200EF267F /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		6CF05B8D2C53F5F200EF267F /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				6CF05B8C2C53F5F200EF267F /* SynchronizedRootGroups.framework */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		6CF05B872C53F5F200EF267F /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		6CF05B8B2C53F5F200EF267F /* SynchronizedRootGroups */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 6CF05B922C53F5F200EF267F /* Build configuration list for PBXNativeTarget "SynchronizedRootGroups" */;
+			buildPhases = (
+				6CF05B872C53F5F200EF267F /* Headers */,
+				6CF05B882C53F5F200EF267F /* Sources */,
+				6CF05B892C53F5F200EF267F /* Frameworks */,
+				6CF05B8A2C53F5F200EF267F /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			fileSystemSynchronizedGroups = (
+				6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */,
+			);
+			name = SynchronizedRootGroups;
+			packageProductDependencies = (
+			);
+			productName = SynchronizedRootGroups;
+			productReference = 6CF05B8C2C53F5F200EF267F /* SynchronizedRootGroups.framework */;
+			productType = "com.apple.product-type.framework";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		6CF05B832C53F5F200EF267F /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				BuildIndependentTargetsInParallel = 1;
+				LastUpgradeCheck = 1600;
+				TargetAttributes = {
+					6CF05B8B2C53F5F200EF267F = {
+						CreatedOnToolsVersion = 16.0;
+						LastSwiftMigration = 1600;
+					};
+				};
+			};
+			buildConfigurationList = 6CF05B862C53F5F200EF267F /* Build configuration list for PBXProject "SynchronizedRootGroups" */;
+			compatibilityVersion = "Xcode 15.0";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 6CF05B822C53F5F200EF267F;
+			productRefGroup = 6CF05B8D2C53F5F200EF267F /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				6CF05B8B2C53F5F200EF267F /* SynchronizedRootGroups */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		6CF05B8A2C53F5F200EF267F /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		6CF05B882C53F5F200EF267F /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+		6CF05B932C53F5F200EF267F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				CURRENT_PROJECT_VERSION = 1;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_MODULE_VERIFIER = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+				MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+				PRODUCT_BUNDLE_IDENTIFIER = io.tuist.SynchronizedRootGroups;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_INSTALL_OBJC_HEADER = NO;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		6CF05B942C53F5F200EF267F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				CURRENT_PROJECT_VERSION = 1;
+				DEFINES_MODULE = YES;
+				DYLIB_COMPATIBILITY_VERSION = 1;
+				DYLIB_CURRENT_VERSION = 1;
+				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				ENABLE_MODULE_VERIFIER = YES;
+				GENERATE_INFOPLIST_FILE = YES;
+				INFOPLIST_KEY_NSHumanReadableCopyright = "";
+				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+					"@loader_path/Frameworks",
+				);
+				MARKETING_VERSION = 1.0;
+				MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++";
+				MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20";
+				PRODUCT_BUNDLE_IDENTIFIER = io.tuist.SynchronizedRootGroups;
+				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
+				SKIP_INSTALL = YES;
+				SWIFT_EMIT_LOC_STRINGS = YES;
+				SWIFT_INSTALL_OBJC_HEADER = NO;
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		6CF05B952C53F5F200EF267F /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu17;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+				MACOSX_DEPLOYMENT_TARGET = 14.5;
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+				MTL_FAST_MATH = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Debug;
+		};
+		6CF05B962C53F5F200EF267F /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_ENABLE_OBJC_WEAK = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				COPY_PHASE_STRIP = NO;
+				CURRENT_PROJECT_VERSION = 1;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_USER_SCRIPT_SANDBOXING = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu17;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+				MACOSX_DEPLOYMENT_TARGET = 14.5;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				MTL_FAST_MATH = YES;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				VERSIONING_SYSTEM = "apple-generic";
+				VERSION_INFO_PREFIX = "";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		6CF05B862C53F5F200EF267F /* Build configuration list for PBXProject "SynchronizedRootGroups" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				6CF05B952C53F5F200EF267F /* Debug */,
+				6CF05B962C53F5F200EF267F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		6CF05B922C53F5F200EF267F /* Build configuration list for PBXNativeTarget "SynchronizedRootGroups" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				6CF05B932C53F5F200EF267F /* Debug */,
+				6CF05B942C53F5F200EF267F /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 6CF05B832C53F5F200EF267F /* Project object */;
+}
diff --git a/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 000000000..e04d3354a
--- /dev/null
+++ b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/xcuserdata/pepicrft.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>SchemeUserState</key>
+	<dict>
+		<key>SynchronizedRootGroups.xcscheme_^#shared#^_</key>
+		<dict>
+			<key>orderHint</key>
+			<integer>0</integer>
+		</dict>
+	</dict>
+</dict>
+</plist>
diff --git a/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/Exception/Exception.swift b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/Exception/Exception.swift
new file mode 100644
index 000000000..190cf0a74
--- /dev/null
+++ b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/Exception/Exception.swift
@@ -0,0 +1,6 @@
+//
+//  Exception.swift
+//  SynchronizedRootGroups
+//
+//  Created by Pedro Piñera Buendía on 26.07.24.
+//
diff --git a/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/SynchronizedRootGroups.swift b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/SynchronizedRootGroups.swift
new file mode 100644
index 000000000..9286b7807
--- /dev/null
+++ b/Fixtures/SynchronizedRootGroups/SynchronizedRootGroups/SynchronizedRootGroups.swift
@@ -0,0 +1,6 @@
+//
+//  SynchronizedRootGroups.swift
+//  SynchronizedRootGroups
+//
+//  Created by Pedro Piñera Buendía on 26.07.24.
+//
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
index 6d7a12156..b883cc8dd 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -1,23 +1,22 @@
 import Foundation
 
 /// Class representing an element that may contain other elements.
-public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
-    
+public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSerializable {
     // MARK: - Attributes
-    
+
     /// A list of relative paths to children subfolders for which exceptions are applied.
     public var membershipExceptions: [String]?
-    
+
     /// Changes the default header visibility (project) to public for the following headers.
     /// Every item in the list is the relative path inside the root synchronized group.
     public var publicHeaders: [String]?
-    
+
     /// Changes the default header visibility (project) to private for the following headers.
     /// Every item in the list is the relative path inside the root synchronized group.
     public var privateHeaders: [String]?
-    
+
     var targetReference: PBXObjectReference
-    
+
     public var target: PBXTarget! {
         get {
             targetReference.getObject() as? PBXTarget
@@ -26,17 +25,17 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
             targetReference = newValue.reference
         }
     }
-    
+
     // MARK: - Init
-    
+
     public init(target: PBXTarget, membershipExceptions: [String]?, publicHeaders: [String]?, privateHeaders: [String]?) {
-        self.targetReference = target.reference
+        targetReference = target.reference
         self.membershipExceptions = membershipExceptions
         self.publicHeaders = publicHeaders
         self.privateHeaders = privateHeaders
         super.init()
     }
-    
+
     // MARK: - Decodable
 
     fileprivate enum CodingKeys: String, CodingKey {
@@ -52,17 +51,34 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject {
         let objects = decoder.context.objects
         let targetReference: String = try container.decode(.target)
         self.targetReference = referenceRepository.getOrCreate(reference: targetReference, objects: objects)
-        self.membershipExceptions = try container.decodeIfPresent(.membershipExceptions)
-        self.publicHeaders = try container.decodeIfPresent(.publicHeaders)
-        self.privateHeaders = try container.decodeIfPresent(.privateHeaders)
+        membershipExceptions = try container.decodeIfPresent(.membershipExceptions)
+        publicHeaders = try container.decodeIfPresent(.publicHeaders)
+        privateHeaders = try container.decodeIfPresent(.privateHeaders)
         try super.init(from: decoder)
     }
-    
+
     // MARK: - Equatable
 
     override func isEqual(to object: Any?) -> Bool {
         guard let rhs = object as? PBXFileSystemSynchronizedBuildFileExceptionSet else { return false }
         return isEqual(to: rhs)
     }
-    
+
+    // MARK: - PlistSerializable
+
+    func plistKeyAndValue(proj _: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
+        var dictionary: [CommentedString: PlistValue] = [:]
+        dictionary["isa"] = .string(CommentedString(PBXFileSystemSynchronizedBuildFileExceptionSet.isa))
+        if let membershipExceptions {
+            dictionary["membershipExceptions"] = .array(membershipExceptions.map { .string(CommentedString($0)) })
+        }
+        if let publicHeaders {
+            dictionary["publicHeaders"] = .array(publicHeaders.map { .string(CommentedString($0)) })
+        }
+        if let privateHeaders {
+            dictionary["privateHeaders"] = .array(privateHeaders.map { .string(CommentedString($0)) })
+        }
+        dictionary["target"] = .string(CommentedString(target.reference.value, comment: target.name))
+        return (key: CommentedString(reference, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"), value: .dictionary(dictionary))
+    }
 }
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index 23da01498..4768ea3f9 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -2,12 +2,11 @@ import Foundation
 import PathKit
 
 public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
-    
     /// It maps relative paths inside the synchronized root group to a particular file type.
     /// If a path doesn't have a particular file type specified, Xcode defaults to the default file type
     /// based on the extension of the file.
     public var explicitFileTypes: [String: String]
-    
+
     /// Returns the references of the exceptions.
     var exceptionsReferences: [PBXObjectReference]
 
@@ -21,10 +20,10 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
             exceptionsReferences.objects()
         }
     }
-    
+
     /// A list of relative paths to children folder whose configuration is overriden.
     public var explicitFolders: [String]
-    
+
     /// Initializes the file element with its properties.
     ///
     /// - Parameters:
@@ -51,7 +50,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
                 exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [],
                 explicitFolders: [String] = []) {
         self.explicitFileTypes = explicitFileTypes
-        self.exceptionsReferences = exceptions.references()
+        exceptionsReferences = exceptions.references()
         self.explicitFolders = explicitFolders
         super.init(sourceTree: sourceTree,
                    path: path,
@@ -62,38 +61,40 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
                    tabWidth: tabWidth,
                    wrapsLines: wrapsLines)
     }
-    
+
     // MARK: - Decodable
-    
+
     fileprivate enum CodingKeys: String, CodingKey {
         case explicitFileTypes
         case exceptions
         case explicitFolders
     }
-    
+
     public required init(from decoder: Decoder) throws {
         let objects = decoder.context.objects
         let objectReferenceRepository = decoder.context.objectReferenceRepository
         let container = try decoder.container(keyedBy: CodingKeys.self)
-        self.explicitFileTypes = (try container.decodeIfPresent(.explicitFileTypes)) ?? [:]
-        let exceptionsReferences: [String] = (try container.decodeIfPresent(.exceptions)) ?? []
+        explicitFileTypes = try (container.decodeIfPresent(.explicitFileTypes)) ?? [:]
+        let exceptionsReferences: [String] = try (container.decodeIfPresent(.exceptions)) ?? []
         self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
-        self.explicitFolders = (try container.decodeIfPresent(.explicitFolders)) ?? []
+        explicitFolders = try (container.decodeIfPresent(.explicitFolders)) ?? []
         try super.init(from: decoder)
     }
-    
+
     // MARK: - PlistSerializable
 
+    override var multiline: Bool { false }
+
     override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
         var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
         dictionary["isa"] = .string(CommentedString(type(of: self).isa))
-        dictionary["exceptions"] = .array(self.exceptionsReferences.map({ exceptionReference in
-            return .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
+        dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in
+            .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
+        })
+        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in
+            (CommentedString(relativePath), .string(CommentedString(fileType)))
         }))
-        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: self.explicitFileTypes.map({ (relativePath, fileType) in
-            return (CommentedString(relativePath), .string(CommentedString(fileType)))
-        })))
-        dictionary["explicitFolders"] = .array(explicitFolders.map({ .string(CommentedString($0)) }))
+        dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) })
         return (key: CommentedString(reference,
                                      comment: name ?? path),
                 value: .dictionary(dictionary))
diff --git a/Sources/XcodeProj/Objects/Files/PBXGroup.swift b/Sources/XcodeProj/Objects/Files/PBXGroup.swift
index c3c913d5b..8b8d2814b 100644
--- a/Sources/XcodeProj/Objects/Files/PBXGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXGroup.swift
@@ -115,7 +115,7 @@ public extension PBXGroup {
             .objects()
             .first(where: { $0.name == name })
     }
-    
+
     /// Returns the synchronized root group with the given name contained in the given parent group.
     ///
     /// - Parameter groupName: group name.
diff --git a/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift b/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift
index ef80d0466..3c0ad0044 100644
--- a/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift
+++ b/Sources/XcodeProj/Objects/Project/PBXObjectParser.swift
@@ -68,6 +68,10 @@ final class PBXObjectParser {
             return try decoder.decode(XCLocalSwiftPackageReference.self, from: data)
         case XCSwiftPackageProductDependency.isa:
             return try decoder.decode(XCSwiftPackageProductDependency.self, from: data)
+        case PBXFileSystemSynchronizedRootGroup.isa:
+            return try decoder.decode(PBXFileSystemSynchronizedRootGroup.self, from: data)
+        case PBXFileSystemSynchronizedBuildFileExceptionSet.isa:
+            return try decoder.decode(PBXFileSystemSynchronizedBuildFileExceptionSet.self, from: data)
         default:
             throw PBXObjectError.unknownElement(isa)
         }
diff --git a/Sources/XcodeProj/Objects/Project/PBXObjects.swift b/Sources/XcodeProj/Objects/Project/PBXObjects.swift
index 5fbf74e1a..34ff68b66 100644
--- a/Sources/XcodeProj/Objects/Project/PBXObjects.swift
+++ b/Sources/XcodeProj/Objects/Project/PBXObjects.swift
@@ -135,6 +135,16 @@ class PBXObjects: Equatable {
         lock.whileLocked { _swiftPackageProductDependencies }
     }
 
+    private var _fileSystemSynchronizedRootGroups: [PBXObjectReference: PBXFileSystemSynchronizedRootGroup] = [:]
+    var fileSystemSynchronizedRootGroups: [PBXObjectReference: PBXFileSystemSynchronizedRootGroup] {
+        lock.whileLocked { _fileSystemSynchronizedRootGroups }
+    }
+
+    private var _fileSystemSynchronizedBuildFileExceptionSets: [PBXObjectReference: PBXFileSystemSynchronizedBuildFileExceptionSet] = [:]
+    var fileSystemSynchronizedBuildFileExceptionSets: [PBXObjectReference: PBXFileSystemSynchronizedBuildFileExceptionSet] {
+        lock.whileLocked { _fileSystemSynchronizedBuildFileExceptionSets }
+    }
+
     // XCSwiftPackageProductDependency
 
     /// Initializes the project objects container
@@ -218,6 +228,8 @@ class PBXObjects: Equatable {
         case let object as XCRemoteSwiftPackageReference: _remoteSwiftPackageReferences[objectReference] = object
         case let object as XCLocalSwiftPackageReference: _localSwiftPackageReferences[objectReference] = object
         case let object as XCSwiftPackageProductDependency: _swiftPackageProductDependencies[objectReference] = object
+        case let object as PBXFileSystemSynchronizedRootGroup: _fileSystemSynchronizedRootGroups[objectReference] = object
+        case let object as PBXFileSystemSynchronizedBuildFileExceptionSet: _fileSystemSynchronizedBuildFileExceptionSets[objectReference] = object
         default: fatalError("Unhandled PBXObject type for \(object), this is likely a bug / todo")
         }
     }
diff --git a/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift b/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift
index 9b0dd2f26..9ee78300c 100644
--- a/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift
+++ b/Sources/XcodeProj/Objects/Project/PBXProjEncoder.swift
@@ -113,6 +113,18 @@ final class PBXProjEncoder {
                   outputSettings: outputSettings,
                   stateHolder: &stateHolder,
                   to: &output)
+        try write(section: "PBXFileSystemSynchronizedBuildFileExceptionSet",
+                  proj: proj,
+                  objects: proj.objects.fileSystemSynchronizedBuildFileExceptionSets,
+                  outputSettings: outputSettings,
+                  stateHolder: &stateHolder,
+                  to: &output)
+        try write(section: "PBXFileSystemSynchronizedRootGroup",
+                  proj: proj,
+                  objects: proj.objects.fileSystemSynchronizedRootGroups,
+                  outputSettings: outputSettings,
+                  stateHolder: &stateHolder,
+                  to: &output)
         try write(section: "PBXFrameworksBuildPhase",
                   proj: proj,
                   objects: proj.objects.frameworksBuildPhases,
diff --git a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
index 518b26736..fa73903fd 100644
--- a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
+++ b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
@@ -181,9 +181,9 @@ public class PBXTarget: PBXContainerItem {
             objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
         }
         let fileSystemSynchronizedGroupsReferences: [String]? = try container.decodeIfPresent(.fileSystemSynchronizedGroups)
-        if let fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences {
+        if let fileSystemSynchronizedGroupsReferences {
             self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map {
-              objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
+                objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
             }
         }
         productType = try container.decodeIfPresent(.productType)
diff --git a/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift b/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift
index b3ccc4cf0..6c4864996 100644
--- a/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift
+++ b/Sources/XcodeProj/Scheme/XCScheme+ProfileAction.swift
@@ -81,12 +81,8 @@ public extension XCScheme {
             commandlineArguments: CommandLineArguments? = nil,
             environmentVariables: [EnvironmentVariable]? = nil,
             enableTestabilityWhenProfilingTests: Bool = true,
-<<<<<<< HEAD
             launchAutomaticallySubstyle: String? = nil
         ) {
-=======
-            launchAutomaticallySubstyle: String? = nil) {
->>>>>>> be7969e7 (Fix some linting issues)
             self.init(
                 runnable: buildableProductRunnable,
                 buildConfiguration: buildConfiguration,
diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift
index cd1846ce5..e1e585ea6 100644
--- a/Sources/XcodeProj/Utils/XCConfig.swift
+++ b/Sources/XcodeProj/Utils/XCConfig.swift
@@ -68,11 +68,7 @@ enum XCConfigParser {
                     do {
                         // first try to load the included xcconfig relative to the current xcconfig
                         config = try XCConfig(path: path.parent() + includePath, projectPath: projectPath)
-<<<<<<< HEAD
                     } catch XCConfigError.notFound(_) where projectPath != nil {
-=======
-                    } catch XCConfigError.notFound(_)where projectPath != nil {
->>>>>>> be7969e7 (Fix some linting issues)
                         // if that fails, try to load the included xcconfig relative to the project
                         config = try? XCConfig(path: projectPath!.parent() + includePath, projectPath: projectPath)
                     } catch {
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
index d49b85791..67ad69832 100644
--- a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
@@ -5,9 +5,9 @@ extension PBXFileSystemSynchronizedBuildFileExceptionSet {
                         membershipExceptions: [String]? = [],
                         publicHeaders: [String]? = [],
                         privateHeaders: [String]? = []) -> PBXFileSystemSynchronizedBuildFileExceptionSet {
-        return PBXFileSystemSynchronizedBuildFileExceptionSet(target: target,
-                                                              membershipExceptions: membershipExceptions,
-                                                              publicHeaders: publicHeaders,
-                                                              privateHeaders: privateHeaders)
+        PBXFileSystemSynchronizedBuildFileExceptionSet(target: target,
+                                                       membershipExceptions: membershipExceptions,
+                                                       publicHeaders: publicHeaders,
+                                                       privateHeaders: privateHeaders)
     }
 }
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
index 799edaee3..842954846 100644
--- a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
@@ -5,23 +5,23 @@ import XCTest
 final class PBXFileSystemSynchronizedBuildFileExceptionSetTests: XCTestCase {
     var target: PBXTarget!
     var subject: PBXFileSystemSynchronizedBuildFileExceptionSet!
-    
+
     override func setUp() {
         super.setUp()
         target = PBXTarget.fixture()
         subject = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target)
     }
-    
+
     override func tearDown() {
         target = nil
         subject = nil
         super.tearDown()
     }
-    
+
     func test_itHasTheCorrectIsa() {
         XCTAssertEqual(PBXFileSystemSynchronizedBuildFileExceptionSet.isa, "PBXFileSystemSynchronizedBuildFileExceptionSet")
     }
-    
+
     func test_equal_returnsTheCorrectValue() {
         let another = PBXFileSystemSynchronizedBuildFileExceptionSet.fixture(target: target)
         XCTAssertEqual(subject, another)
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift
index 7a14963af..c4eabf4f7 100644
--- a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroup+Fixtures.swift
@@ -2,26 +2,26 @@ import XcodeProj
 
 extension PBXFileSystemSynchronizedRootGroup {
     static func fixture(sourceTree: PBXSourceTree? = nil,
-                            path: String? = nil,
-                            name: String? = nil,
-                            includeInIndex: Bool? = nil,
-                            usesTabs: Bool? = nil,
-                            indentWidth: UInt? = nil,
-                            tabWidth: UInt? = nil,
-                            wrapsLines: Bool? = nil,
-                            explicitFileTypes: [String: String] = [:],
-                            exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [],
+                        path: String? = nil,
+                        name: String? = nil,
+                        includeInIndex: Bool? = nil,
+                        usesTabs: Bool? = nil,
+                        indentWidth: UInt? = nil,
+                        tabWidth: UInt? = nil,
+                        wrapsLines: Bool? = nil,
+                        explicitFileTypes: [String: String] = [:],
+                        exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] = [],
                         explicitFolders: [String] = []) -> PBXFileSystemSynchronizedRootGroup {
-        return PBXFileSystemSynchronizedRootGroup(sourceTree: sourceTree,
-                                                  path: path,
-                                                  name: name,
-                                                  includeInIndex: includeInIndex,
-                                                  usesTabs: usesTabs,
-                                                  indentWidth: indentWidth,
-                                                  tabWidth: tabWidth,
-                                                  wrapsLines: wrapsLines,
-                                                  explicitFileTypes: explicitFileTypes,
-                                                  exceptions: exceptions, 
-                                                  explicitFolders: explicitFolders)
+        PBXFileSystemSynchronizedRootGroup(sourceTree: sourceTree,
+                                           path: path,
+                                           name: name,
+                                           includeInIndex: includeInIndex,
+                                           usesTabs: usesTabs,
+                                           indentWidth: indentWidth,
+                                           tabWidth: tabWidth,
+                                           wrapsLines: wrapsLines,
+                                           explicitFileTypes: explicitFileTypes,
+                                           exceptions: exceptions,
+                                           explicitFolders: explicitFolders)
     }
 }
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift
index 21894e0e0..374156104 100644
--- a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedRootGroupTests.swift
@@ -6,7 +6,7 @@ final class PBXFileSystemSynchronizedRootGroupTests: XCTestCase {
     var subject: PBXFileSystemSynchronizedRootGroup!
     var target: PBXTarget!
     var exception: PBXFileSystemSynchronizedBuildFileExceptionSet!
-    
+
     override func setUp() {
         super.setUp()
         target = PBXTarget.fixture()
@@ -23,17 +23,17 @@ final class PBXFileSystemSynchronizedRootGroupTests: XCTestCase {
                                                      exceptions: [exception],
                                                      explicitFolders: [])
     }
-    
+
     override func tearDown() {
         exception = nil
         subject = nil
         super.tearDown()
     }
-    
+
     func test_itHasTheCorrectIsa() {
         XCTAssertEqual(PBXFileSystemSynchronizedRootGroup.isa, "PBXFileSystemSynchronizedRootGroup")
     }
-    
+
     func test_equal_returnsTheCorrectValue() {
         let another = PBXFileSystemSynchronizedRootGroup(sourceTree: .group,
                                                          path: "synchronized",
diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift
index d60e08380..9ff5e3cbc 100644
--- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift
+++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift
@@ -285,6 +285,38 @@ class PBXProjEncoderTests: XCTestCase {
         lines.validate(line: ");", after: line)
     }
 
+    // MARK: - File system synchronized root groups
+
+    func test_fileSystemSynchronizedRootGroups_when_projectWithFileSystemSynchronizedRootGroups() throws {
+        // Given
+        try loadSynchronizedRootGroups()
+        let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst)
+        let lines = lines(fromFile: encodeProject(settings: settings))
+
+        let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedRootGroup section */")
+        var line = lines.validate(line: "6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = \"<group>\"; };", after: beginGroup)
+        line = lines.validate(line: "/* End PBXFileSystemSynchronizedRootGroup section */", after: line)
+    }
+
+    // MARK: - File system synchronized build file exception set
+
+    func test_fileSystemSynchronizedBuildFileExceptionSets_when_projectWithFileSystemSynchronizedRootGroups() throws {
+        // Given
+        try loadSynchronizedRootGroups()
+        let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst)
+        let lines = lines(fromFile: encodeProject(settings: settings))
+
+        let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */")
+        var line = lines.validate(line: "6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {", after: beginGroup)
+        line = lines.validate(line: "isa = PBXFileSystemSynchronizedBuildFileExceptionSet;", after: line)
+        line = lines.validate(line: "membershipExceptions = (", after: line)
+        line = lines.validate(line: "Exception/Exception.swift,", after: line)
+        line = lines.validate(line: ");", after: line)
+        line = lines.validate(line: "target = 6CF05B8B2C53F5F200EF267F /* SynchronizedRootGroups */;", after: line)
+        line = lines.validate(line: "};", after: line)
+        line = lines.validate(line: "/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */", after: line)
+    }
+
     // MARK: - Build phases
 
     func test_build_phase_sources_unsorted_when_iOSProject() throws {
@@ -457,6 +489,10 @@ class PBXProjEncoderTests: XCTestCase {
         proj = try PBXProj(jsonDictionary: iosProjectDictionary().1)
     }
 
+    private func loadSynchronizedRootGroups() throws {
+        proj = try PBXProj(jsonDictionary: synchronizedRootGroupsFixture().1)
+    }
+
     private func loadFileSharedAcrossTargetsProject() throws {
         proj = try PBXProj(jsonDictionary: fileSharedAcrossTargetsDictionary().1)
     }
diff --git a/Tests/XcodeProjTests/Tests/Fixtures.swift b/Tests/XcodeProjTests/Tests/Fixtures.swift
index 40cc343f9..edb724a5b 100644
--- a/Tests/XcodeProjTests/Tests/Fixtures.swift
+++ b/Tests/XcodeProjTests/Tests/Fixtures.swift
@@ -6,6 +6,11 @@ func fixturesPath() -> Path {
     Path(#file).parent().parent().parent().parent() + "Fixtures"
 }
 
+func synchronizedRootGroupsFixture() -> (Path, [String: Any]) {
+    let iosProject = fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj/project.pbxproj"
+    return (iosProject, loadPlist(path: iosProject.string)!)
+}
+
 func iosProjectDictionary() -> (Path, [String: Any]) {
     let iosProject = fixturesPath() + "iOS/Project.xcodeproj/project.pbxproj"
     return (iosProject, loadPlist(path: iosProject.string)!)

From 0f6328502366de1344727ce15df9e5d87cc5c74b Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Thu, 8 Aug 2024 13:43:09 +0200
Subject: [PATCH 08/12] feat: Add optional exceptions to
 PBXFileSystemSynchronizedRootGroup

---
 .../PBXFileSystemSynchronizedRootGroup.swift  | 24 ++++++++++---------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index 4768ea3f9..b03603c5b 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -8,16 +8,16 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
     public var explicitFileTypes: [String: String]
 
     /// Returns the references of the exceptions.
-    var exceptionsReferences: [PBXObjectReference]
+    var exceptionsReferences: [PBXObjectReference]?
 
     /// It returns a list of exception objects that override the configuration for some children
     /// in the synchronized root group.
-    public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet] {
+    public var exceptions: [PBXFileSystemSynchronizedBuildFileExceptionSet]? {
         set {
-            exceptionsReferences = newValue.references()
+            exceptionsReferences = newValue?.references()
         }
         get {
-            exceptionsReferences.objects()
+            exceptionsReferences?.objects()
         }
     }
 
@@ -88,13 +88,15 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
     override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
         var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
         dictionary["isa"] = .string(CommentedString(type(of: self).isa))
-        dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in
-            .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
-        })
-        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in
-            (CommentedString(relativePath), .string(CommentedString(fileType)))
-        }))
-        dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) })
+        if let exceptionsReferences = self.exceptionsReferences {
+            dictionary["exceptions"] = .array(exceptionsReferences.map({ exceptionReference in
+                return .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
+            }))
+        }
+        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: self.explicitFileTypes.map({ (relativePath, fileType) in
+            return (CommentedString(relativePath), .string(CommentedString(fileType)))
+        })))
+        dictionary["explicitFolders"] = .array(explicitFolders.map({ .string(CommentedString($0)) }))
         return (key: CommentedString(reference,
                                      comment: name ?? path),
                 value: .dictionary(dictionary))

From 1e5ba340e2aceb7c97d6da53e67b362619754c8f Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Thu, 8 Aug 2024 14:07:27 +0200
Subject: [PATCH 09/12] fix!: Fix non-determinism reading and writing the
 packageProductDependencies attribute of PBXTarget

---
 .../PBXFileSystemSynchronizedRootGroup.swift  | 16 ++++++-------
 .../Objects/Project/PBXObjects.swift          | 15 +++++++++++-
 .../Objects/Project/PBXProject.swift          |  4 ++--
 .../XcodeProj/Objects/Targets/PBXTarget.swift | 24 ++++++++++++-------
 .../Objects/Project/PBXProjectTests.swift     |  6 ++---
 ....swift => XcodeProjIntegrationTests.swift} |  9 +++++++
 6 files changed, 51 insertions(+), 23 deletions(-)
 rename Tests/XcodeProjTests/Project/{XcodeProjTests.swift => XcodeProjIntegrationTests.swift} (89%)

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index b03603c5b..cfe92fa97 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -88,15 +88,15 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
     override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
         var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
         dictionary["isa"] = .string(CommentedString(type(of: self).isa))
-        if let exceptionsReferences = self.exceptionsReferences {
-            dictionary["exceptions"] = .array(exceptionsReferences.map({ exceptionReference in
-                return .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
-            }))
+        if let exceptionsReferences {
+            dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in
+                .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
+            })
         }
-        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: self.explicitFileTypes.map({ (relativePath, fileType) in
-            return (CommentedString(relativePath), .string(CommentedString(fileType)))
-        })))
-        dictionary["explicitFolders"] = .array(explicitFolders.map({ .string(CommentedString($0)) }))
+        dictionary["explicitFileTypes"] = .dictionary(Dictionary(uniqueKeysWithValues: explicitFileTypes.map { relativePath, fileType in
+            (CommentedString(relativePath), .string(CommentedString(fileType)))
+        }))
+        dictionary["explicitFolders"] = .array(explicitFolders.map { .string(CommentedString($0)) })
         return (key: CommentedString(reference,
                                      comment: name ?? path),
                 value: .dictionary(dictionary))
diff --git a/Sources/XcodeProj/Objects/Project/PBXObjects.swift b/Sources/XcodeProj/Objects/Project/PBXObjects.swift
index 34ff68b66..22cfe75cd 100644
--- a/Sources/XcodeProj/Objects/Project/PBXObjects.swift
+++ b/Sources/XcodeProj/Objects/Project/PBXObjects.swift
@@ -183,7 +183,9 @@ class PBXObjects: Equatable {
             lhs.carbonResourcesBuildPhases == rhs.carbonResourcesBuildPhases &&
             lhs.buildRules == rhs.buildRules &&
             lhs.swiftPackageProductDependencies == rhs._swiftPackageProductDependencies &&
-            lhs.remoteSwiftPackageReferences == rhs.remoteSwiftPackageReferences
+            lhs.remoteSwiftPackageReferences == rhs.remoteSwiftPackageReferences &&
+            lhs.fileSystemSynchronizedRootGroups == rhs.fileSystemSynchronizedRootGroups &&
+            lhs.fileSystemSynchronizedBuildFileExceptionSets == rhs.fileSystemSynchronizedBuildFileExceptionSets
     }
 
     // MARK: - Helpers
@@ -290,7 +292,12 @@ class PBXObjects: Equatable {
             return _remoteSwiftPackageReferences.remove(at: index).value
         } else if let index = swiftPackageProductDependencies.index(forKey: reference) {
             return _swiftPackageProductDependencies.remove(at: index).value
+        } else if let index = fileSystemSynchronizedRootGroups.index(forKey: reference) {
+            return _fileSystemSynchronizedRootGroups.remove(at: index).value
+        } else if let index = fileSystemSynchronizedBuildFileExceptionSets.index(forKey: reference) {
+            return _fileSystemSynchronizedBuildFileExceptionSets.remove(at: index).value
         }
+
         return nil
     }
 
@@ -352,6 +359,10 @@ class PBXObjects: Equatable {
             return object
         } else if let object = swiftPackageProductDependencies[reference] {
             return object
+        } else if let object = fileSystemSynchronizedRootGroups[reference] {
+            return object
+        } else if let object = fileSystemSynchronizedBuildFileExceptionSets[reference] {
+            return object
         } else {
             return nil
         }
@@ -443,5 +454,7 @@ extension PBXObjects {
         carbonResourcesBuildPhases.values.forEach(closure)
         remoteSwiftPackageReferences.values.forEach(closure)
         swiftPackageProductDependencies.values.forEach(closure)
+        fileSystemSynchronizedRootGroups.values.forEach(closure)
+        fileSystemSynchronizedBuildFileExceptionSets.values.forEach(closure)
     }
 }
diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift
index 79a3e1485..378309da8 100644
--- a/Sources/XcodeProj/Objects/Project/PBXProject.swift
+++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift
@@ -443,7 +443,7 @@ extension PBXProject {
             productDependency = XCSwiftPackageProductDependency(productName: productName, package: reference)
             objects.add(object: productDependency)
         }
-        target.packageProductDependencies.append(productDependency)
+        target.packageProductDependencies?.append(productDependency)
 
         return productDependency
     }
@@ -465,7 +465,7 @@ extension PBXProject {
             productDependency = XCSwiftPackageProductDependency(productName: productName)
             objects.add(object: productDependency)
         }
-        target.packageProductDependencies.append(productDependency)
+        target.packageProductDependencies?.append(productDependency)
 
         return productDependency
     }
diff --git a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
index fa73903fd..b23e0b47f 100644
--- a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
+++ b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
@@ -76,15 +76,15 @@ public class PBXTarget: PBXContainerItem {
     }
 
     /// Swift package product references.
-    var packageProductDependencyReferences: [PBXObjectReference]
+    var packageProductDependencyReferences: [PBXObjectReference]?
 
     /// Swift packages products.
-    public var packageProductDependencies: [XCSwiftPackageProductDependency] {
+    public var packageProductDependencies: [XCSwiftPackageProductDependency]? {
         set {
-            packageProductDependencyReferences = newValue.references()
+            packageProductDependencyReferences = newValue?.references()
         }
         get {
-            packageProductDependencyReferences.objects()
+            packageProductDependencyReferences?.objects()
         }
     }
 
@@ -176,10 +176,15 @@ public class PBXTarget: PBXContainerItem {
         } else {
             productReference = nil
         }
-        let packageProductDependencyReferenceStrings: [String] = try container.decodeIfPresent(.packageProductDependencies) ?? []
-        packageProductDependencyReferences = packageProductDependencyReferenceStrings.map {
-            objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
+        let packageProductDependencyReferenceStrings: [String]? = try container.decodeIfPresent(.packageProductDependencies)
+        if let packageProductDependencyReferenceStrings {
+            packageProductDependencyReferences = packageProductDependencyReferenceStrings.map {
+                objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
+            }
+        } else {
+            packageProductDependencyReferences = nil
         }
+
         let fileSystemSynchronizedGroupsReferences: [String]? = try container.decodeIfPresent(.fileSystemSynchronizedGroups)
         if let fileSystemSynchronizedGroupsReferences {
             self.fileSystemSynchronizedGroupsReferences = fileSystemSynchronizedGroupsReferences.map {
@@ -213,7 +218,7 @@ public class PBXTarget: PBXContainerItem {
         if let fileSystemSynchronizedGroupsReferences {
             dictionary["fileSystemSynchronizedGroups"] = .array(fileSystemSynchronizedGroupsReferences.map { fileSystemSynchronizedGroupReference in
                 let fileSystemSynchronizedGroup: PBXFileSystemSynchronizedRootGroup? = fileSystemSynchronizedGroupReference.getObject()
-                return .string(CommentedString(fileSystemSynchronizedGroupReference.value, comment: fileSystemSynchronizedGroup?.name))
+                return .string(CommentedString(fileSystemSynchronizedGroupReference.value, comment: fileSystemSynchronizedGroup?.path))
             })
         }
 
@@ -228,11 +233,12 @@ public class PBXTarget: PBXContainerItem {
             let fileElement: PBXFileElement? = productReference.getObject()
             dictionary["productReference"] = .string(CommentedString(productReference.value, comment: fileElement?.fileName()))
         }
-        if !packageProductDependencies.isEmpty {
+        if let packageProductDependencies {
             dictionary["packageProductDependencies"] = .array(packageProductDependencies.map {
                 PlistValue.string(.init($0.reference.value, comment: $0.productName))
             })
         }
+
         return (key: CommentedString(reference, comment: name),
                 value: .dictionary(dictionary))
     }
diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift
index f2b9e0912..5a343bfec 100644
--- a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift
+++ b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift
@@ -67,7 +67,7 @@ final class PBXProjectTests: XCTestCase {
         // Then
         XCTAssertEqual(packageProduct, objects.buildFiles.first?.value.product)
         XCTAssertEqual(packageProduct, objects.swiftPackageProductDependencies.first?.value)
-        XCTAssertEqual(packageProduct, target.packageProductDependencies.first)
+        XCTAssertEqual(packageProduct, target.packageProductDependencies?.first)
 
         XCTAssertEqual(objects.fileReferences.first?.value.name, "Product")
 
@@ -240,8 +240,8 @@ final class PBXProjectTests: XCTestCase {
         XCTAssertEqual(packageProduct, secondPackageProduct)
         XCTAssertEqual(packageProduct, thirdPackageProduct)
         XCTAssertEqual(project.remotePackages.count, 1)
-        XCTAssertEqual(target.packageProductDependencies.count, 2)
-        XCTAssertEqual(secondTarget.packageProductDependencies.count, 1)
+        XCTAssertEqual(target.packageProductDependencies?.count, 2)
+        XCTAssertEqual(secondTarget.packageProductDependencies?.count, 1)
         XCTAssertNotEqual(buildPhase.files?.first?.hashValue, secondBuildPhase.files?.first?.hashValue)
         XCTAssertEqual(objects.swiftPackageProductDependencies.count, 2)
 
diff --git a/Tests/XcodeProjTests/Project/XcodeProjTests.swift b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift
similarity index 89%
rename from Tests/XcodeProjTests/Project/XcodeProjTests.swift
rename to Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift
index 424767edd..8bcb72580 100644
--- a/Tests/XcodeProjTests/Project/XcodeProjTests.swift
+++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift
@@ -25,6 +25,11 @@ final class XcodeProjIntegrationTests: XCTestCase {
                                         initModel: XcodeProj.init(path:))
     }
 
+    func test_read_write_produces_no_diff_when_synchronizedRootGroupsFixture() throws {
+        try testReadWriteProducesNoDiff(from: synchronizedRootGroupsFixturePath,
+                                        initModel: XcodeProj.init(path:))
+    }
+
     func test_initialize_PBXProj_with_data() throws {
         // Given
         let pbxprojPath = iosProjectPath + "project.pbxproj"
@@ -90,4 +95,8 @@ final class XcodeProjIntegrationTests: XCTestCase {
     private var iosProjectPath: Path {
         fixturesPath() + "iOS/Project.xcodeproj"
     }
+
+    private var synchronizedRootGroupsFixturePath: Path {
+        fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj"
+    }
 }

From fe6311f9814f62e6561f36fc60d6a4e215774681 Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Thu, 8 Aug 2024 15:56:38 +0200
Subject: [PATCH 10/12] Fix issues after rebasing

---
 Sources/XcodeProj/Utils/ReferenceGenerator.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/XcodeProj/Utils/ReferenceGenerator.swift b/Sources/XcodeProj/Utils/ReferenceGenerator.swift
index 823c582bd..a098c8298 100644
--- a/Sources/XcodeProj/Utils/ReferenceGenerator.swift
+++ b/Sources/XcodeProj/Utils/ReferenceGenerator.swift
@@ -100,7 +100,7 @@ final class ReferenceGenerator: ReferenceGenerating {
             identifiers.append(target.name)
 
             // Packages
-            for packageProductDependency in target.packageProductDependencies {
+            target.packageProductDependencies?.forEach { packageProductDependency in
                 var identifiers = identifiers
                 identifiers.append(packageProductDependency.productName)
                 fixReference(for: packageProductDependency, identifiers: identifiers)

From 029c3dd9a3f27f376973d63596404e2ba1f0344e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pedro=20Pi=C3=B1era=20Buend=C3=ADa?=
 <663605+pepicrft@users.noreply.github.com>
Date: Sat, 10 Aug 2024 10:49:28 +0200
Subject: [PATCH 11/12] Update
 Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift

Co-authored-by: Kas <kwridan@users.noreply.github.com>
---
 .../Objects/Files/PBXFileSystemSynchronizedRootGroup.swift      | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index cfe92fa97..816ae9268 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -88,7 +88,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
     override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
         var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
         dictionary["isa"] = .string(CommentedString(type(of: self).isa))
-        if let exceptionsReferences {
+        if let exceptionsReferences, !exceptionReferences.isEmpty {
             dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in
                 .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
             })

From a73dee4bba3d7ee4c273925ce38a3a12e9e34abc Mon Sep 17 00:00:00 2001
From: Pedro <pedro@pepicrft.me>
Date: Sat, 10 Aug 2024 18:20:16 +0200
Subject: [PATCH 12/12] Add missing attributes

---
 ...temSynchronizedBuildFileExceptionSet.swift | 28 ++++++++++++++++++-
 .../PBXFileSystemSynchronizedRootGroup.swift  |  2 +-
 ...onizedBuildFileExceptionSet+Fixtures.swift |  8 ++++--
 3 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
index b883cc8dd..572380cf8 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -15,6 +15,16 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSer
     /// Every item in the list is the relative path inside the root synchronized group.
     public var privateHeaders: [String]?
 
+    /// Additional compiler flags by relative path.
+    /// Every item in the list is the relative path inside the root synchronized group.
+    /// The value is the additional compiler flags.
+    public var additionalCompilerFlagsByRelativePath: [String: String]?
+
+    /// Attributes by relative path.
+    /// Every item in the list is the relative path inside the root synchronized group.
+    /// This is used for example when linking frameworks to specify that they are optional with the attribute "Weak"
+    public var attributesByRelativePath: [String: [String]]?
+
     var targetReference: PBXObjectReference
 
     public var target: PBXTarget! {
@@ -28,11 +38,13 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSer
 
     // MARK: - Init
 
-    public init(target: PBXTarget, membershipExceptions: [String]?, publicHeaders: [String]?, privateHeaders: [String]?) {
+    public init(target: PBXTarget, membershipExceptions: [String]?, publicHeaders: [String]?, privateHeaders: [String]?, additionalCompilerFlagsByRelativePath: [String: String]?, attributesByRelativePath: [String: [String]]?) {
         targetReference = target.reference
         self.membershipExceptions = membershipExceptions
         self.publicHeaders = publicHeaders
         self.privateHeaders = privateHeaders
+        self.additionalCompilerFlagsByRelativePath = additionalCompilerFlagsByRelativePath
+        self.attributesByRelativePath = attributesByRelativePath
         super.init()
     }
 
@@ -43,6 +55,8 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSer
         case membershipExceptions
         case publicHeaders
         case privateHeaders
+        case additionalCompilerFlagsByRelativePath
+        case attributesByRelativePath
     }
 
     public required init(from decoder: Decoder) throws {
@@ -54,6 +68,8 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSer
         membershipExceptions = try container.decodeIfPresent(.membershipExceptions)
         publicHeaders = try container.decodeIfPresent(.publicHeaders)
         privateHeaders = try container.decodeIfPresent(.privateHeaders)
+        additionalCompilerFlagsByRelativePath = try container.decodeIfPresent(.additionalCompilerFlagsByRelativePath)
+        attributesByRelativePath = try container.decodeIfPresent(.attributesByRelativePath)
         try super.init(from: decoder)
     }
 
@@ -78,6 +94,16 @@ public class PBXFileSystemSynchronizedBuildFileExceptionSet: PBXObject, PlistSer
         if let privateHeaders {
             dictionary["privateHeaders"] = .array(privateHeaders.map { .string(CommentedString($0)) })
         }
+        if let additionalCompilerFlagsByRelativePath {
+            dictionary["additionalCompilerFlagsByRelativePath"] = .dictionary(Dictionary(uniqueKeysWithValues: additionalCompilerFlagsByRelativePath.map { key, value in
+                (CommentedString(key), PlistValue.string(CommentedString(value)))
+            }))
+        }
+        if let attributesByRelativePath {
+            dictionary["attributesByRelativePath"] = .dictionary(Dictionary(uniqueKeysWithValues: attributesByRelativePath.map { key, value in
+                (CommentedString(key), .array(value.map { .string(CommentedString($0)) }))
+            }))
+        }
         dictionary["target"] = .string(CommentedString(target.reference.value, comment: target.name))
         return (key: CommentedString(reference, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"), value: .dictionary(dictionary))
     }
diff --git a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
index 816ae9268..5df7fe342 100644
--- a/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -88,7 +88,7 @@ public class PBXFileSystemSynchronizedRootGroup: PBXFileElement {
     override func plistKeyAndValue(proj: PBXProj, reference: String) throws -> (key: CommentedString, value: PlistValue) {
         var dictionary: [CommentedString: PlistValue] = try super.plistKeyAndValue(proj: proj, reference: reference).value.dictionary ?? [:]
         dictionary["isa"] = .string(CommentedString(type(of: self).isa))
-        if let exceptionsReferences, !exceptionReferences.isEmpty {
+        if let exceptionsReferences, !exceptionsReferences.isEmpty {
             dictionary["exceptions"] = .array(exceptionsReferences.map { exceptionReference in
                 .string(CommentedString(exceptionReference.value, comment: "PBXFileSystemSynchronizedBuildFileExceptionSet"))
             })
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
index 67ad69832..17e0b1f0d 100644
--- a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
@@ -4,10 +4,14 @@ extension PBXFileSystemSynchronizedBuildFileExceptionSet {
     static func fixture(target: PBXTarget = .fixture(),
                         membershipExceptions: [String]? = [],
                         publicHeaders: [String]? = [],
-                        privateHeaders: [String]? = []) -> PBXFileSystemSynchronizedBuildFileExceptionSet {
+                        privateHeaders: [String]? = [],
+                        additionalCompilerFlagsByRelativePath: [String: String]? = nil,
+                        attributesByRelativePath: [String: [String]]? = nil) -> PBXFileSystemSynchronizedBuildFileExceptionSet {
         PBXFileSystemSynchronizedBuildFileExceptionSet(target: target,
                                                        membershipExceptions: membershipExceptions,
                                                        publicHeaders: publicHeaders,
-                                                       privateHeaders: privateHeaders)
+                                                       privateHeaders: privateHeaders,
+                                                       additionalCompilerFlagsByRelativePath: additionalCompilerFlagsByRelativePath,
+                                                       attributesByRelativePath: attributesByRelativePath)
     }
 }
