diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml
index 7d921922f..5d1e3b5f7 100644
--- a/.github/workflows/xcodeproj.yml
+++ b/.github/workflows/xcodeproj.yml
@@ -1,7 +1,11 @@
 # 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 }}
@@ -52,6 +56,7 @@ jobs:
           git config --global init.defaultBranch main
       - name: Build and run tests
         run: swift test --enable-test-discovery
+
   lint:
     name: Lint
     runs-on: macos-latest
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.toml b/.mise.toml
index 317af7dd7..ec954e154 100644
--- a/.mise.toml
+++ b/.mise.toml
@@ -1,3 +1,4 @@
 [tools]
-swiftlint = "0.54.0"
 swiftformat = "0.54.3"
+tuist = "4.21.2"
+swiftlint = "0.55.1"
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
new file mode 100644
index 000000000..572380cf8
--- /dev/null
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet.swift
@@ -0,0 +1,110 @@
+import Foundation
+
+/// Class representing an element that may contain other elements.
+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]?
+
+    /// 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! {
+        get {
+            targetReference.getObject() as? PBXTarget
+        }
+        set {
+            targetReference = newValue.reference
+        }
+    }
+
+    // MARK: - Init
+
+    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()
+    }
+
+    // MARK: - Decodable
+
+    fileprivate enum CodingKeys: String, CodingKey {
+        case target
+        case membershipExceptions
+        case publicHeaders
+        case privateHeaders
+        case additionalCompilerFlagsByRelativePath
+        case attributesByRelativePath
+    }
+
+    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)
+        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)
+    }
+
+    // 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)) })
+        }
+        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
new file mode 100644
index 000000000..5df7fe342
--- /dev/null
+++ b/Sources/XcodeProj/Objects/Files/PBXFileSystemSynchronizedRootGroup.swift
@@ -0,0 +1,109 @@
+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]?
+
+    /// 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
+        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)
+        explicitFileTypes = try (container.decodeIfPresent(.explicitFileTypes)) ?? [:]
+        let exceptionsReferences: [String] = try (container.decodeIfPresent(.exceptions)) ?? []
+        self.exceptionsReferences = exceptionsReferences.map { objectReferenceRepository.getOrCreate(reference: $0, objects: objects) }
+        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))
+        if let exceptionsReferences, !exceptionsReferences.isEmpty {
+            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)) })
+        return (key: CommentedString(reference,
+                                     comment: name ?? path),
+                value: .dictionary(dictionary))
+    }
+
+    override func isEqual(to object: Any?) -> Bool {
+        guard let rhs = object as? PBXFileSystemSynchronizedRootGroup else { return false }
+        return isEqual(to: rhs)
+    }
+}
diff --git a/Sources/XcodeProj/Objects/Files/PBXGroup.swift b/Sources/XcodeProj/Objects/Files/PBXGroup.swift
index 05cece653..8b8d2814b 100644
--- a/Sources/XcodeProj/Objects/Files/PBXGroup.swift
+++ b/Sources/XcodeProj/Objects/Files/PBXGroup.swift
@@ -116,6 +116,16 @@ public extension PBXGroup {
             .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.
     ///
     /// - Parameter name: file 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..22cfe75cd 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
@@ -173,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
@@ -218,6 +230,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")
         }
     }
@@ -278,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
     }
 
@@ -340,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
         }
@@ -431,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/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/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/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
index c20d93c20..4c9a1d98c 100644
--- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
+++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift
@@ -301,3 +301,24 @@ 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 }
+        if publicHeaders != rhs.publicHeaders { return false }
+        if privateHeaders != rhs.privateHeaders { return false }
+        return super.isEqual(to: rhs)
+    }
+}
diff --git a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
index 86646cfd8..b23e0b47f 100644
--- a/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
+++ b/Sources/XcodeProj/Objects/Targets/PBXTarget.swift
@@ -76,15 +76,28 @@ 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()
+        }
+    }
+
+    // File system synchronized groups references.
+    var fileSystemSynchronizedGroupsReferences: [PBXObjectReference]?
+
+    // File system synchronized groups.
+    public var fileSystemSynchronizedGroups: [PBXFileSystemSynchronizedRootGroup]? {
+        set {
+            fileSystemSynchronizedGroupsReferences = newValue?.references()
+        }
+        get {
+            fileSystemSynchronizedGroupsReferences?.objects()
         }
     }
 
@@ -110,12 +123,14 @@ 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 +150,7 @@ public class PBXTarget: PBXContainerItem {
         case productReference
         case productType
         case packageProductDependencies
+        case fileSystemSynchronizedGroups
     }
 
     public required init(from decoder: Decoder) throws {
@@ -160,12 +176,21 @@ 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 {
+                objectReferenceRepository.getOrCreate(reference: $0, objects: objects)
+            }
+        }
         productType = try container.decodeIfPresent(.productType)
         try super.init(from: decoder)
     }
@@ -190,6 +215,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?.path))
+            })
+        }
+
         dictionary["name"] = .string(CommentedString(name))
         if let productName {
             dictionary["productName"] = .string(CommentedString(productName))
@@ -201,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/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)
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
new file mode 100644
index 000000000..17e0b1f0d
--- /dev/null
+++ b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSet+Fixtures.swift
@@ -0,0 +1,17 @@
+import XcodeProj
+
+extension PBXFileSystemSynchronizedBuildFileExceptionSet {
+    static func fixture(target: PBXTarget = .fixture(),
+                        membershipExceptions: [String]? = [],
+                        publicHeaders: [String]? = [],
+                        privateHeaders: [String]? = [],
+                        additionalCompilerFlagsByRelativePath: [String: String]? = nil,
+                        attributesByRelativePath: [String: [String]]? = nil) -> PBXFileSystemSynchronizedBuildFileExceptionSet {
+        PBXFileSystemSynchronizedBuildFileExceptionSet(target: target,
+                                                       membershipExceptions: membershipExceptions,
+                                                       publicHeaders: publicHeaders,
+                                                       privateHeaders: privateHeaders,
+                                                       additionalCompilerFlagsByRelativePath: additionalCompilerFlagsByRelativePath,
+                                                       attributesByRelativePath: attributesByRelativePath)
+    }
+}
diff --git a/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift b/Tests/XcodeProjTests/Objects/Files/PBXFileSystemSynchronizedBuildFileExceptionSetTests.swift
new file mode 100644
index 000000000..842954846
--- /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..c4eabf4f7
--- /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 {
+        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..374156104
--- /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)
+    }
+}
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/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"
+    }
 }
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)!)
