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 = ""; }; +/* 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 = ""; + }; + 6CF05B8D2C53F5F200EF267F /* Products */ = { + isa = PBXGroup; + children = ( + 6CF05B8C2C53F5F200EF267F /* SynchronizedRootGroups.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* 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 @@ + + + + + SchemeUserState + + SynchronizedRootGroups.xcscheme_^#shared#^_ + + orderHint + 0 + + + + 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 = \"\"; };", 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)!)