diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml index dd16df6b0..591f2df6c 100644 --- a/.github/workflows/xcodeproj.yml +++ b/.github/workflows/xcodeproj.yml @@ -31,6 +31,8 @@ jobs: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - uses: swift-actions/setup-swift@v2 + with: + swift-version: "6.0.2" - name: Build run: swift build --configuration release test: @@ -49,6 +51,8 @@ jobs: - uses: actions/checkout@v3 - uses: jdx/mise-action@v2 - uses: swift-actions/setup-swift@v2 + with: + swift-version: "6.0.2" - run: | git config --global user.email 'xcodeproj@tuist.dev' git config --global user.name 'xcodeproj' diff --git a/Package.swift b/Package.swift index 3511847a3..6364f98a0 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.10.0 +// swift-tools-version:6.0.0 import PackageDescription diff --git a/Sources/XcodeProj/Extensions/Path+Extras.swift b/Sources/XcodeProj/Extensions/Path+Extras.swift index 75d0f1547..4b76668eb 100644 --- a/Sources/XcodeProj/Extensions/Path+Extras.swift +++ b/Sources/XcodeProj/Extensions/Path+Extras.swift @@ -43,7 +43,7 @@ extension Path { let matchc = gt.gl_pathc #endif return (0 ..< Int(matchc)).compactMap { index in - if let path = String(validatingUTF8: gt.gl_pathv[index]!) { + if let path = String(validatingCString: gt.gl_pathv[index]!) { return Path(path) } return nil diff --git a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift new file mode 100644 index 000000000..0c2a101f1 --- /dev/null +++ b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift @@ -0,0 +1,55 @@ +public enum BuildFileSetting: Sendable, Equatable, Hashable { + case string(String) + case array([String]) + + public var stringValue: String? { + if case let .string(value) = self { + value + } else { + nil + } + } + + public var arrayValue: [String]? { + if case let .array(value) = self { + value + } else { + nil + } + } +} + +extension BuildFileSetting: Codable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + do { + let string = try container.decode(String.self) + self = .string(string) + } catch { + let array = try container.decode([String].self) + self = .array(array) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .string(string): + try container.encode(string) + case let .array(array): + try container.encode(array) + } + } +} + +extension BuildFileSetting: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension BuildFileSetting: ExpressibleByStringInterpolation { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift index 5bf89bc25..094132d79 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift @@ -31,7 +31,25 @@ public final class PBXBuildFile: PBXObject { } /// Element settings - public var settings: [String: Any]? + public var settings: [String: BuildFileSetting]? + + /// Potentially present for `PBXHeadersBuildPhase` : https://buck.build/javadoc/com/facebook/buck/apple/xcode/xcodeproj/PBXBuildFile.html + public var attributes: [String]? { + if case let .array(attributes) = settings?["ATTRIBUTES"] { + attributes + } else { + nil + } + } + + /// Potentially present for `PBXSourcesBuildPhase` : https://buck.build/javadoc/com/facebook/buck/apple/xcode/xcodeproj/PBXBuildFile.html + public var compilerFlags: String? { + if case let .string(compilerFlags) = settings?["COMPILER_FLAGS"] { + compilerFlags + } else { + nil + } + } /// Platform filter attribute. /// Introduced in: Xcode 11 @@ -53,7 +71,7 @@ public final class PBXBuildFile: PBXObject { /// - settings: build file settings. public init(file: PBXFileElement? = nil, product: XCSwiftPackageProductDependency? = nil, - settings: [String: Any]? = nil, + settings: [String: BuildFileSetting]? = nil, platformFilter: String? = nil, platformFilters: [String]? = nil) { fileReference = file?.reference @@ -84,7 +102,7 @@ public final class PBXBuildFile: PBXObject { if let productRefString: String = try container.decodeIfPresent(.productRef) { productReference = objectReferenceRepository.getOrCreate(reference: productRefString, objects: objects) } - settings = try container.decodeIfPresent([String: Any].self, forKey: .settings) + settings = try container.decodeIfPresent([String: BuildFileSetting].self, forKey: .settings) platformFilter = try container.decodeIfPresent(.platformFilter) platformFilters = try container.decodeIfPresent([String].self, forKey: .platformFilters) try super.init(from: decoder) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 066dbb50d..5ca94d202 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -1,4 +1,82 @@ import Foundation /// Build settings. -public typealias BuildSettings = [String: Any] +public typealias BuildSettings = [String: BuildSetting] + +private let yes = "YES" +private let no = "NO" + +public enum BuildSetting: Sendable, Equatable { + case string(String) + case array([String]) + + public var stringValue: String? { + if case let .string(value) = self { + value + } else { + nil + } + } + + public var boolValue: Bool? { + if case let .string(value) = self { + switch value { + case yes: true + case no: false + default: nil + } + } else { + nil + } + } + + public var arrayValue: [String]? { + if case let .array(value) = self { + value + } else { + nil + } + } +} + +extension BuildSetting: CustomStringConvertible { + public var description: String { + switch self { + case let .string(string): + string + case let .array(array): + array.joined(separator: " ") + } + } +} + +extension BuildSetting: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + do { + let string = try container.decode(String.self) + self = .string(string) + } catch { + let array = try container.decode([String].self) + self = .array(array) + } + } +} + +extension BuildSetting: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension BuildSetting: ExpressibleByStringInterpolation { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} + +extension BuildSetting: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .string(value ? yes : no) + } +} diff --git a/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift b/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift index 71e05c3dc..159dc201c 100644 --- a/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift +++ b/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift @@ -59,7 +59,7 @@ public final class XCBuildConfiguration: PBXObject { } else { baseConfigurationReference = nil } - buildSettings = try container.decode([String: Any].self, forKey: .buildSettings) + buildSettings = try container.decode(BuildSettings.self, forKey: .buildSettings) name = try container.decode(.name) try super.init(from: decoder) } @@ -75,16 +75,16 @@ public final class XCBuildConfiguration: PBXObject { public func append(setting name: String, value: String) { guard !value.isEmpty else { return } - let existing: Any = buildSettings[name] ?? "$(inherited)" + let existing: BuildSetting = buildSettings[name] ?? "$(inherited)" switch existing { - case let string as String where string != value: + case let .string(string) where string != value: let newValue = [string, value].joined(separator: " ") - buildSettings[name] = newValue - case let array as [String]: + buildSettings[name] = .string(newValue) + case let .array(array): var newValue = array newValue.append(value) - buildSettings[name] = newValue.uniqued() + buildSettings[name] = .array(newValue.uniqued()) default: break } diff --git a/Sources/XcodeProj/Objects/Project/PBXProj.swift b/Sources/XcodeProj/Objects/Project/PBXProj.swift index 78e6da101..c8faeb274 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProj.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProj.swift @@ -14,7 +14,8 @@ public final class PBXProj: Decodable { public var objectVersion: UInt /// Project classes. - public var classes: [String: Any] + /// This appears to always be empty as defined here: http://www.monobjc.net/xcode-project-file-format.html + public var classes: [String: [String]] /// Project root object. var rootObjectReference: PBXObjectReference? @@ -40,7 +41,7 @@ public final class PBXProj: Decodable { public init(rootObject: PBXProject? = nil, objectVersion: UInt = Xcode.LastKnown.objectVersion, archiveVersion: UInt = Xcode.LastKnown.archiveVersion, - classes: [String: Any] = [:], + classes: [String: [String]] = [:], objects: [PBXObject] = []) { self.archiveVersion = archiveVersion self.objectVersion = objectVersion @@ -88,7 +89,7 @@ public final class PBXProj: Decodable { rootObject: PBXProject? = nil, objectVersion: UInt = Xcode.LastKnown.objectVersion, archiveVersion: UInt = Xcode.LastKnown.archiveVersion, - classes: [String: Any] = [:], + classes: [String: [String]] = [:], objects: PBXObjects ) { self.archiveVersion = archiveVersion @@ -116,7 +117,7 @@ public final class PBXProj: Decodable { self.rootObjectReference = objectReferenceRepository.getOrCreate(reference: rootObjectReference, objects: objects) objectVersion = try container.decodeIntIfPresent(.objectVersion) ?? 0 archiveVersion = try container.decodeIntIfPresent(.archiveVersion) ?? 1 - classes = try container.decodeIfPresent([String: Any].self, forKey: .classes) ?? [:] + classes = try container.decodeIfPresent([String: [String]].self, forKey: .classes) ?? [:] let objectsDictionary: [String: PBXObjectDictionaryEntry] = try container.decodeIfPresent([String: PBXObjectDictionaryEntry].self, forKey: .objects) ?? [:] for entry in objectsDictionary { @@ -261,10 +262,9 @@ extension PBXProj { extension PBXProj: Equatable { public static func == (lhs: PBXProj, rhs: PBXProj) -> Bool { - let equalClasses = NSDictionary(dictionary: lhs.classes).isEqual(to: rhs.classes) - return lhs.archiveVersion == rhs.archiveVersion && + lhs.archiveVersion == rhs.archiveVersion && lhs.objectVersion == rhs.objectVersion && - equalClasses && + lhs.classes == rhs.classes && lhs.objects == rhs.objects } } diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift index 8e8ddcab5..85b3cae41 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProject.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift @@ -108,20 +108,20 @@ public final class PBXProject: PBXObject { /// Project attributes. /// Target attributes will be merged into this - public var attributes: [String: Any] + public var attributes: [String: ProjectAttribute] /// Target attribute references. - var targetAttributeReferences: [PBXObjectReference: [String: Any]] + var targetAttributeReferences: [PBXObjectReference: [String: ProjectAttribute]] /// Target attributes. - public var targetAttributes: [PBXTarget: [String: Any]] { + public var targetAttributes: [PBXTarget: [String: ProjectAttribute]] { set { targetAttributeReferences = [:] for item in newValue { targetAttributeReferences[item.key.reference] = item.value } } get { - var attributes: [PBXTarget: [String: Any]] = [:] + var attributes: [PBXTarget: [String: ProjectAttribute]] = [:] for targetAttributeReference in targetAttributeReferences { if let object: PBXTarget = targetAttributeReference.key.getObject() { attributes[object] = targetAttributeReference.value @@ -176,7 +176,7 @@ public final class PBXProject: PBXObject { /// - Parameters: /// - attributes: attributes that will be set. /// - target: target. - public func setTargetAttributes(_ attributes: [String: Any], target: PBXTarget) { + public func setTargetAttributes(_ attributes: [String: ProjectAttribute], target: PBXTarget) { targetAttributeReferences[target.reference] = attributes } @@ -321,8 +321,8 @@ public final class PBXProject: PBXObject { projectRoots: [String] = [], targets: [PBXTarget] = [], packages: [XCRemoteSwiftPackageReference] = [], - attributes: [String: Any] = [:], - targetAttributes: [PBXTarget: [String: Any]] = [:]) { + attributes: [String: ProjectAttribute] = [:], + targetAttributes: [PBXTarget: [String: ProjectAttribute]] = [:]) { self.name = name buildConfigurationListReference = buildConfigurationList.reference self.compatibilityVersion = compatibilityVersion @@ -417,10 +417,12 @@ public final class PBXProject: PBXObject { let packageRefeferenceStrings: [String] = try container.decodeIfPresent(.packageReferences) ?? [] packageReferences = packageRefeferenceStrings.map { referenceRepository.getOrCreate(reference: $0, objects: objects) } - var attributes = try (container.decodeIfPresent([String: Any].self, forKey: .attributes) ?? [:]) - var targetAttributeReferences: [PBXObjectReference: [String: Any]] = [:] - if let targetAttributes = attributes[PBXProject.targetAttributesKey] as? [String: [String: Any]] { - targetAttributes.forEach { targetAttributeReferences[referenceRepository.getOrCreate(reference: $0.key, objects: objects)] = $0.value } + var attributes = try (container.decodeIfPresent([String: ProjectAttribute].self, forKey: .attributes) ?? [:]) + var targetAttributeReferences: [PBXObjectReference: [String: ProjectAttribute]] = [:] + if case let .attributeDictionary(targetAttributes) = attributes[PBXProject.targetAttributesKey] { + for targetAttribute in targetAttributes { + targetAttributeReferences[referenceRepository.getOrCreate(reference: targetAttribute.key, objects: objects)] = targetAttribute.value + } attributes[PBXProject.targetAttributesKey] = nil } self.attributes = attributes @@ -562,16 +564,17 @@ extension PBXProject: PlistSerializable { dictionary["packageReferences"] = PlistValue.array(finalPackageReferences) } - var plistAttributes: [String: Any] = attributes + var plistAttributes: [String: ProjectAttribute] = attributes // merge target attributes - var plistTargetAttributes: [String: Any] = [:] + var plistTargetAttributes: [String: [String: ProjectAttribute]] = [:] for (reference, value) in targetAttributeReferences { - plistTargetAttributes[reference.value] = value.mapValues { value in - (value as? PBXObject)?.reference.value ?? value - } + plistTargetAttributes[reference.value] = value + } + + if !plistTargetAttributes.isEmpty { + plistAttributes[PBXProject.targetAttributesKey] = .attributeDictionary(plistTargetAttributes) } - plistAttributes[PBXProject.targetAttributesKey] = plistTargetAttributes.isEmpty ? nil : plistTargetAttributes dictionary["attributes"] = plistAttributes.plist() diff --git a/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift b/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift new file mode 100644 index 000000000..02a78f1d7 --- /dev/null +++ b/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift @@ -0,0 +1,55 @@ +public enum ProjectAttribute: Equatable { + case string(String) + case array([String]) + case targetReference(PBXObject) + case attributeDictionary([String: [String: ProjectAttribute]]) + + public var stringValue: String? { + if case let .string(value) = self { + value + } else { + nil + } + } + + public var arrayValue: [String]? { + if case let .array(value) = self { + value + } else { + nil + } + } +} + +extension ProjectAttribute: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if let string = try? container.decode(String.self) { + self = .string(string) + } else if let array = try? container.decode([String].self) { + self = .array(array) + } else { + let targetAttributes = try container.decode([String: [String: ProjectAttribute]].self) + self = .attributeDictionary(targetAttributes) + } + } +} + +extension ProjectAttribute: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension ProjectAttribute: ExpressibleByStringInterpolation { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} + +extension ProjectAttribute: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, [String: ProjectAttribute])...) { + self = .attributeDictionary(Dictionary(uniqueKeysWithValues: elements)) + } +} diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift index b8087736f..cd69699a9 100644 --- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift +++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift @@ -15,7 +15,7 @@ extension PBXBuildFile { func isEqual(to rhs: PBXBuildFile) -> Bool { if fileReference != rhs.fileReference { return false } if productReference != rhs.productReference { return false } - if !NSDictionary(dictionary: settings ?? [:]).isEqual(NSDictionary(dictionary: rhs.settings ?? [:])) { return false } + if settings != rhs.settings { return false } if platformFilter != rhs.platformFilter { return false } if buildPhase != rhs.buildPhase { return false } return super.isEqual(to: rhs) @@ -166,8 +166,8 @@ extension PBXProject { if projectReferences != rhs.projectReferences { return false } if projectRoots != rhs.projectRoots { return false } if targetReferences != rhs.targetReferences { return false } - if !NSDictionary(dictionary: attributes).isEqual(NSDictionary(dictionary: rhs.attributes)) { return false } - if !NSDictionary(dictionary: targetAttributeReferences).isEqual(NSDictionary(dictionary: rhs.targetAttributeReferences)) { return false } + if attributes != rhs.attributes { return false } + if targetAttributeReferences != rhs.targetAttributeReferences { return false } if packageReferences != rhs.packageReferences { return false } if remotePackages != rhs.remotePackages { return false } if localPackages != rhs.localPackages { return false } @@ -259,7 +259,7 @@ extension XCBuildConfiguration { /// :nodoc: func isEqual(to rhs: XCBuildConfiguration) -> Bool { if baseConfigurationReference != rhs.baseConfigurationReference { return false } - if !NSDictionary(dictionary: buildSettings).isEqual(NSDictionary(dictionary: rhs.buildSettings)) { return false } + if buildSettings != rhs.buildSettings { return false } if name != rhs.name { return false } return super.isEqual(to: rhs) } diff --git a/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift b/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift index da8f87c44..f6b6a4b42 100644 --- a/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift +++ b/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift @@ -6,12 +6,6 @@ public extension XCScheme { // MARK: - Attributes public var skipped: Bool - @available(*, deprecated, message: "Please use parallelization property instead") - public var parallelizable: Bool { - get { parallelization == .swiftTestingOnly } - set { parallelization = newValue ? .swiftTestingOnly : .none } - } - public var parallelization: TestParallelization public var randomExecutionOrdering: Bool public var useTestSelectionWhitelist: Bool? @@ -40,25 +34,6 @@ public extension XCScheme { self.skippedTests = skippedTests } - @available(*, deprecated, message: "Use init with Parallelization argument instead") - public init(skipped: Bool, - parallelizable: Bool = false, - randomExecutionOrdering: Bool = false, - buildableReference: BuildableReference, - locationScenarioReference: LocationScenarioReference? = nil, - skippedTests: [TestItem] = [], - selectedTests: [TestItem] = [], - useTestSelectionWhitelist: Bool? = nil) { - self.skipped = skipped - parallelization = parallelizable ? .swiftTestingOnly : .none - self.randomExecutionOrdering = randomExecutionOrdering - self.buildableReference = buildableReference - self.locationScenarioReference = locationScenarioReference - self.useTestSelectionWhitelist = useTestSelectionWhitelist - self.selectedTests = selectedTests - self.skippedTests = skippedTests - } - init(element: AEXMLElement) throws { skipped = element.attributes["skipped"] == "YES" @@ -139,7 +114,7 @@ public extension XCScheme { public static func == (lhs: TestableReference, rhs: TestableReference) -> Bool { lhs.skipped == rhs.skipped && - lhs.parallelizable == rhs.parallelizable && + lhs.parallelization == rhs.parallelization && lhs.randomExecutionOrdering == rhs.randomExecutionOrdering && lhs.buildableReference == rhs.buildableReference && lhs.locationScenarioReference == rhs.locationScenarioReference && diff --git a/Sources/XcodeProj/Utils/BuildSettingsProvider.swift b/Sources/XcodeProj/Utils/BuildSettingsProvider.swift index f348d2ad1..71a4d59ca 100644 --- a/Sources/XcodeProj/Utils/BuildSettingsProvider.swift +++ b/Sources/XcodeProj/Utils/BuildSettingsProvider.swift @@ -46,7 +46,7 @@ public class BuildSettingsProvider { /// - swift: true if the target contains Swift code. /// - Returns: build settings. public static func targetDefault(variant: Variant? = nil, platform: Platform?, product: Product?, swift: Bool? = nil) -> BuildSettings { - var buildSettings: [String: Any] = [:] + var buildSettings: BuildSettings = [:] if let platform { buildSettings.merge(targetSettings(platform: platform), uniquingKeysWith: { $1 }) @@ -375,17 +375,6 @@ public class BuildSettingsProvider { [:] } } - - private static func targetSwiftSettings(platform: Platform, product: Product) -> BuildSettings { - switch (platform, product) { - case (.watchOS, .application): - [ - "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES": "YES", - ] - default: - [:] - } - } } // Overloading `~=` enables customizing switch statement pattern matching diff --git a/Sources/XcodeProj/Utils/Decoders.swift b/Sources/XcodeProj/Utils/Decoders.swift index e0cb19b43..31b610065 100644 --- a/Sources/XcodeProj/Utils/Decoders.swift +++ b/Sources/XcodeProj/Utils/Decoders.swift @@ -46,7 +46,7 @@ extension CodingUserInfoKey { } /// Xcodeproj JSON decoder. -class XcodeprojJSONDecoder: JSONDecoder { +final class XcodeprojJSONDecoder: JSONDecoder, @unchecked Sendable { /// Default init. init(context: ProjectDecodingContext = ProjectDecodingContext()) { super.init() @@ -55,7 +55,7 @@ class XcodeprojJSONDecoder: JSONDecoder { } /// Xcodeproj property list decoder. -class XcodeprojPropertyListDecoder: PropertyListDecoder { +final class XcodeprojPropertyListDecoder: PropertyListDecoder, @unchecked Sendable { /// Default init. init(context: ProjectDecodingContext = ProjectDecodingContext()) { super.init() diff --git a/Sources/XcodeProj/Utils/PlistValue.swift b/Sources/XcodeProj/Utils/PlistValue.swift index 92e2e8efe..432a04b29 100644 --- a/Sources/XcodeProj/Utils/PlistValue.swift +++ b/Sources/XcodeProj/Utils/PlistValue.swift @@ -85,6 +85,55 @@ extension PlistValue: Equatable { // MARK: - Dictionary Extension (PlistValue) +extension [String: BuildSetting] { + func plist() -> PlistValue { + var dictionary: [CommentedString: PlistValue] = [:] + forEach { key, value in + switch value { + case let .string(stringValue): + dictionary[CommentedString(key)] = PlistValue.string(CommentedString(stringValue)) + case let .array(arrayValue): + dictionary[CommentedString(key)] = arrayValue.plist() + } + } + return .dictionary(dictionary) + } +} + +extension [String: BuildFileSetting] { + func plist() -> PlistValue { + var dictionary: [CommentedString: PlistValue] = [:] + forEach { key, value in + switch value { + case let .string(stringValue): + dictionary[CommentedString(key)] = PlistValue.string(CommentedString(stringValue)) + case let .array(arrayValue): + dictionary[CommentedString(key)] = arrayValue.plist() + } + } + return .dictionary(dictionary) + } +} + +extension [String: ProjectAttribute] { + func plist() -> PlistValue { + var dictionary: [CommentedString: PlistValue] = [:] + forEach { key, value in + switch value { + case let .string(stringValue): + dictionary[CommentedString(key)] = PlistValue.string(CommentedString(stringValue)) + case let .array(arrayValue): + dictionary[CommentedString(key)] = arrayValue.plist() + case let .attributeDictionary(attributes): + dictionary[CommentedString(key)] = attributes.mapValues { $0.plist() }.plist() + case let .targetReference(object): + dictionary[CommentedString(key)] = .string(CommentedString(object.reference.value)) + } + } + return .dictionary(dictionary) + } +} + extension Dictionary where Key == String { func plist() -> PlistValue { var dictionary: [CommentedString: PlistValue] = [:] @@ -95,6 +144,8 @@ extension Dictionary where Key == String { dictionary[CommentedString(key)] = subDictionary.plist() } else if let string = value as? CustomStringConvertible { dictionary[CommentedString(key)] = .string(CommentedString(string.description)) + } else if let plistValue = value as? PlistValue { + dictionary[CommentedString(key)] = plistValue } } return .dictionary(dictionary) diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift index e1e585ea6..639f6c1b0 100644 --- a/Sources/XcodeProj/Utils/XCConfig.swift +++ b/Sources/XcodeProj/Utils/XCConfig.swift @@ -35,10 +35,10 @@ public final class XCConfig { let fileLines = try path.read().components(separatedBy: "\n") includes = fileLines .compactMap(XCConfigParser.configFrom(path: path, projectPath: projectPath)) - var buildSettings: [String: String] = [:] + var buildSettings: BuildSettings = [:] fileLines .compactMap(XCConfigParser.settingFrom) - .forEach { buildSettings[$0.key] = $0.value } + .forEach { buildSettings[$0.key] = .string($0.value) } self.buildSettings = buildSettings } } @@ -62,7 +62,7 @@ enum XCConfigParser { } return nil } - .compactMap { pathString in + .compactMap { pathString -> (include: Path, config: XCConfig)? in let includePath: Path = .init(pathString) var config: XCConfig? do { @@ -114,7 +114,7 @@ extension XCConfig: Equatable { return false } } - return NSDictionary(dictionary: lhs.buildSettings).isEqual(to: rhs.buildSettings) + return lhs.buildSettings == rhs.buildSettings } } @@ -124,8 +124,8 @@ public extension XCConfig { /// It returns the build settings after flattening all the includes. /// /// - Returns: build settings flattening all the includes. - func flattenedBuildSettings() -> [String: Any] { - var content: [String: Any] = buildSettings + func flattenedBuildSettings() -> [String: BuildSetting] { + var content: [String: BuildSetting] = buildSettings includes .map(\.1) .flattened() diff --git a/Tests/XcodeProjTests/Extensions/XCTestCase+Assertions.swift b/Tests/XcodeProjTests/Extensions/XCTestCase+Assertions.swift index 9d1330dd1..a9b989716 100644 --- a/Tests/XcodeProjTests/Extensions/XCTestCase+Assertions.swift +++ b/Tests/XcodeProjTests/Extensions/XCTestCase+Assertions.swift @@ -12,7 +12,7 @@ extension XCTestCase { return unwrappedObj } - func XCTAssertThrowsSpecificError(_ expression: @autoclosure () throws -> some Any, _ error: E, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) { + func XCTAssertThrowsSpecificError(_ expression: @autoclosure () throws -> some Any, _ error: E, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { XCTAssertThrowsError(try expression(), message(), file: file, line: line) { actualError in let message = "Expected \(error) got \(actualError)" diff --git a/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift b/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift index 5dbd1cd22..63686a756 100644 --- a/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift +++ b/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift @@ -13,4 +13,20 @@ final class PBXBuildFileTests: XCTestCase { ) XCTAssertEqual(pbxBuildFile.platformFilter, "platformFilter") } + + func test_platformCompilerFlagsIsSet() { + let expected = "flagValue" + let pbxBuildFile = PBXBuildFile( + settings: ["COMPILER_FLAGS": .string(expected)] + ) + XCTAssertEqual(pbxBuildFile.compilerFlags, expected) + } + + func test_platformAttributesIsSet() { + let expected = ["Public"] + let pbxBuildFile = PBXBuildFile( + settings: ["ATTRIBUTES": .array(expected)] + ) + XCTAssertEqual(pbxBuildFile.attributes, expected) + } } diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift new file mode 100644 index 000000000..5064f56e2 --- /dev/null +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift @@ -0,0 +1,34 @@ +import Foundation +import Testing +@testable import XcodeProj + +struct BuildFileSettingTests { + @Test func test_BuildSettings_encodes_to_JSON() async throws { + let expectedJSON = #"{"one":"one","two":["two","two"]}"# + + let settings: [String: BuildFileSetting] = [ + "one": .string("one"), + "two": .array(["two", "two"]), + ] + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + let result = try encoder.encode(settings) + + #expect(result == expectedJSON.data(using: .utf8)) + } + + @Test func test_buildSettings_decodes_from_JSON() async throws { + let json = #"{"one":"one","two":["two","two"]}"# + + let expectedSettings: [String: BuildFileSetting] = [ + "one": .string("one"), + "two": .array(["two", "two"]), + ] + + let result = try JSONDecoder().decode([String: BuildFileSetting].self, from: json.data(using: .utf8)!) + + #expect(result == expectedSettings) + } +} diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift new file mode 100644 index 000000000..5842a0a7c --- /dev/null +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift @@ -0,0 +1,49 @@ +import Foundation +import Testing +@testable import XcodeProj + +struct BuildSettingTests { + @Test func test_BuildSettings_encode_to_string() async throws { + #expect(BuildSetting.string("one").description == "one") + #expect(BuildSetting.array(["one", "two"]).description == "one two") + } + + @Test func test_buildSettings_decodes_from_JSON() async throws { + let json = #"{"one":"one","two":["two","two"]}"# + + let expectedSettings: BuildSettings = [ + "one": .string("one"), + "two": .array(["two", "two"]), + ] + + let result = try JSONDecoder().decode(BuildSettings.self, from: json.data(using: .utf8)!) + + #expect(result == expectedSettings) + } + + @Test func test_buildSettings_bool_conversion() async throws { + let settings: [BuildSetting] = [ + BuildSetting.string("YES"), + BuildSetting.string("NO"), + BuildSetting.string("tuist"), + BuildSetting.string("No"), + BuildSetting.string("yES"), + BuildSetting.array(["YES", "yES"]), + true, + false, + ] + + let expected: [Bool?] = [ + true, + false, + nil, + nil, + nil, + nil, + true, + false, + ] + + #expect(settings.map(\.boolValue) == expected) + } +} diff --git a/Tests/XcodeProjTests/Objects/Configuration/XCBuildConfigurationTests.swift b/Tests/XcodeProjTests/Objects/Configuration/XCBuildConfigurationTests.swift index c818562b8..e29ef2d54 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/XCBuildConfigurationTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/XCBuildConfigurationTests.swift @@ -28,7 +28,7 @@ final class XCBuildConfigurationTests: XCTestCase { subject.append(setting: "PRODUCT_NAME", value: "$(TARGET_NAME:c99extidentifier)") // Then - XCTAssertEqual(subject.buildSettings["PRODUCT_NAME"] as? String, "$(inherited) $(TARGET_NAME:c99extidentifier)") + XCTAssertEqual(subject.buildSettings["PRODUCT_NAME"], "$(inherited) $(TARGET_NAME:c99extidentifier)") } func test_append_when_theSettingExists() { @@ -41,7 +41,7 @@ final class XCBuildConfigurationTests: XCTestCase { subject.append(setting: "OTHER_LDFLAGS", value: "flag2") // Then - XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"] as? String, "flag1 flag2") + XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"], "flag1 flag2") } func test_append_when_duplicateSettingExists() { @@ -54,7 +54,7 @@ final class XCBuildConfigurationTests: XCTestCase { subject.append(setting: "OTHER_LDFLAGS", value: "flag1") // Then - XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"] as? String, "flag1") + XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"], "flag1") } func test_append_removesDuplicates_when_theSettingIsAnArray() { @@ -69,7 +69,7 @@ final class XCBuildConfigurationTests: XCTestCase { subject.append(setting: "OTHER_LDFLAGS", value: "flag1") // Then - XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"] as? [String], ["flag1", "flag2"]) + XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"], ["flag1", "flag2"]) } func test_append_when_theSettingExistsAsAnArray() { @@ -82,7 +82,7 @@ final class XCBuildConfigurationTests: XCTestCase { subject.append(setting: "OTHER_LDFLAGS", value: "flag3") // Then - XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"] as? [String], ["flag1", "flag2", "flag3"]) + XCTAssertEqual(subject.buildSettings["OTHER_LDFLAGS"], ["flag1", "flag2", "flag3"]) } private func testDictionary() -> [String: Any] { diff --git a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift index 9e4813b95..dfc935bed 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift @@ -1,276 +1,278 @@ +#if os(macOS) || (os(Linux) && compiler(>=6.1)) -import XCTest -@testable import XcodeProj + import XCTest + @testable import XcodeProj -class PBXOutputSettingsTests: XCTestCase { - // MARK: - PBXFileOrder - PBXBuldFile + class PBXOutputSettingsTests: XCTestCase { + // MARK: - PBXFileOrder - PBXBuldFile - func test_PBXFileOrder_PBXBuildFile_by_uuid_when_iosProject() { - let iosProject = iosProject() + func test_PBXFileOrder_PBXBuildFile_by_uuid_when_iosProject() throws { + let iosProject = try iosProject() - XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) - XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) - } - - func test_PBXFileOrder_PBXBuildFile_by_filename_when_iosProject() { - let iosProject = iosProject() - - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) - } - - func test_PBXFileOrder_PBXBuildFile_by_filename_when_fileSharedAcrossTargetsProject() { - let fileSharedAcrossTargetsProject = fileSharedAcrossTargetsProject() - - let sameNameByFilename = fileSharedAcrossTargetsProject.objectBuildFileSameName.sorted(by: PBXFileOrder.byFilename.sort) - XCTAssertLessThan(sameNameByFilename.first!.1.uuid, sameNameByFilename.last!.1.uuid) - } - - func test_PBXFileOrder_PBXBuildFile_by_filename_when_nil_name_and_path_when_iosProject() { - let iosProject = iosProject() - - iosProject.buildFileAssets.file?.name = nil - iosProject.buildFileMain.file?.name = nil - iosProject.buildFileAssets.file?.path = nil - iosProject.buildFileMain.file?.path = nil - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) - } - - func test_PBXFileOrder_PBXBuildFile_by_filename_when_no_file_when_iosProject() { - let iosProject = iosProject() - - let ref1 = iosProject.buildFileAssets.reference - let ref2 = iosProject.buildFileMain.reference - iosProject.buildFileAssets.file = nil - iosProject.buildFileMain.file = nil - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: (ref1, iosProject.buildFileAssets), rhs: (ref2, iosProject.buildFileMain))) - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: (ref2, iosProject.buildFileMain), rhs: (ref1, iosProject.buildFileAssets))) - } - - // MARK: - PBXFileOrder - PBXBuildPhaseFile + XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) + XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) + } - func test_PBXFileOrder_PBXBuildPhaseFile_by_uuid_when_iosProject() { - let iosProject = iosProject() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_iosProject() throws { + let iosProject = try iosProject() - XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildPhaseFileAssets, rhs: iosProject.objectBuildPhaseFileMain)) - XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildPhaseFileMain, rhs: iosProject.objectBuildPhaseFileAssets)) - } - - func test_PBXFileOrder_PBXBuildPhaseFile_by_filename_when_iosProject() { - let iosProject = iosProject() - - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildPhaseFileAssets, rhs: iosProject.objectBuildPhaseFileMain)) - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildPhaseFileMain, rhs: iosProject.objectBuildPhaseFileAssets)) - } - - // MARK: - PBXFileOrder - PBXFileReference - - func test_PBXFileOrder_PBXFileReference_by_uuid_when_iosProject() { - let iosProject = iosProject() - - XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectFileReferenceAssets, rhs: iosProject.objectFileReferenceCoreData)) - XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectFileReferenceCoreData, rhs: iosProject.objectFileReferenceAssets)) - } + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) + } - func test_PBXFileOrder_PBXFileReference_by_filename_when_iosProject() { - let iosProject = iosProject() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_fileSharedAcrossTargetsProject() throws { + let fileSharedAcrossTargetsProject = try fileSharedAcrossTargetsProject() - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceAssets, rhs: iosProject.objectFileReferenceCoreData)) - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceCoreData, rhs: iosProject.objectFileReferenceAssets)) - } + let sameNameByFilename = fileSharedAcrossTargetsProject.objectBuildFileSameName.sorted(by: PBXFileOrder.byFilename.sort) + XCTAssertLessThan(sameNameByFilename.first!.1.uuid, sameNameByFilename.last!.1.uuid) + } - func test_PBXFileOrder_PBXFileReference_by_filename_when_fileSharedAcrossTargetsProject() { - let fileSharedAcrossTargetsProject = fileSharedAcrossTargetsProject() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_nil_name_and_path_when_iosProject() throws { + let iosProject = try iosProject() - let sameNameByFilename = fileSharedAcrossTargetsProject.objectFileReferenceSameName.sorted(by: PBXFileOrder.byFilename.sort) - XCTAssertLessThan(sameNameByFilename.first!.1.uuid, sameNameByFilename.last!.1.uuid) - } + iosProject.buildFileAssets.file?.name = nil + iosProject.buildFileMain.file?.name = nil + iosProject.buildFileAssets.file?.path = nil + iosProject.buildFileMain.file?.path = nil + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) + } - func test_PBXFileOrder_PBXFileReference_by_filename_when_nil_name_and_path_when_iosProject() { - let iosProject = iosProject() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_no_file_when_iosProject() throws { + let iosProject = try iosProject() - iosProject.fileReferenceAssets.name = nil - iosProject.fileReferenceCoreData.name = nil - iosProject.fileReferenceAssets.path = nil - iosProject.fileReferenceCoreData.path = nil - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceAssets, rhs: iosProject.objectFileReferenceCoreData)) - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceCoreData, rhs: iosProject.objectFileReferenceAssets)) - } + let ref1 = iosProject.buildFileAssets.reference + let ref2 = iosProject.buildFileMain.reference + iosProject.buildFileAssets.file = nil + iosProject.buildFileMain.file = nil + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: (ref1, iosProject.buildFileAssets), rhs: (ref2, iosProject.buildFileMain))) + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: (ref2, iosProject.buildFileMain), rhs: (ref1, iosProject.buildFileAssets))) + } - // MARK: - PBXFileOrder - Other + // MARK: - PBXFileOrder - PBXBuildPhaseFile + + func test_PBXFileOrder_PBXBuildPhaseFile_by_uuid_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildPhaseFileAssets, rhs: iosProject.objectBuildPhaseFileMain)) + XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildPhaseFileMain, rhs: iosProject.objectBuildPhaseFileAssets)) + } + + func test_PBXFileOrder_PBXBuildPhaseFile_by_filename_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildPhaseFileAssets, rhs: iosProject.objectBuildPhaseFileMain)) + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildPhaseFileMain, rhs: iosProject.objectBuildPhaseFileAssets)) + } + + // MARK: - PBXFileOrder - PBXFileReference + + func test_PBXFileOrder_PBXFileReference_by_uuid_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectFileReferenceAssets, rhs: iosProject.objectFileReferenceCoreData)) + XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectFileReferenceCoreData, rhs: iosProject.objectFileReferenceAssets)) + } + + func test_PBXFileOrder_PBXFileReference_by_filename_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceAssets, rhs: iosProject.objectFileReferenceCoreData)) + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceCoreData, rhs: iosProject.objectFileReferenceAssets)) + } + + func test_PBXFileOrder_PBXFileReference_by_filename_when_fileSharedAcrossTargetsProject() throws { + let fileSharedAcrossTargetsProject = try fileSharedAcrossTargetsProject() + + let sameNameByFilename = fileSharedAcrossTargetsProject.objectFileReferenceSameName.sorted(by: PBXFileOrder.byFilename.sort) + XCTAssertLessThan(sameNameByFilename.first!.1.uuid, sameNameByFilename.last!.1.uuid) + } + + func test_PBXFileOrder_PBXFileReference_by_filename_when_nil_name_and_path_when_iosProject() throws { + let iosProject = try iosProject() + + iosProject.fileReferenceAssets.name = nil + iosProject.fileReferenceCoreData.name = nil + iosProject.fileReferenceAssets.path = nil + iosProject.fileReferenceCoreData.path = nil + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceAssets, rhs: iosProject.objectFileReferenceCoreData)) + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectFileReferenceCoreData, rhs: iosProject.objectFileReferenceAssets)) + } - func test_PBXFileOrder_Other_by_uuid_when_iosProject() { - let iosProject = iosProject() + // MARK: - PBXFileOrder - Other + + func test_PBXFileOrder_Other_by_uuid_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectGroupFrameworks, rhs: iosProject.objectGroupProducts)) + XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectGroupProducts, rhs: iosProject.objectGroupFrameworks)) + } + + func test_PBXFileOrder_Other_by_filename_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectGroupFrameworks, rhs: iosProject.objectGroupProducts)) + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectGroupProducts, rhs: iosProject.objectGroupFrameworks)) + } - XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectGroupFrameworks, rhs: iosProject.objectGroupProducts)) - XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectGroupProducts, rhs: iosProject.objectGroupFrameworks)) - } - - func test_PBXFileOrder_Other_by_filename_when_iosProject() { - let iosProject = iosProject() + // MARK: - PBXNavigatorFileOrder + + func test_PBXNavigatorFileOrder_unsorted_when_iosProject() { + XCTAssertNil(PBXNavigatorFileOrder.unsorted.sort) + } - XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectGroupFrameworks, rhs: iosProject.objectGroupProducts)) - XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectGroupProducts, rhs: iosProject.objectGroupFrameworks)) - } - - // MARK: - PBXNavigatorFileOrder - - func test_PBXNavigatorFileOrder_unsorted_when_iosProject() { - XCTAssertNil(PBXNavigatorFileOrder.unsorted.sort) - } - - func test_PBXNavigatorFileOrder_by_filename_when_iosProject() { - let iosProject = iosProject() - - let sort: (PBXFileElement, PBXFileElement) -> Bool = PBXNavigatorFileOrder.byFilename.sort! - let sorted = iosProject.navigatorFileGroup.children.sorted(by: sort).map { $0.fileName()! } - XCTAssertEqual([ - "AppDelegate.swift", - "Assets.xcassets", - "GroupWithoutFolder", - "Info.plist", - "LaunchScreen.storyboard", - "Main.storyboard", - "Model.xcdatamodeld", - "Private.h", - "Protected.h", - "Public.h", - "ViewController.swift", - ], sorted) - } - - func test_PBXNavigatorFileOrder_by_filename_groups_first_when_iosProject() { - let iosProject = iosProject() - - let sort: (PBXFileElement, PBXFileElement) -> Bool = PBXNavigatorFileOrder.byFilenameGroupsFirst.sort! - let sorted = iosProject.navigatorFileGroup.children.sorted(by: sort).map { $0.fileName()! } - XCTAssertEqual([ - "GroupWithoutFolder", - "AppDelegate.swift", - "Assets.xcassets", - "Info.plist", - "LaunchScreen.storyboard", - "Main.storyboard", - "Model.xcdatamodeld", - "Private.h", - "Protected.h", - "Public.h", - "ViewController.swift", - ], sorted) - } - - // MARK: - PBXBuildPhaseFileOrder - - func test_PBXBuildPhaseFileOrder_unsorted() { - XCTAssertNil(PBXBuildPhaseFileOrder.unsorted.sort) - } - - func test_PBXBuildPhaseFileOrder_by_filename_when_iosProject() { - let iosProject = iosProject() - - XCTAssertTrue(PBXBuildPhaseFileOrder.byFilename.sort!(iosProject.buildFileAssets, iosProject.buildFileMain)) - XCTAssertFalse(PBXBuildPhaseFileOrder.byFilename.sort!(iosProject.buildFileMain, iosProject.buildFileAssets)) - } - - // MARK: - Private - - struct iOSProject { - var proj: PBXProj! - var buildFileAssets: PBXBuildFile! - var buildFileMain: PBXBuildFile! - - var objectBuildFileAssets: (PBXObjectReference, PBXBuildFile)! - var objectBuildFileMain: (PBXObjectReference, PBXBuildFile)! - - var objectBuildPhaseFileAssets: (PBXObjectReference, PBXBuildPhaseFile)! - var objectBuildPhaseFileMain: (PBXObjectReference, PBXBuildPhaseFile)! - - var fileReferenceAssets: PBXFileReference! - var fileReferenceCoreData: PBXFileReference! - - var objectFileReferenceAssets: (PBXObjectReference, PBXFileReference)! - var objectFileReferenceCoreData: (PBXObjectReference, PBXFileReference)! - - var groupFrameworks: PBXGroup! - var groupProducts: PBXGroup! - - var objectGroupFrameworks: (PBXObjectReference, PBXGroup)! - var objectGroupProducts: (PBXObjectReference, PBXGroup)! - - var navigatorFileGroup: PBXGroup! - } - - private func iosProject() -> iOSProject { - let data = try! iosProjectData() - let proj = try! PBXProj(data: data) - - let buildFileAssets = proj.buildFiles.first { $0.file?.fileName() == "Assets.xcassets" }! - let buildFileMain = proj.buildFiles.first { $0.file?.fileName() == "Main.storyboard" }! - - let objectBuildFileAssets = (buildFileAssets.reference, buildFileAssets) - let objectBuildFileMain = (buildFileMain.reference, buildFileMain) - - let objectBuildPhaseFileAssets = proj.objects.buildPhaseFile.first { $0.value.buildFile.file?.fileName() == "Assets.xcassets" }! - let objectBuildPhaseFileMain = proj.objects.buildPhaseFile.first { $0.value.buildFile.file?.fileName() == "Main.storyboard" }! - - let fileReferenceAssets = proj.fileReferences.first { $0.fileName() == "Assets.xcassets" }! - let fileReferenceCoreData = proj.fileReferences.first { $0.fileName() == "CoreData.framework" }! - - let objectFileReferenceAssets = (buildFileAssets.reference, fileReferenceAssets) - let objectFileReferenceCoreData = (buildFileMain.reference, fileReferenceCoreData) - - let groupFrameworks = proj.groups.first { $0.fileName() == "Frameworks" }! - let groupProducts = proj.groups.first { $0.fileName() == "Products" }! - - let objectGroupFrameworks = (groupFrameworks.reference, groupFrameworks) - let objectGroupProducts = (groupProducts.reference, groupProducts) - - let navigatorFileGroup = proj.groups.first { $0.fileName() == "iOS" }! - - return iOSProject( - proj: proj, - buildFileAssets: buildFileAssets, - buildFileMain: buildFileMain, - objectBuildFileAssets: objectBuildFileAssets, - objectBuildFileMain: objectBuildFileMain, - objectBuildPhaseFileAssets: objectBuildPhaseFileAssets, - objectBuildPhaseFileMain: objectBuildPhaseFileMain, - fileReferenceAssets: fileReferenceAssets, - fileReferenceCoreData: fileReferenceCoreData, - objectFileReferenceAssets: objectFileReferenceAssets, - objectFileReferenceCoreData: objectFileReferenceCoreData, - groupFrameworks: groupFrameworks, - groupProducts: groupProducts, - objectGroupFrameworks: objectGroupFrameworks, - objectGroupProducts: objectGroupProducts, - navigatorFileGroup: navigatorFileGroup - ) - } - - struct FileSharedAcrossTargetsProject { - var proj: PBXProj! - var buildFileSameName: [PBXBuildFile]! - var objectBuildFileSameName: [(PBXObjectReference, PBXBuildFile)]! - var fileReferenceSameName: [PBXFileReference]! - var objectFileReferenceSameName: [(PBXObjectReference, PBXFileReference)]! - } + func test_PBXNavigatorFileOrder_by_filename_when_iosProject() throws { + let iosProject = try iosProject() - func fileSharedAcrossTargetsProject() -> FileSharedAcrossTargetsProject { - let dic = try! fileSharedAcrossTargetsData() - let proj = try! PBXProj(data: dic) - - let buildFileSameName = proj.buildFiles.filter { $0.file?.fileName() == "SameName.h" } - let objectBuildFileSameName = proj.buildFiles.map { ($0.reference, $0) } - let fileReferenceSameName = proj.fileReferences.filter { $0.fileName() == "FileSharedAcrossTargetsTests.swift" } - let objectFileReferenceSameName = fileReferenceSameName.map { ($0.reference, $0) } - - return FileSharedAcrossTargetsProject( - proj: proj, - buildFileSameName: buildFileSameName, - objectBuildFileSameName: objectBuildFileSameName, - fileReferenceSameName: fileReferenceSameName, - objectFileReferenceSameName: objectFileReferenceSameName - ) + let sort: (PBXFileElement, PBXFileElement) -> Bool = PBXNavigatorFileOrder.byFilename.sort! + let sorted = iosProject.navigatorFileGroup.children.sorted(by: sort).map { $0.fileName()! } + XCTAssertEqual([ + "AppDelegate.swift", + "Assets.xcassets", + "GroupWithoutFolder", + "Info.plist", + "LaunchScreen.storyboard", + "Main.storyboard", + "Model.xcdatamodeld", + "Private.h", + "Protected.h", + "Public.h", + "ViewController.swift", + ], sorted) + } + + func test_PBXNavigatorFileOrder_by_filename_groups_first_when_iosProject() throws { + let iosProject = try iosProject() + + let sort: (PBXFileElement, PBXFileElement) -> Bool = PBXNavigatorFileOrder.byFilenameGroupsFirst.sort! + let sorted = iosProject.navigatorFileGroup.children.sorted(by: sort).map { $0.fileName()! } + XCTAssertEqual([ + "GroupWithoutFolder", + "AppDelegate.swift", + "Assets.xcassets", + "Info.plist", + "LaunchScreen.storyboard", + "Main.storyboard", + "Model.xcdatamodeld", + "Private.h", + "Protected.h", + "Public.h", + "ViewController.swift", + ], sorted) + } + + // MARK: - PBXBuildPhaseFileOrder + + func test_PBXBuildPhaseFileOrder_unsorted() { + XCTAssertNil(PBXBuildPhaseFileOrder.unsorted.sort) + } + + func test_PBXBuildPhaseFileOrder_by_filename_when_iosProject() throws { + let iosProject = try iosProject() + + XCTAssertTrue(PBXBuildPhaseFileOrder.byFilename.sort!(iosProject.buildFileAssets, iosProject.buildFileMain)) + XCTAssertFalse(PBXBuildPhaseFileOrder.byFilename.sort!(iosProject.buildFileMain, iosProject.buildFileAssets)) + } + + // MARK: - Private + + struct iOSProject { + var proj: PBXProj! + var buildFileAssets: PBXBuildFile! + var buildFileMain: PBXBuildFile! + + var objectBuildFileAssets: (PBXObjectReference, PBXBuildFile)! + var objectBuildFileMain: (PBXObjectReference, PBXBuildFile)! + + var objectBuildPhaseFileAssets: (PBXObjectReference, PBXBuildPhaseFile)! + var objectBuildPhaseFileMain: (PBXObjectReference, PBXBuildPhaseFile)! + + var fileReferenceAssets: PBXFileReference! + var fileReferenceCoreData: PBXFileReference! + + var objectFileReferenceAssets: (PBXObjectReference, PBXFileReference)! + var objectFileReferenceCoreData: (PBXObjectReference, PBXFileReference)! + + var groupFrameworks: PBXGroup! + var groupProducts: PBXGroup! + + var objectGroupFrameworks: (PBXObjectReference, PBXGroup)! + var objectGroupProducts: (PBXObjectReference, PBXGroup)! + + var navigatorFileGroup: PBXGroup! + } + + private func iosProject() throws -> iOSProject { + let data = try XCTUnwrap(iosProjectData()) + let proj = try XCTUnwrap(PBXProj(data: data)) + + let buildFileAssets = proj.buildFiles.first { $0.file?.fileName() == "Assets.xcassets" }! + let buildFileMain = proj.buildFiles.first { $0.file?.fileName() == "Main.storyboard" }! + + let objectBuildFileAssets = (buildFileAssets.reference, buildFileAssets) + let objectBuildFileMain = (buildFileMain.reference, buildFileMain) + + let objectBuildPhaseFileAssets = proj.objects.buildPhaseFile.first { $0.value.buildFile.file?.fileName() == "Assets.xcassets" }! + let objectBuildPhaseFileMain = proj.objects.buildPhaseFile.first { $0.value.buildFile.file?.fileName() == "Main.storyboard" }! + + let fileReferenceAssets = proj.fileReferences.first { $0.fileName() == "Assets.xcassets" }! + let fileReferenceCoreData = proj.fileReferences.first { $0.fileName() == "CoreData.framework" }! + + let objectFileReferenceAssets = (buildFileAssets.reference, fileReferenceAssets) + let objectFileReferenceCoreData = (buildFileMain.reference, fileReferenceCoreData) + + let groupFrameworks = proj.groups.first { $0.fileName() == "Frameworks" }! + let groupProducts = proj.groups.first { $0.fileName() == "Products" }! + + let objectGroupFrameworks = (groupFrameworks.reference, groupFrameworks) + let objectGroupProducts = (groupProducts.reference, groupProducts) + + let navigatorFileGroup = proj.groups.first { $0.fileName() == "iOS" }! + + return iOSProject( + proj: proj, + buildFileAssets: buildFileAssets, + buildFileMain: buildFileMain, + objectBuildFileAssets: objectBuildFileAssets, + objectBuildFileMain: objectBuildFileMain, + objectBuildPhaseFileAssets: objectBuildPhaseFileAssets, + objectBuildPhaseFileMain: objectBuildPhaseFileMain, + fileReferenceAssets: fileReferenceAssets, + fileReferenceCoreData: fileReferenceCoreData, + objectFileReferenceAssets: objectFileReferenceAssets, + objectFileReferenceCoreData: objectFileReferenceCoreData, + groupFrameworks: groupFrameworks, + groupProducts: groupProducts, + objectGroupFrameworks: objectGroupFrameworks, + objectGroupProducts: objectGroupProducts, + navigatorFileGroup: navigatorFileGroup + ) + } + + struct FileSharedAcrossTargetsProject { + var proj: PBXProj! + var buildFileSameName: [PBXBuildFile]! + var objectBuildFileSameName: [(PBXObjectReference, PBXBuildFile)]! + var fileReferenceSameName: [PBXFileReference]! + var objectFileReferenceSameName: [(PBXObjectReference, PBXFileReference)]! + } + + func fileSharedAcrossTargetsProject() throws -> FileSharedAcrossTargetsProject { + let dic = try XCTUnwrap(fileSharedAcrossTargetsData()) + let proj = try XCTUnwrap(PBXProj(data: dic)) + + let buildFileSameName = proj.buildFiles.filter { $0.file?.fileName() == "SameName.h" } + let objectBuildFileSameName = proj.buildFiles.map { ($0.reference, $0) } + let fileReferenceSameName = proj.fileReferences.filter { $0.fileName() == "FileSharedAcrossTargetsTests.swift" } + let objectFileReferenceSameName = fileReferenceSameName.map { ($0.reference, $0) } + + return FileSharedAcrossTargetsProject( + proj: proj, + buildFileSameName: buildFileSameName, + objectBuildFileSameName: objectBuildFileSameName, + fileReferenceSameName: fileReferenceSameName, + objectFileReferenceSameName: objectFileReferenceSameName + ) + } } -} +#endif diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProj+Fixtures.swift b/Tests/XcodeProjTests/Objects/Project/PBXProj+Fixtures.swift index ade46a2a2..8488d0fb7 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProj+Fixtures.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProj+Fixtures.swift @@ -5,7 +5,7 @@ extension PBXProj { static func fixture(rootObject: PBXProject? = PBXProject.fixture(), objectVersion: UInt = Xcode.LastKnown.objectVersion, archiveVersion: UInt = Xcode.LastKnown.archiveVersion, - classes: [String: Any] = [:], + classes: [String: [String]] = [:], objects: [PBXObject] = []) -> PBXProj { PBXProj(rootObject: rootObject, diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index 2f0e49c81..4fb2b6ec0 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -1,618 +1,620 @@ +#if os(macOS) || (os(Linux) && compiler(>=6.1)) + import Foundation + import XCTest + @testable import XcodeProj -import Foundation -import XCTest -@testable import XcodeProj + class PBXProjEncoderTests: XCTestCase { + var proj: PBXProj! -class PBXProjEncoderTests: XCTestCase { - var proj: PBXProj! + // MARK: - Header - // MARK: - Header + func test_writeHeaders_when_iOSProject() throws { + try loadiOSProject() - func test_writeHeaders_when_iOSProject() throws { - try loadiOSProject() - - let lines = lines(fromFile: encodeProject()) - XCTAssertEqual(583, lines.count) - XCTAssertEqual("// !$*UTF8*$!", lines[0]) - } + let lines = lines(fromFile: encodeProject()) + XCTAssertEqual(583, lines.count) + XCTAssertEqual("// !$*UTF8*$!", lines[0]) + } - // MARK: - Internal file lists - - func test_buildFiles_in_default_uuid_order_when_iOSProject() throws { - try loadiOSProject() - - let lines = lines(fromFile: encodeProject()) - var line = lines.validate(line: "/* Begin PBXBuildFile section */") - line = lines.validate(lineContaining: "04D5C09F1F153824008A2F98 /* CoreData.framework in Frameworks */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A31F153924008A2F98 /* Public.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A51F153924008A2F98 /* Private.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C2B1EAA3484007A9026 /* iOSTests.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "42AA1A1A22AAF48100428760 /* MyLocalPackage in Frameworks */", onLineAfter: line) - line = lines.validate(lineContaining: "42AA1A1C22AAF48100428760 /* RxSwift in Frameworks */", onLineAfter: line) - lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) - } + // MARK: - Internal file lists + + func test_buildFiles_in_default_uuid_order_when_iOSProject() throws { + try loadiOSProject() + + let lines = lines(fromFile: encodeProject()) + var line = lines.validate(line: "/* Begin PBXBuildFile section */") + line = lines.validate(lineContaining: "04D5C09F1F153824008A2F98 /* CoreData.framework in Frameworks */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A31F153924008A2F98 /* Public.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A51F153924008A2F98 /* Private.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C2B1EAA3484007A9026 /* iOSTests.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "42AA1A1A22AAF48100428760 /* MyLocalPackage in Frameworks */", onLineAfter: line) + line = lines.validate(lineContaining: "42AA1A1C22AAF48100428760 /* RxSwift in Frameworks */", onLineAfter: line) + lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) + } - func test_buildFiles_in_filename_order_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projFileListOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - var line = lines.validate(line: "/* Begin PBXBuildFile section */") - line = lines.validate(lineContaining: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C09F1F153824008A2F98 /* CoreData.framework in Frameworks */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */", onLineAfter: line) - line = lines.validate(lineContaining: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A51F153924008A2F98 /* Private.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A31F153924008A2F98 /* Public.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C2B1EAA3484007A9026 /* iOSTests.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "42AA1A1A22AAF48100428760 /* MyLocalPackage in Frameworks */", onLineAfter: line) - line = lines.validate(lineContaining: "42AA1A1C22AAF48100428760 /* RxSwift in Frameworks */", onLineAfter: line) - lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) - } + func test_buildFiles_in_filename_order_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projFileListOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + var line = lines.validate(line: "/* Begin PBXBuildFile section */") + line = lines.validate(lineContaining: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C09F1F153824008A2F98 /* CoreData.framework in Frameworks */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */", onLineAfter: line) + line = lines.validate(lineContaining: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A51F153924008A2F98 /* Private.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A31F153924008A2F98 /* Public.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C2B1EAA3484007A9026 /* iOSTests.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "42AA1A1A22AAF48100428760 /* MyLocalPackage in Frameworks */", onLineAfter: line) + line = lines.validate(lineContaining: "42AA1A1C22AAF48100428760 /* RxSwift in Frameworks */", onLineAfter: line) + lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) + } - func test_buildFiles_in_filename_order_when_fileSharedAcrossTargetsProject() throws { - try loadFileSharedAcrossTargetsProject() + func test_buildFiles_in_filename_order_when_fileSharedAcrossTargetsProject() throws { + try loadFileSharedAcrossTargetsProject() - let settings = PBXOutputSettings(projFileListOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - var line = lines.validate(line: "/* Begin PBXBuildFile section */") + let settings = PBXOutputSettings(projFileListOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + var line = lines.validate(line: "/* Begin PBXBuildFile section */") - line = lines.validate(lineContaining: "6C103C032A49CC5400D7EFE4 /* FileSharedAcrossTargets.framework in Frameworks */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C092A49CC5400D7EFE4 /* FileSharedAcrossTargets.h in Headers */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C082A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.swift in Sources */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C132A49CC7300D7EFE4 /* SharedHeader.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C032A49CC5400D7EFE4 /* FileSharedAcrossTargets.framework in Frameworks */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C092A49CC5400D7EFE4 /* FileSharedAcrossTargets.h in Headers */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C082A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.swift in Sources */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C132A49CC7300D7EFE4 /* SharedHeader.h in Headers */", onLineAfter: line) - lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) - } + lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) + } - func test_file_references_in_default_uuid_order_when_iOSProject() throws { - try loadiOSProject() - - let lines = lines(fromFile: encodeProject()) - var line = lines.validate(line: "/* Begin PBXFileReference section */") - line = lines.validate(lineContaining: "04D5C09E1F153824008A2F98 /* CoreData.framework */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A01F153915008A2F98 /* Public.h */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A11F15391B008A2F98 /* Protected.h */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A21F153921008A2F98 /* Private.h */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C121EAA3484007A9026 /* iOS.app */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C151EAA3484007A9026 /* AppDelegate.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C171EAA3484007A9026 /* ViewController.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1A1EAA3484007A9026 /* Base */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1C1EAA3484007A9026 /* Assets.xcassets */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1F1EAA3484007A9026 /* Base */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C211EAA3484007A9026 /* Info.plist */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C261EAA3484007A9026 /* iOSTests.xctest */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C2A1EAA3484007A9026 /* iOSTests.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C2C1EAA3484007A9026 /* Info.plist */", onLineAfter: line) - line = lines.validate(lineContaining: "23C1E0AF23657FB500B8D1EF /* iOS.xctestplan */", onLineAfter: line) - line = lines.validate(lineContaining: "3CD1EADC205763E400DAEECB /* Model.xcdatamodel */", onLineAfter: line) - line = lines.validate(lineContaining: "42AA1A1822AAF41000428760 /* MyLocalPackage */", onLineAfter: line) - - lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) - } + func test_file_references_in_default_uuid_order_when_iOSProject() throws { + try loadiOSProject() + + let lines = lines(fromFile: encodeProject()) + var line = lines.validate(line: "/* Begin PBXFileReference section */") + line = lines.validate(lineContaining: "04D5C09E1F153824008A2F98 /* CoreData.framework */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A01F153915008A2F98 /* Public.h */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A11F15391B008A2F98 /* Protected.h */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A21F153921008A2F98 /* Private.h */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C121EAA3484007A9026 /* iOS.app */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C151EAA3484007A9026 /* AppDelegate.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C171EAA3484007A9026 /* ViewController.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1A1EAA3484007A9026 /* Base */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1C1EAA3484007A9026 /* Assets.xcassets */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1F1EAA3484007A9026 /* Base */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C211EAA3484007A9026 /* Info.plist */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C261EAA3484007A9026 /* iOSTests.xctest */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C2A1EAA3484007A9026 /* iOSTests.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C2C1EAA3484007A9026 /* Info.plist */", onLineAfter: line) + line = lines.validate(lineContaining: "23C1E0AF23657FB500B8D1EF /* iOS.xctestplan */", onLineAfter: line) + line = lines.validate(lineContaining: "3CD1EADC205763E400DAEECB /* Model.xcdatamodel */", onLineAfter: line) + line = lines.validate(lineContaining: "42AA1A1822AAF41000428760 /* MyLocalPackage */", onLineAfter: line) + + lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) + } - func test_file_references_in_default_uuid_order_when_fileSharedAcrossTargetsProject() throws { - try loadFileSharedAcrossTargetsProject() + func test_file_references_in_default_uuid_order_when_fileSharedAcrossTargetsProject() throws { + try loadFileSharedAcrossTargetsProject() - let lines = lines(fromFile: encodeProject()) - var line = lines.validate(line: "/* Begin PBXFileReference section */") - line = lines.validate(lineContaining: "6C103BFA2A49CC5300D7EFE4 /* FileSharedAcrossTargets.framework */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103BFD2A49CC5300D7EFE4 /* FileSharedAcrossTargets.h */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C022A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.xctest */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C072A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C122A49CC7300D7EFE4 /* SharedHeader.h */", onLineAfter: line) - line = lines.validate(lineContaining: "6CB965012A49DC1F009186C6 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) + let lines = lines(fromFile: encodeProject()) + var line = lines.validate(line: "/* Begin PBXFileReference section */") + line = lines.validate(lineContaining: "6C103BFA2A49CC5300D7EFE4 /* FileSharedAcrossTargets.framework */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103BFD2A49CC5300D7EFE4 /* FileSharedAcrossTargets.h */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C022A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.xctest */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C072A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C122A49CC7300D7EFE4 /* SharedHeader.h */", onLineAfter: line) + line = lines.validate(lineContaining: "6CB965012A49DC1F009186C6 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) - lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) - } + lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) + } - func test_file_references_in_filename_order_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projFileListOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - var line = lines.validate(line: "/* Begin PBXFileReference section */") - line = lines.validate(lineContaining: "23766C151EAA3484007A9026 /* AppDelegate.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1C1EAA3484007A9026 /* Assets.xcassets */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1A1EAA3484007A9026 /* Base */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C1F1EAA3484007A9026 /* Base */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C09E1F153824008A2F98 /* CoreData.framework */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C211EAA3484007A9026 /* Info.plist */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C2C1EAA3484007A9026 /* Info.plist */", onLineAfter: line) - line = lines.validate(lineContaining: "3CD1EADC205763E400DAEECB /* Model.xcdatamodel */", onLineAfter: line) - line = lines.validate(lineContaining: "42AA1A1822AAF41000428760 /* MyLocalPackage */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A21F153921008A2F98 /* Private.h */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A11F15391B008A2F98 /* Protected.h */", onLineAfter: line) - line = lines.validate(lineContaining: "04D5C0A01F153915008A2F98 /* Public.h */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C171EAA3484007A9026 /* ViewController.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C121EAA3484007A9026 /* iOS.app */", onLineAfter: line) - line = lines.validate(lineContaining: "23C1E0AF23657FB500B8D1EF /* iOS.xctestplan */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C2A1EAA3484007A9026 /* iOSTests.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "23766C261EAA3484007A9026 /* iOSTests.xctest */", onLineAfter: line) - lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) - } + func test_file_references_in_filename_order_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projFileListOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + var line = lines.validate(line: "/* Begin PBXFileReference section */") + line = lines.validate(lineContaining: "23766C151EAA3484007A9026 /* AppDelegate.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1C1EAA3484007A9026 /* Assets.xcassets */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1A1EAA3484007A9026 /* Base */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C1F1EAA3484007A9026 /* Base */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C09E1F153824008A2F98 /* CoreData.framework */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C211EAA3484007A9026 /* Info.plist */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C2C1EAA3484007A9026 /* Info.plist */", onLineAfter: line) + line = lines.validate(lineContaining: "3CD1EADC205763E400DAEECB /* Model.xcdatamodel */", onLineAfter: line) + line = lines.validate(lineContaining: "42AA1A1822AAF41000428760 /* MyLocalPackage */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A21F153921008A2F98 /* Private.h */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A11F15391B008A2F98 /* Protected.h */", onLineAfter: line) + line = lines.validate(lineContaining: "04D5C0A01F153915008A2F98 /* Public.h */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C171EAA3484007A9026 /* ViewController.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C121EAA3484007A9026 /* iOS.app */", onLineAfter: line) + line = lines.validate(lineContaining: "23C1E0AF23657FB500B8D1EF /* iOS.xctestplan */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C2A1EAA3484007A9026 /* iOSTests.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "23766C261EAA3484007A9026 /* iOSTests.xctest */", onLineAfter: line) + lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) + } - func test_file_references_in_filename_order_when_fileSharedAcrossTargetsProject() throws { - try loadFileSharedAcrossTargetsProject() - - let settings = PBXOutputSettings(projFileListOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - var line = lines.validate(line: "/* Begin PBXFileReference section */") - line = lines.validate(lineContaining: "6C103BFA2A49CC5300D7EFE4 /* FileSharedAcrossTargets.framework */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103BFD2A49CC5300D7EFE4 /* FileSharedAcrossTargets.h */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C072A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "6CB965012A49DC1F009186C6 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C022A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.xctest */", onLineAfter: line) - line = lines.validate(lineContaining: "6C103C122A49CC7300D7EFE4 /* SharedHeader.h */", onLineAfter: line) - lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) - } + func test_file_references_in_filename_order_when_fileSharedAcrossTargetsProject() throws { + try loadFileSharedAcrossTargetsProject() + + let settings = PBXOutputSettings(projFileListOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + var line = lines.validate(line: "/* Begin PBXFileReference section */") + line = lines.validate(lineContaining: "6C103BFA2A49CC5300D7EFE4 /* FileSharedAcrossTargets.framework */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103BFD2A49CC5300D7EFE4 /* FileSharedAcrossTargets.h */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C072A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "6CB965012A49DC1F009186C6 /* FileSharedAcrossTargetsTests.swift */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C022A49CC5400D7EFE4 /* FileSharedAcrossTargetsTests.xctest */", onLineAfter: line) + line = lines.validate(lineContaining: "6C103C122A49CC7300D7EFE4 /* SharedHeader.h */", onLineAfter: line) + lines.validate(line: "/* End PBXFileReference section */", onLineAfter: line) + } - // MARK: - Navigator - - func test_navigator_groups_in_default_order_when_iOSProject() throws { - try loadiOSProject() - - let lines = lines(fromFile: encodeProject()) - - let beginGroup = lines.findLine("/* Begin PBXGroup section */") - - // Root - let rootGroup = lines.findLine("23766C091EAA3484007A9026 = {", after: beginGroup) - let rootChildrenStart = lines.findLine("children = (", after: rootGroup) - let rootChildrenEnd = lines.findLine(");", after: rootChildrenStart) - - lines.validate(line: "23766C141EAA3484007A9026 /* iOS */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) - lines.validate(line: "23766C291EAA3484007A9026 /* iOSTests */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) - lines.validate(line: "23766C131EAA3484007A9026 /* Products */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) - lines.validate(line: "04D5C09D1F153824008A2F98 /* Frameworks */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) - - // iOS - let iosGroup = lines.findLine("23766C141EAA3484007A9026 /* iOS */ = {", after: beginGroup) - let iosChildrenStart = lines.findLine("children = (", after: iosGroup) - let iosChildrenEnd = lines.findLine(");", after: iosChildrenStart) - - lines.validate(line: "3CD1EADB205763E400DAEECB /* Model.xcdatamodeld */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "3CD1EAD92057638200DAEECB /* GroupWithoutFolder */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "23766C151EAA3484007A9026 /* AppDelegate.swift */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "23766C171EAA3484007A9026 /* ViewController.swift */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "23766C191EAA3484007A9026 /* Main.storyboard */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "23766C1C1EAA3484007A9026 /* Assets.xcassets */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "23766C1E1EAA3484007A9026 /* LaunchScreen.storyboard */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "23766C211EAA3484007A9026 /* Info.plist */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "04D5C0A01F153915008A2F98 /* Public.h */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "04D5C0A11F15391B008A2F98 /* Protected.h */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - lines.validate(line: "04D5C0A21F153921008A2F98 /* Private.h */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) - - // iOS Tests - let iosTestsGroup = lines.findLine("23766C291EAA3484007A9026 /* iOSTests */ = {", after: beginGroup) - let iosTestsChildrenStart = lines.findLine("children = (", after: iosTestsGroup) - let iosTestsChildrenEnd = lines.findLine(");", after: iosTestsChildrenStart) - - lines.validate(line: "23766C2A1EAA3484007A9026 /* iOSTests.swift */,", betweenLine: iosTestsChildrenStart, andLine: iosTestsChildrenEnd) - lines.validate(line: "23766C2C1EAA3484007A9026 /* Info.plist */,", betweenLine: iosTestsChildrenStart, andLine: iosTestsChildrenEnd) - } + // MARK: - Navigator + + func test_navigator_groups_in_default_order_when_iOSProject() throws { + try loadiOSProject() + + let lines = lines(fromFile: encodeProject()) + + let beginGroup = lines.findLine("/* Begin PBXGroup section */") + + // Root + let rootGroup = lines.findLine("23766C091EAA3484007A9026 = {", after: beginGroup) + let rootChildrenStart = lines.findLine("children = (", after: rootGroup) + let rootChildrenEnd = lines.findLine(");", after: rootChildrenStart) + + lines.validate(line: "23766C141EAA3484007A9026 /* iOS */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) + lines.validate(line: "23766C291EAA3484007A9026 /* iOSTests */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) + lines.validate(line: "23766C131EAA3484007A9026 /* Products */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) + lines.validate(line: "04D5C09D1F153824008A2F98 /* Frameworks */,", betweenLine: rootChildrenStart, andLine: rootChildrenEnd) + + // iOS + let iosGroup = lines.findLine("23766C141EAA3484007A9026 /* iOS */ = {", after: beginGroup) + let iosChildrenStart = lines.findLine("children = (", after: iosGroup) + let iosChildrenEnd = lines.findLine(");", after: iosChildrenStart) + + lines.validate(line: "3CD1EADB205763E400DAEECB /* Model.xcdatamodeld */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "3CD1EAD92057638200DAEECB /* GroupWithoutFolder */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "23766C151EAA3484007A9026 /* AppDelegate.swift */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "23766C171EAA3484007A9026 /* ViewController.swift */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "23766C191EAA3484007A9026 /* Main.storyboard */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "23766C1C1EAA3484007A9026 /* Assets.xcassets */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "23766C1E1EAA3484007A9026 /* LaunchScreen.storyboard */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "23766C211EAA3484007A9026 /* Info.plist */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "04D5C0A01F153915008A2F98 /* Public.h */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "04D5C0A11F15391B008A2F98 /* Protected.h */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + lines.validate(line: "04D5C0A21F153921008A2F98 /* Private.h */,", betweenLine: iosChildrenStart, andLine: iosChildrenEnd) + + // iOS Tests + let iosTestsGroup = lines.findLine("23766C291EAA3484007A9026 /* iOSTests */ = {", after: beginGroup) + let iosTestsChildrenStart = lines.findLine("children = (", after: iosTestsGroup) + let iosTestsChildrenEnd = lines.findLine(");", after: iosTestsChildrenStart) + + lines.validate(line: "23766C2A1EAA3484007A9026 /* iOSTests.swift */,", betweenLine: iosTestsChildrenStart, andLine: iosTestsChildrenEnd) + lines.validate(line: "23766C2C1EAA3484007A9026 /* Info.plist */,", betweenLine: iosTestsChildrenStart, andLine: iosTestsChildrenEnd) + } - func test_navigator_groups_in_filename_order_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - - let beginGroup = lines.findLine("/* Begin PBXGroup section */") - - // Root - let rootGroup = lines.findLine("23766C091EAA3484007A9026 = {", after: beginGroup) - var line = lines.findLine("children = (", after: rootGroup) - line = lines.validate(line: "04D5C09D1F153824008A2F98 /* Frameworks */,", after: line) - line = lines.validate(line: "23766C131EAA3484007A9026 /* Products */,", after: line) - line = lines.validate(line: "23766C141EAA3484007A9026 /* iOS */,", after: line) - line = lines.validate(line: "23766C291EAA3484007A9026 /* iOSTests */,", after: line) - lines.validate(line: ");", after: line) - - // iOS - let iosGroup = lines.findLine("23766C141EAA3484007A9026 /* iOS */ = {", after: beginGroup) - line = lines.findLine("children = (", after: iosGroup) - line = lines.validate(line: "23766C151EAA3484007A9026 /* AppDelegate.swift */,", after: line) - line = lines.validate(line: "23766C1C1EAA3484007A9026 /* Assets.xcassets */,", after: line) - line = lines.validate(line: "3CD1EAD92057638200DAEECB /* GroupWithoutFolder */,", after: line) - line = lines.validate(line: "23766C211EAA3484007A9026 /* Info.plist */,", after: line) - line = lines.validate(line: "23766C1E1EAA3484007A9026 /* LaunchScreen.storyboard */,", after: line) - line = lines.validate(line: "23766C191EAA3484007A9026 /* Main.storyboard */,", after: line) - line = lines.validate(line: "3CD1EADB205763E400DAEECB /* Model.xcdatamodeld */,", after: line) - line = lines.validate(line: "04D5C0A21F153921008A2F98 /* Private.h */,", after: line) - line = lines.validate(line: "04D5C0A11F15391B008A2F98 /* Protected.h */,", after: line) - line = lines.validate(line: "04D5C0A01F153915008A2F98 /* Public.h */,", after: line) - line = lines.validate(line: "23766C171EAA3484007A9026 /* ViewController.swift */,", after: line) - lines.validate(line: ");", after: line) - - // iOS Tests - let iosTestsGroup = lines.findLine("23766C291EAA3484007A9026 /* iOSTests */ = {", after: beginGroup) - line = lines.findLine("children = (", after: iosTestsGroup) - line = lines.validate(line: "23766C2C1EAA3484007A9026 /* Info.plist */,", after: line) - line = lines.validate(line: "23766C2A1EAA3484007A9026 /* iOSTests.swift */,", after: line) - lines.validate(line: ");", after: line) - } + func test_navigator_groups_in_filename_order_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + + let beginGroup = lines.findLine("/* Begin PBXGroup section */") + + // Root + let rootGroup = lines.findLine("23766C091EAA3484007A9026 = {", after: beginGroup) + var line = lines.findLine("children = (", after: rootGroup) + line = lines.validate(line: "04D5C09D1F153824008A2F98 /* Frameworks */,", after: line) + line = lines.validate(line: "23766C131EAA3484007A9026 /* Products */,", after: line) + line = lines.validate(line: "23766C141EAA3484007A9026 /* iOS */,", after: line) + line = lines.validate(line: "23766C291EAA3484007A9026 /* iOSTests */,", after: line) + lines.validate(line: ");", after: line) + + // iOS + let iosGroup = lines.findLine("23766C141EAA3484007A9026 /* iOS */ = {", after: beginGroup) + line = lines.findLine("children = (", after: iosGroup) + line = lines.validate(line: "23766C151EAA3484007A9026 /* AppDelegate.swift */,", after: line) + line = lines.validate(line: "23766C1C1EAA3484007A9026 /* Assets.xcassets */,", after: line) + line = lines.validate(line: "3CD1EAD92057638200DAEECB /* GroupWithoutFolder */,", after: line) + line = lines.validate(line: "23766C211EAA3484007A9026 /* Info.plist */,", after: line) + line = lines.validate(line: "23766C1E1EAA3484007A9026 /* LaunchScreen.storyboard */,", after: line) + line = lines.validate(line: "23766C191EAA3484007A9026 /* Main.storyboard */,", after: line) + line = lines.validate(line: "3CD1EADB205763E400DAEECB /* Model.xcdatamodeld */,", after: line) + line = lines.validate(line: "04D5C0A21F153921008A2F98 /* Private.h */,", after: line) + line = lines.validate(line: "04D5C0A11F15391B008A2F98 /* Protected.h */,", after: line) + line = lines.validate(line: "04D5C0A01F153915008A2F98 /* Public.h */,", after: line) + line = lines.validate(line: "23766C171EAA3484007A9026 /* ViewController.swift */,", after: line) + lines.validate(line: ");", after: line) + + // iOS Tests + let iosTestsGroup = lines.findLine("23766C291EAA3484007A9026 /* iOSTests */ = {", after: beginGroup) + line = lines.findLine("children = (", after: iosTestsGroup) + line = lines.validate(line: "23766C2C1EAA3484007A9026 /* Info.plist */,", after: line) + line = lines.validate(line: "23766C2A1EAA3484007A9026 /* iOSTests.swift */,", after: line) + lines.validate(line: ");", after: line) + } - func test_navigator_groups_in_filename_groups_first_order_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst) - let lines = lines(fromFile: encodeProject(settings: settings)) - - let beginGroup = lines.findLine("/* Begin PBXGroup section */") - - // Root - let rootGroup = lines.findLine("23766C091EAA3484007A9026 = {", after: beginGroup) - var line = lines.findLine("children = (", after: rootGroup) - line = lines.validate(line: "04D5C09D1F153824008A2F98 /* Frameworks */,", after: line) - line = lines.validate(line: "23766C131EAA3484007A9026 /* Products */,", after: line) - line = lines.validate(line: "23766C141EAA3484007A9026 /* iOS */,", after: line) - line = lines.validate(line: "23766C291EAA3484007A9026 /* iOSTests */,", after: line) - lines.validate(line: ");", after: line) - - // iOS - let iosGroup = lines.findLine("23766C141EAA3484007A9026 /* iOS */ = {", after: beginGroup) - line = lines.findLine("children = (", after: iosGroup) - line = lines.validate(line: "3CD1EAD92057638200DAEECB /* GroupWithoutFolder */,", after: line) - line = lines.validate(line: "23766C151EAA3484007A9026 /* AppDelegate.swift */,", after: line) - line = lines.validate(line: "23766C1C1EAA3484007A9026 /* Assets.xcassets */,", after: line) - line = lines.validate(line: "23766C211EAA3484007A9026 /* Info.plist */,", after: line) - line = lines.validate(line: "23766C1E1EAA3484007A9026 /* LaunchScreen.storyboard */,", after: line) - line = lines.validate(line: "23766C191EAA3484007A9026 /* Main.storyboard */,", after: line) - line = lines.validate(line: "3CD1EADB205763E400DAEECB /* Model.xcdatamodeld */,", after: line) - line = lines.validate(line: "04D5C0A21F153921008A2F98 /* Private.h */,", after: line) - line = lines.validate(line: "04D5C0A11F15391B008A2F98 /* Protected.h */,", after: line) - line = lines.validate(line: "04D5C0A01F153915008A2F98 /* Public.h */,", after: line) - line = lines.validate(line: "23766C171EAA3484007A9026 /* ViewController.swift */,", after: line) - lines.validate(line: ");", after: line) - - // iOS Tests - let iosTestsGroup = lines.findLine("23766C291EAA3484007A9026 /* iOSTests */ = {", after: beginGroup) - line = lines.findLine("children = (", after: iosTestsGroup) - line = lines.validate(line: "23766C2C1EAA3484007A9026 /* Info.plist */,", after: line) - line = lines.validate(line: "23766C2A1EAA3484007A9026 /* iOSTests.swift */,", after: line) - lines.validate(line: ");", after: line) - } + func test_navigator_groups_in_filename_groups_first_order_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst) + let lines = lines(fromFile: encodeProject(settings: settings)) + + let beginGroup = lines.findLine("/* Begin PBXGroup section */") + + // Root + let rootGroup = lines.findLine("23766C091EAA3484007A9026 = {", after: beginGroup) + var line = lines.findLine("children = (", after: rootGroup) + line = lines.validate(line: "04D5C09D1F153824008A2F98 /* Frameworks */,", after: line) + line = lines.validate(line: "23766C131EAA3484007A9026 /* Products */,", after: line) + line = lines.validate(line: "23766C141EAA3484007A9026 /* iOS */,", after: line) + line = lines.validate(line: "23766C291EAA3484007A9026 /* iOSTests */,", after: line) + lines.validate(line: ");", after: line) + + // iOS + let iosGroup = lines.findLine("23766C141EAA3484007A9026 /* iOS */ = {", after: beginGroup) + line = lines.findLine("children = (", after: iosGroup) + line = lines.validate(line: "3CD1EAD92057638200DAEECB /* GroupWithoutFolder */,", after: line) + line = lines.validate(line: "23766C151EAA3484007A9026 /* AppDelegate.swift */,", after: line) + line = lines.validate(line: "23766C1C1EAA3484007A9026 /* Assets.xcassets */,", after: line) + line = lines.validate(line: "23766C211EAA3484007A9026 /* Info.plist */,", after: line) + line = lines.validate(line: "23766C1E1EAA3484007A9026 /* LaunchScreen.storyboard */,", after: line) + line = lines.validate(line: "23766C191EAA3484007A9026 /* Main.storyboard */,", after: line) + line = lines.validate(line: "3CD1EADB205763E400DAEECB /* Model.xcdatamodeld */,", after: line) + line = lines.validate(line: "04D5C0A21F153921008A2F98 /* Private.h */,", after: line) + line = lines.validate(line: "04D5C0A11F15391B008A2F98 /* Protected.h */,", after: line) + line = lines.validate(line: "04D5C0A01F153915008A2F98 /* Public.h */,", after: line) + line = lines.validate(line: "23766C171EAA3484007A9026 /* ViewController.swift */,", after: line) + lines.validate(line: ");", after: line) + + // iOS Tests + let iosTestsGroup = lines.findLine("23766C291EAA3484007A9026 /* iOSTests */ = {", after: beginGroup) + line = lines.findLine("children = (", after: iosTestsGroup) + line = lines.validate(line: "23766C2C1EAA3484007A9026 /* Info.plist */,", after: line) + line = lines.validate(line: "23766C2A1EAA3484007A9026 /* iOSTests.swift */,", after: line) + lines.validate(line: ");", after: line) + } - // MARK: - File system synchronized root groups + // 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)) + 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 */, F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SynchronizedRootGroups; sourceTree = \"\"; };", after: beginGroup) - line = lines.validate(line: "/* End PBXFileSystemSynchronizedRootGroup section */", after: line) - } + let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedRootGroup section */") + var line = lines.validate(line: "6CF05B9D2C53F64800EF267F /* SynchronizedRootGroups */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (6CF05BA32C53F97F00EF267F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); 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: - 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: - File system synchronized group build phase membership exception set - - func test_fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSets_when_projectWithFileSystemSynchronizedRootGroups() throws { - // Given - try loadSynchronizedRootGroups() - let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst) - let lines = lines(fromFile: encodeProject(settings: settings)) - - let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */") - var line = lines.validate(line: "F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {", after: beginGroup) - line = lines.validate(line: "isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;", after: line) - line = lines.validate(line: "attributesByRelativePath = {", after: line) - line = lines.validate(line: "XPCService.xpc = (", after: line) - line = lines.validate(line: "RemoveHeadersOnCopy,", after: line) - line = lines.validate(line: ");", after: line) - line = lines.validate(line: "};", after: line) - line = lines.validate(line: "buildPhase = F841A9CA2D63AFBB00059ED6 /* CopyFiles */;", after: line) - line = lines.validate(line: "membershipExceptions = (", after: line) - line = lines.validate(line: "XPCService.xpc,", after: line) - line = lines.validate(line: ");", after: line) - line = lines.validate(line: "};", after: line) - line = lines.validate(line: "/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */", after: line) - } + // MARK: - File system synchronized group build phase membership exception set + + func test_fileSystemSynchronizedGroupBuildPhaseMembershipExceptionSets_when_projectWithFileSystemSynchronizedRootGroups() throws { + // Given + try loadSynchronizedRootGroups() + let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst) + let lines = lines(fromFile: encodeProject(settings: settings)) + + let beginGroup = lines.findLine("/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */") + var line = lines.validate(line: "F841A9D12D63B00A00059ED6 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {", after: beginGroup) + line = lines.validate(line: "isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;", after: line) + line = lines.validate(line: "attributesByRelativePath = {", after: line) + line = lines.validate(line: "XPCService.xpc = (", after: line) + line = lines.validate(line: "RemoveHeadersOnCopy,", after: line) + line = lines.validate(line: ");", after: line) + line = lines.validate(line: "};", after: line) + line = lines.validate(line: "buildPhase = F841A9CA2D63AFBB00059ED6 /* CopyFiles */;", after: line) + line = lines.validate(line: "membershipExceptions = (", after: line) + line = lines.validate(line: "XPCService.xpc,", after: line) + line = lines.validate(line: ");", after: line) + line = lines.validate(line: "};", after: line) + line = lines.validate(line: "/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */", after: line) + } - // MARK: - Build phases + // MARK: - Build phases - func test_build_phase_sources_unsorted_when_iOSProject() throws { - try loadiOSProject() + func test_build_phase_sources_unsorted_when_iOSProject() throws { + try loadiOSProject() - let lines = lines(fromFile: encodeProject()) - let beginGroup = lines.findLine("/* Begin PBXSourcesBuildPhase section */") - let files = lines.findLine("files = (", after: beginGroup) - let endGroup = lines.findLine("/* End PBXSourcesBuildPhase section */") - lines.validate(line: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */,", betweenLine: files, andLine: endGroup) - lines.validate(line: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */,", betweenLine: files, andLine: endGroup) - lines.validate(line: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */,", betweenLine: files, andLine: endGroup) - } + let lines = lines(fromFile: encodeProject()) + let beginGroup = lines.findLine("/* Begin PBXSourcesBuildPhase section */") + let files = lines.findLine("files = (", after: beginGroup) + let endGroup = lines.findLine("/* End PBXSourcesBuildPhase section */") + lines.validate(line: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */,", betweenLine: files, andLine: endGroup) + lines.validate(line: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */,", betweenLine: files, andLine: endGroup) + lines.validate(line: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */,", betweenLine: files, andLine: endGroup) + } - func test_build_phase_sources_sorted_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("/* Begin PBXSourcesBuildPhase section */") - var line = lines.findLine("files = (", after: beginGroup) - line = lines.validate(line: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */,", after: line) - line = lines.validate(line: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */,", after: line) - line = lines.validate(line: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */,", after: line) - line = lines.validate(line: "/* End PBXSourcesBuildPhase section */", after: line) - } + func test_build_phase_sources_sorted_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("/* Begin PBXSourcesBuildPhase section */") + var line = lines.findLine("files = (", after: beginGroup) + line = lines.validate(line: "23766C161EAA3484007A9026 /* AppDelegate.swift in Sources */,", after: line) + line = lines.validate(line: "3CD1EADD205763E400DAEECB /* Model.xcdatamodeld in Sources */,", after: line) + line = lines.validate(line: "23766C181EAA3484007A9026 /* ViewController.swift in Sources */,", after: line) + line = lines.validate(line: "/* End PBXSourcesBuildPhase section */", after: line) + } - func test_build_phase_headers_unsorted_when_iOSProject() throws { - try loadiOSProject() + func test_build_phase_headers_unsorted_when_iOSProject() throws { + try loadiOSProject() - let lines = lines(fromFile: encodeProject()) - let beginGroup = lines.findLine("/* Begin PBXHeadersBuildPhase section */") - let files = lines.findLine("files = (", after: beginGroup) - let endGroup = lines.findLine("/* End PBXHeadersBuildPhase section */") - lines.validate(line: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */,", betweenLine: files, andLine: endGroup) - lines.validate(line: "04D5C0A51F153924008A2F98 /* Private.h in Headers */,", betweenLine: files, andLine: endGroup) - lines.validate(line: "04D5C0A31F153924008A2F98 /* Public.h in Headers */,", betweenLine: files, andLine: endGroup) - } + let lines = lines(fromFile: encodeProject()) + let beginGroup = lines.findLine("/* Begin PBXHeadersBuildPhase section */") + let files = lines.findLine("files = (", after: beginGroup) + let endGroup = lines.findLine("/* End PBXHeadersBuildPhase section */") + lines.validate(line: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */,", betweenLine: files, andLine: endGroup) + lines.validate(line: "04D5C0A51F153924008A2F98 /* Private.h in Headers */,", betweenLine: files, andLine: endGroup) + lines.validate(line: "04D5C0A31F153924008A2F98 /* Public.h in Headers */,", betweenLine: files, andLine: endGroup) + } - func test_build_phase_headers_sorted_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("/* Begin PBXHeadersBuildPhase section */") - var line = lines.findLine("files = (", after: beginGroup) - line = lines.validate(line: "04D5C0A51F153924008A2F98 /* Private.h in Headers */,", after: line) - line = lines.validate(line: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */,", after: line) - line = lines.validate(line: "04D5C0A31F153924008A2F98 /* Public.h in Headers */,", after: line) - line = lines.validate(line: "/* End PBXHeadersBuildPhase section */", after: line) - } + func test_build_phase_headers_sorted_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("/* Begin PBXHeadersBuildPhase section */") + var line = lines.findLine("files = (", after: beginGroup) + line = lines.validate(line: "04D5C0A51F153924008A2F98 /* Private.h in Headers */,", after: line) + line = lines.validate(line: "04D5C0A41F153924008A2F98 /* Protected.h in Headers */,", after: line) + line = lines.validate(line: "04D5C0A31F153924008A2F98 /* Public.h in Headers */,", after: line) + line = lines.validate(line: "/* End PBXHeadersBuildPhase section */", after: line) + } - func test_build_phase_resources_unsorted_when_iOSProject() throws { - try loadiOSProject() + func test_build_phase_resources_unsorted_when_iOSProject() throws { + try loadiOSProject() - let lines = lines(fromFile: encodeProject()) - let beginGroup = lines.findLine("/* Begin PBXResourcesBuildPhase section */") - let files = lines.findLine("files = (", after: beginGroup) - let endGroup = lines.findLine("/* End PBXResourcesBuildPhase section */") - lines.validate(line: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */,", betweenLine: files, andLine: endGroup) - lines.validate(line: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */,", betweenLine: files, andLine: endGroup) - lines.validate(line: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */,", betweenLine: files, andLine: endGroup) - } + let lines = lines(fromFile: encodeProject()) + let beginGroup = lines.findLine("/* Begin PBXResourcesBuildPhase section */") + let files = lines.findLine("files = (", after: beginGroup) + let endGroup = lines.findLine("/* End PBXResourcesBuildPhase section */") + lines.validate(line: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */,", betweenLine: files, andLine: endGroup) + lines.validate(line: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */,", betweenLine: files, andLine: endGroup) + lines.validate(line: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */,", betweenLine: files, andLine: endGroup) + } - func test_build_phase_resources_sorted_when_iOSProject() throws { - try loadiOSProject() - - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("/* Begin PBXResourcesBuildPhase section */") - var line = lines.findLine("files = (", after: beginGroup) - line = lines.validate(line: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */,", after: line) - line = lines.validate(line: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */,", after: line) - line = lines.validate(line: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */,", after: line) - line = lines.validate(line: "/* End PBXResourcesBuildPhase section */", after: line) - } + func test_build_phase_resources_sorted_when_iOSProject() throws { + try loadiOSProject() + + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("/* Begin PBXResourcesBuildPhase section */") + var line = lines.findLine("files = (", after: beginGroup) + line = lines.validate(line: "23766C1D1EAA3484007A9026 /* Assets.xcassets in Resources */,", after: line) + line = lines.validate(line: "23766C201EAA3484007A9026 /* LaunchScreen.storyboard in Resources */,", after: line) + line = lines.validate(line: "23766C1B1EAA3484007A9026 /* Main.storyboard in Resources */,", after: line) + line = lines.validate(line: "/* End PBXResourcesBuildPhase section */", after: line) + } - func test_build_rules_when_targetWithCustomBuildRulesProject() throws { - try loadTargetWithCustomBuildRulesProject() - - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("6CAD68202A56E31400662D8A /* PBXBuildRule */ = {") - var line = lines.validate(line: "isa = PBXBuildRule;", after: beginGroup) - line = lines.validate(line: "compilerSpec = com.apple.compilers.proxy.script;", after: line) - line = lines.validate(line: "dependencyFile = \"$(DERIVED_FILES_DIR)/$(INPUT_FILE_PATH).d\";", after: line) - line = lines.validate(line: "fileType = pattern.proxy;", after: line) - line = lines.validate(line: "inputFiles = (", after: line) - line = lines.validate(line: ");", after: line) - line = lines.validate(line: "isEditable = 1;", after: line) - line = lines.validate(line: "name = \"Custom 2 with dependency file\";", after: line) - line = lines.validate(line: "outputFiles = (", after: line) - line = lines.validate(line: ");", after: line) - line = lines.validate(line: "script = \"# Type a script or drag a script file from your workspace to insert its path.\\n\";", after: line) - line = lines.validate(line: "};", after: line) - } + func test_build_rules_when_targetWithCustomBuildRulesProject() throws { + try loadTargetWithCustomBuildRulesProject() + + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("6CAD68202A56E31400662D8A /* PBXBuildRule */ = {") + var line = lines.validate(line: "isa = PBXBuildRule;", after: beginGroup) + line = lines.validate(line: "compilerSpec = com.apple.compilers.proxy.script;", after: line) + line = lines.validate(line: "dependencyFile = \"$(DERIVED_FILES_DIR)/$(INPUT_FILE_PATH).d\";", after: line) + line = lines.validate(line: "fileType = pattern.proxy;", after: line) + line = lines.validate(line: "inputFiles = (", after: line) + line = lines.validate(line: ");", after: line) + line = lines.validate(line: "isEditable = 1;", after: line) + line = lines.validate(line: "name = \"Custom 2 with dependency file\";", after: line) + line = lines.validate(line: "outputFiles = (", after: line) + line = lines.validate(line: ");", after: line) + line = lines.validate(line: "script = \"# Type a script or drag a script file from your workspace to insert its path.\\n\";", after: line) + line = lines.validate(line: "};", after: line) + } - func test_package_section_when_projectWithXCLocalSwiftPackageReference() throws { - try loadProjectWithXCLocalSwiftPackageReference() - - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("/* Begin XCLocalSwiftPackageReference section */") - var line = lines.validate(line: "C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference \"MyLocalPackage\" */ = {", after: beginGroup) - line = lines.validate(line: "isa = XCLocalSwiftPackageReference;", after: line) - line = lines.validate(line: "relativePath = MyLocalPackage;", after: line) - line = lines.validate(line: "};", after: line) - line = lines.validate(line: "/* End XCLocalSwiftPackageReference section */", after: line) - } + func test_package_section_when_projectWithXCLocalSwiftPackageReference() throws { + try loadProjectWithXCLocalSwiftPackageReference() + + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("/* Begin XCLocalSwiftPackageReference section */") + var line = lines.validate(line: "C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference \"MyLocalPackage\" */ = {", after: beginGroup) + line = lines.validate(line: "isa = XCLocalSwiftPackageReference;", after: line) + line = lines.validate(line: "relativePath = MyLocalPackage;", after: line) + line = lines.validate(line: "};", after: line) + line = lines.validate(line: "/* End XCLocalSwiftPackageReference section */", after: line) + } - func test_package_references_when_projectWithXCLocalSwiftPackageReference() throws { - try loadProjectWithXCLocalSwiftPackageReference() + func test_package_references_when_projectWithXCLocalSwiftPackageReference() throws { + try loadProjectWithXCLocalSwiftPackageReference() - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("packageReferences = (") - var line = lines.validate(line: "42AA19FF22AAF0D600428760 /* XCRemoteSwiftPackageReference \"RxSwift\" */,", after: beginGroup) - line = lines.validate(line: "C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference \"MyLocalPackage\" */,", after: line) - line = lines.validate(line: ");", after: line) - } + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("packageReferences = (") + var line = lines.validate(line: "42AA19FF22AAF0D600428760 /* XCRemoteSwiftPackageReference \"RxSwift\" */,", after: beginGroup) + line = lines.validate(line: "C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference \"MyLocalPackage\" */,", after: line) + line = lines.validate(line: ");", after: line) + } - func test_package_references_when_projectWithRelativePathForXCLocalSwiftPackageReference() throws { - try loadProjectWithRelativeXCLocalSwiftPackageReference() + func test_package_references_when_projectWithRelativePathForXCLocalSwiftPackageReference() throws { + try loadProjectWithRelativeXCLocalSwiftPackageReference() - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("packageReferences = (") - var line = lines.validate(line: "C9FDF5C82AD8AE400096A37A /* XCLocalSwiftPackageReference \"../MyLocalPackage\" */,", after: beginGroup) - line = lines.validate(line: ");", after: line) - } + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("packageReferences = (") + var line = lines.validate(line: "C9FDF5C82AD8AE400096A37A /* XCLocalSwiftPackageReference \"../MyLocalPackage\" */,", after: beginGroup) + line = lines.validate(line: ");", after: line) + } - func test_package_references_when_projectWithXCLocalSwiftPackageReferences() throws { - try loadProjectWithXCLocalSwiftPackageReferences() + func test_package_references_when_projectWithXCLocalSwiftPackageReferences() throws { + try loadProjectWithXCLocalSwiftPackageReferences() - let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - let beginGroup = lines.findLine("packageReferences = (") - var line = lines.validate(line: "C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference \"MyLocalPackage\" */,", after: beginGroup) - line = lines.validate(line: "C9FDF5CB2AD8B3B50096A37A /* XCLocalSwiftPackageReference \"MyOtherLocalPackage/MyOtherLocalPackage\" */,", after: line) - line = lines.validate(line: ");", after: line) - } + let settings = PBXOutputSettings(projBuildPhaseFileOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + let beginGroup = lines.findLine("packageReferences = (") + var line = lines.validate(line: "C9FDF5C52AD604310096A37A /* XCLocalSwiftPackageReference \"MyLocalPackage\" */,", after: beginGroup) + line = lines.validate(line: "C9FDF5CB2AD8B3B50096A37A /* XCLocalSwiftPackageReference \"MyOtherLocalPackage/MyOtherLocalPackage\" */,", after: line) + line = lines.validate(line: ");", after: line) + } - // MARK: - Test internals + // MARK: - Test internals - private func encodeProject(settings: PBXOutputSettings = PBXOutputSettings(), line: UInt = #line) -> String { - do { - return try PBXProjEncoder(outputSettings: settings).encode(proj: proj) - } catch { - XCTFail("Unexpected error encoding project: \(error)", line: line) - return "" + private func encodeProject(settings: PBXOutputSettings = PBXOutputSettings(), line: UInt = #line) -> String { + do { + return try PBXProjEncoder(outputSettings: settings).encode(proj: proj) + } catch { + XCTFail("Unexpected error encoding project: \(error)", line: line) + return "" + } } - } - private func encodeProjectThrows(error expectedError: some Error, line: UInt = #line) { - do { - _ = try PBXProjEncoder(outputSettings: PBXOutputSettings()).encode(proj: proj) - XCTFail("Expected '\(expectedError)' to be thrown", line: line) - } catch { - if type(of: expectedError) != type(of: error) { - XCTFail("Expected '\(expectedError)' to be thrown, but got \(error)", line: line) + private func encodeProjectThrows(error expectedError: some Error, line: UInt = #line) { + do { + _ = try PBXProjEncoder(outputSettings: PBXOutputSettings()).encode(proj: proj) + XCTFail("Expected '\(expectedError)' to be thrown", line: line) + } catch { + if type(of: expectedError) != type(of: error) { + XCTFail("Expected '\(expectedError)' to be thrown, but got \(error)", line: line) + } } } - } - private func lines(fromFile file: String) -> [String] { - file.replacingOccurrences(of: "\t", with: "").components(separatedBy: "\n") - } + private func lines(fromFile file: String) -> [String] { + file.replacingOccurrences(of: "\t", with: "").components(separatedBy: "\n") + } - private func loadiOSProject() throws { - proj = try PBXProj(data: iosProjectData()) - } + private func loadiOSProject() throws { + proj = try PBXProj(data: iosProjectData()) + } - private func loadSynchronizedRootGroups() throws { - proj = try PBXProj(data: synchronizedRootGroupsFixture()) - } + private func loadSynchronizedRootGroups() throws { + proj = try PBXProj(data: synchronizedRootGroupsFixture()) + } - private func loadFileSharedAcrossTargetsProject() throws { - proj = try PBXProj(data: fileSharedAcrossTargetsData()) - } + private func loadFileSharedAcrossTargetsProject() throws { + proj = try PBXProj(data: fileSharedAcrossTargetsData()) + } - private func loadTargetWithCustomBuildRulesProject() throws { - proj = try PBXProj(data: targetWithCustomBuildRulesData()) - } + private func loadTargetWithCustomBuildRulesProject() throws { + proj = try PBXProj(data: targetWithCustomBuildRulesData()) + } - private func loadProjectWithXCLocalSwiftPackageReference() throws { - proj = try PBXProj(data: iosProjectWithXCLocalSwiftPackageReference()) - } + private func loadProjectWithXCLocalSwiftPackageReference() throws { + proj = try PBXProj(data: iosProjectWithXCLocalSwiftPackageReference()) + } - private func loadProjectWithXCLocalSwiftPackageReferences() throws { - proj = try PBXProj(data: iosProjectWithXCLocalSwiftPackageReferences()) - } + private func loadProjectWithXCLocalSwiftPackageReferences() throws { + proj = try PBXProj(data: iosProjectWithXCLocalSwiftPackageReferences()) + } - private func loadProjectWithRelativeXCLocalSwiftPackageReference() throws { - proj = try PBXProj(data: iosProjectWithRelativeXCLocalSwiftPackageReferences()) + private func loadProjectWithRelativeXCLocalSwiftPackageReference() throws { + proj = try PBXProj(data: iosProjectWithRelativeXCLocalSwiftPackageReferences()) + } } -} -// MARK: - Line validations + // MARK: - Line validations -private extension [String] { - @discardableResult func validate(line string: String, betweenLine lineAbove: Int, andLine lineBelow: Int, line: UInt = #line) -> Int { - validate(string, using: { $0 == $1 }, betweenLine: lineAbove, andLine: lineBelow, line: line) - } + private extension [String] { + @discardableResult func validate(line string: String, betweenLine lineAbove: Int, andLine lineBelow: Int, line: UInt = #line) -> Int { + validate(string, using: { $0 == $1 }, betweenLine: lineAbove, andLine: lineBelow, line: line) + } - @discardableResult func validate(lineContaining string: String, betweenLine lineAbove: Int, andLine lineBelow: Int, line: UInt = #line) -> Int { - validate(string, using: { $0.contains($1) }, betweenLine: lineAbove, andLine: lineBelow, line: line) - } + @discardableResult func validate(lineContaining string: String, betweenLine lineAbove: Int, andLine lineBelow: Int, line: UInt = #line) -> Int { + validate(string, using: { $0.contains($1) }, betweenLine: lineAbove, andLine: lineBelow, line: line) + } - func validate(_ string: String, using: (String, String) -> Bool, betweenLine lineAbove: Int, andLine lineBelow: Int, line: UInt) -> Int { - let lineNumber = validate(string, using: using, after: lineAbove, line: line) - if lineNumber >= lineBelow { - XCTFail("Expected to find line between lines \(lineAbove) and \(lineBelow), but was found after \(lineBelow).", line: line) + func validate(_ string: String, using: (String, String) -> Bool, betweenLine lineAbove: Int, andLine lineBelow: Int, line: UInt) -> Int { + let lineNumber = validate(string, using: using, after: lineAbove, line: line) + if lineNumber >= lineBelow { + XCTFail("Expected to find line between lines \(lineAbove) and \(lineBelow), but was found after \(lineBelow).", line: line) + } + return lineNumber } - return lineNumber - } - @discardableResult func validate(line string: String, onLineAfter: Int, line: UInt = #line) -> Int { - validate(string, using: { $0 == $1 }, onLineAfter: onLineAfter, line: line) - } + @discardableResult func validate(line string: String, onLineAfter: Int, line: UInt = #line) -> Int { + validate(string, using: { $0 == $1 }, onLineAfter: onLineAfter, line: line) + } - @discardableResult func validate(lineContaining string: String, onLineAfter: Int, line: UInt = #line) -> Int { - validate(string, using: { $0.contains($1) }, onLineAfter: onLineAfter, line: line) - } + @discardableResult func validate(lineContaining string: String, onLineAfter: Int, line: UInt = #line) -> Int { + validate(string, using: { $0.contains($1) }, onLineAfter: onLineAfter, line: line) + } - func validate(_ string: String, using: (String, String) -> Bool, onLineAfter: Int, line: UInt) -> Int { - let lineNumber = validate(string, using: using, after: onLineAfter, line: line) - if lineNumber != onLineAfter + 1 { - XCTFail("Expected to find at line \(onLineAfter + 1), but was found on line \(lineNumber).", line: line) + func validate(_ string: String, using: (String, String) -> Bool, onLineAfter: Int, line: UInt) -> Int { + let lineNumber = validate(string, using: using, after: onLineAfter, line: line) + if lineNumber != onLineAfter + 1 { + XCTFail("Expected to find at line \(onLineAfter + 1), but was found on line \(lineNumber).", line: line) + } + return lineNumber } - return lineNumber - } - @discardableResult func validate(line string: String, after: Int = 0, line: UInt = #line) -> Int { - validate(string, using: { $0 == $1 }, after: after, line: line) - } + @discardableResult func validate(line string: String, after: Int = 0, line: UInt = #line) -> Int { + validate(string, using: { $0 == $1 }, after: after, line: line) + } - @discardableResult func validate(lineContaining string: String, after: Int = 0, line: UInt = #line) -> Int { - validate(string, using: { $0.contains($1) }, after: after, line: line) - } + @discardableResult func validate(lineContaining string: String, after: Int = 0, line: UInt = #line) -> Int { + validate(string, using: { $0.contains($1) }, after: after, line: line) + } - func validate(_ string: String, using: (String, String) -> Bool, after: Int, line: UInt) -> Int { - let lineNumber = findLine(string, matcher: using, after: after) - if lineNumber == endIndex { - XCTFail("Line not found after line \(after)", line: line) + func validate(_ string: String, using: (String, String) -> Bool, after: Int, line: UInt) -> Int { + let lineNumber = findLine(string, matcher: using, after: after) + if lineNumber == endIndex { + XCTFail("Line not found after line \(after)", line: line) + } + return lineNumber } - return lineNumber - } - func findLine(_ string: String, after: Int = 0) -> Int { - findLine(string, matcher: { $0 == $1 }, after: after) - } + func findLine(_ string: String, after: Int = 0) -> Int { + findLine(string, matcher: { $0 == $1 }, after: after) + } - func findLine(containing string: String, after: Int = 0) -> Int { - findLine(string, matcher: { $0.contains($1) }, after: after) - } + func findLine(containing string: String, after: Int = 0) -> Int { + findLine(string, matcher: { $0.contains($1) }, after: after) + } - func findLine(_ string: String, matcher: (String, String) -> Bool, after: Int) -> Int { - for i in after ..< endIndex { - if matcher(self[i], string) { - return i + func findLine(_ string: String, matcher: (String, String) -> Bool, after: Int) -> Int { + for i in after ..< endIndex { + if matcher(self[i], string) { + return i + } } + return endIndex } - return endIndex - } - func log() { - var line = 0 - forEach { - let lineStr = "\(line)" - let lineNo = lineStr + String(repeating: " ", count: 5 - lineStr.count) - print(lineNo, "|", $0) - line += 1 + func log() { + var line = 0 + forEach { + let lineStr = "\(line)" + let lineNo = lineStr + String(repeating: " ", count: 5 - lineStr.count) + print(lineNo, "|", $0) + line += 1 + } } } -} + +#endif diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift index fc442ca7d..570c89e8f 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift @@ -1,86 +1,89 @@ -import Foundation -import PathKit -import XCTest -@testable import XcodeProj +#if os(macOS) || (os(Linux) && compiler(>=6.1)) + import Foundation + import PathKit + import XCTest + @testable import XcodeProj -final class PBXProjIntegrationTests: XCTestCase { - func test_init_initializesTheProjCorrectly() { - let data = try! Data(contentsOf: fixturePath().url) - let decoder = XcodeprojPropertyListDecoder() - let proj = try? decoder.decode(PBXProj.self, from: data) - XCTAssertNotNil(proj) - if let proj { - assert(proj: proj) + final class PBXProjIntegrationTests: XCTestCase { + func test_init_initializesTheProjCorrectly() throws { + let data = try XCTUnwrap(Data(contentsOf: fixturePath().url)) + let decoder = XcodeprojPropertyListDecoder() + let proj = try? decoder.decode(PBXProj.self, from: data) + XCTAssertNotNil(proj) + if let proj { + assert(proj: proj) + } } - } - - func test_write() { - testWrite(from: fixturePath(), - initModel: { path -> PBXProj? in - let data = try! Data(contentsOf: path.url) - let decoder = XcodeprojPropertyListDecoder() - return try? decoder.decode(PBXProj.self, from: data) - }, - modify: { $0 }) - } - func test_write_produces_no_diff() throws { - let tmpDir = try Path.uniqueTemporary() - defer { - try? tmpDir.delete() + func test_write() throws { + try testWrite(from: fixturePath(), + initModel: { path -> PBXProj? in + let data = try XCTUnwrap(Data(contentsOf: path.url)) + let decoder = XcodeprojPropertyListDecoder() + return try? decoder.decode(PBXProj.self, from: data) + }, + modify: { $0 }) } - let fixturePath = fixturePath().parent() - let xcodeprojPath = tmpDir + "Project.xcodeproj" - try fixturePath.copy(xcodeprojPath) + func test_write_produces_no_diff() throws { + let tmpDir = try Path.uniqueTemporary() + defer { + try? tmpDir.delete() + } - try tmpDir.chdir { - // Create a commit - try checkedOutput("git", ["init"]) - try checkedOutput("git", ["add", "."]) - try checkedOutput("git", [ - "-c", "user.email=test@example.com", "-c", "user.name=Test User", - "commit", "-m", "test", - ]) + let fixturePath = fixturePath().parent() + let xcodeprojPath = tmpDir + "Project.xcodeproj" + try fixturePath.copy(xcodeprojPath) - // Read/write the project - let project = try XcodeProj(path: xcodeprojPath) - try project.writePBXProj(path: xcodeprojPath, outputSettings: PBXOutputSettings()) + try tmpDir.chdir { + // Create a commit + try checkedOutput("git", ["init"]) + try checkedOutput("git", ["add", "."]) + try checkedOutput("git", [ + "-c", "user.email=test@example.com", "-c", "user.name=Test User", + "commit", "-m", "test", + ]) - let got = try checkedOutput("git", ["status"]) - XCTAssertTrue(got?.contains("nothing to commit") ?? false) + // Read/write the project + let project = try XcodeProj(path: xcodeprojPath) + try project.writePBXProj(path: xcodeprojPath, outputSettings: PBXOutputSettings()) + + let got = try checkedOutput("git", ["status"]) + XCTAssertTrue(got?.contains("nothing to commit") ?? false) + } } - } - private func fixturePath() -> Path { - let path = fixturesPath() + "iOS/Project.xcodeproj/project.pbxproj" - return path - } + private func fixturePath() -> Path { + let path = fixturesPath() + "iOS/Project.xcodeproj/project.pbxproj" + return path + } - private func assert(proj: PBXProj) { - XCTAssertEqual(proj.archiveVersion, 1) - XCTAssertEqual(proj.objectVersion, 52) - XCTAssertEqual(proj.classes.count, 0) - XCTAssertEqual(proj.objects.buildFiles.count, 13) - XCTAssertEqual(proj.objects.aggregateTargets.count, 0) - XCTAssertEqual(proj.objects.containerItemProxies.count, 1) - XCTAssertEqual(proj.objects.copyFilesBuildPhases.count, 1) - XCTAssertEqual(proj.objects.groups.count, 6) - XCTAssertEqual(proj.objects.configurationLists.count, 3) - XCTAssertEqual(proj.objects.buildConfigurations.count, 6) - XCTAssertEqual(proj.objects.variantGroups.count, 2) - XCTAssertEqual(proj.objects.targetDependencies.count, 1) - XCTAssertEqual(proj.objects.sourcesBuildPhases.count, 2) - XCTAssertEqual(proj.objects.shellScriptBuildPhases.count, 1) - XCTAssertEqual(proj.objects.resourcesBuildPhases.count, 2) - XCTAssertEqual(proj.objects.frameworksBuildPhases.count, 2) - XCTAssertEqual(proj.objects.headersBuildPhases.count, 1) - XCTAssertEqual(proj.objects.nativeTargets.count, 2) - XCTAssertEqual(proj.objects.fileReferences.count, 17) - XCTAssertEqual(proj.objects.buildRules.count, 1) - XCTAssertEqual(proj.objects.versionGroups.count, 1) - XCTAssertEqual(proj.objects.projects.count, 1) - XCTAssertEqual(proj.objects.swiftPackageProductDependencies.count, 2) - XCTAssertEqual(proj.objects.remoteSwiftPackageReferences.count, 1) + private func assert(proj: PBXProj) { + XCTAssertEqual(proj.archiveVersion, 1) + XCTAssertEqual(proj.objectVersion, 52) + XCTAssertEqual(proj.classes.count, 0) + XCTAssertEqual(proj.objects.buildFiles.count, 13) + XCTAssertEqual(proj.objects.aggregateTargets.count, 0) + XCTAssertEqual(proj.objects.containerItemProxies.count, 1) + XCTAssertEqual(proj.objects.copyFilesBuildPhases.count, 1) + XCTAssertEqual(proj.objects.groups.count, 6) + XCTAssertEqual(proj.objects.configurationLists.count, 3) + XCTAssertEqual(proj.objects.buildConfigurations.count, 6) + XCTAssertEqual(proj.objects.variantGroups.count, 2) + XCTAssertEqual(proj.objects.targetDependencies.count, 1) + XCTAssertEqual(proj.objects.sourcesBuildPhases.count, 2) + XCTAssertEqual(proj.objects.shellScriptBuildPhases.count, 1) + XCTAssertEqual(proj.objects.resourcesBuildPhases.count, 2) + XCTAssertEqual(proj.objects.frameworksBuildPhases.count, 2) + XCTAssertEqual(proj.objects.headersBuildPhases.count, 1) + XCTAssertEqual(proj.objects.nativeTargets.count, 2) + XCTAssertEqual(proj.objects.fileReferences.count, 17) + XCTAssertEqual(proj.objects.buildRules.count, 1) + XCTAssertEqual(proj.objects.versionGroups.count, 1) + XCTAssertEqual(proj.objects.projects.count, 1) + XCTAssertEqual(proj.objects.swiftPackageProductDependencies.count, 2) + XCTAssertEqual(proj.objects.remoteSwiftPackageReferences.count, 1) + } } -} + +#endif diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift index ba48a65f1..4d767bd15 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift @@ -18,7 +18,40 @@ final class PBXProjectTests: XCTestCase { attributes: ["LastUpgradeCheck": "0940"], targetAttributes: [target: ["TestTargetID": "123"]]) - project.setTargetAttributes(["custom": "abc", "TestTargetID": testTarget], target: target) + project.setTargetAttributes(["custom": "abc", "TestTargetID": .targetReference(testTarget)], target: target) + + let plist = try project.plistKeyAndValue(proj: PBXProj(), reference: "") + let attributes = plist.value.dictionary?["attributes"]?.dictionary ?? [:] + + let expectedAttributes: [CommentedString: PlistValue] = [ + "LastUpgradeCheck": "0940", + "TargetAttributes": ["app": [ + "custom": "abc", + "TestTargetID": "test", + ]], + ] + XCTAssertEqual(attributes, expectedAttributes) + } + + func test_attributes_writes_fixed_value_correctly() throws { + let target = PBXTarget(name: "") + target.reference.fix("app") + + let testTarget = PBXTarget(name: "") + + let project = PBXProject(name: "", + buildConfigurationList: XCConfigurationList(), + compatibilityVersion: "", + preferredProjectObjectVersion: nil, + minimizedProjectReferenceProxies: nil, + mainGroup: PBXGroup(), + attributes: ["LastUpgradeCheck": "0940"], + targetAttributes: [target: ["TestTargetID": "123"]]) + + project.setTargetAttributes(["custom": "abc", "TestTargetID": .targetReference(testTarget)], target: target) + + // When writing the project we need to account for any mutation of the object that may have occurred after being added to the project. + testTarget.reference.fix("test") let plist = try project.plistKeyAndValue(proj: PBXProj(), reference: "") let attributes = plist.value.dictionary?["attributes"]?.dictionary ?? [:] @@ -48,7 +81,7 @@ final class PBXProjectTests: XCTestCase { minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) - project.setTargetAttributes(["custom": "abc", "TestTargetID": testTarget], target: target) + project.setTargetAttributes(["custom": "abc", "TestTargetID": .targetReference(testTarget)], target: target) // When let plist = try project.plistKeyAndValue(proj: PBXProj(), reference: "") diff --git a/Tests/XcodeProjTests/Project/XCBreakpointListTests.swift b/Tests/XcodeProjTests/Project/XCBreakpointListTests.swift index 1d1da4d8b..5def85b4e 100644 --- a/Tests/XcodeProjTests/Project/XCBreakpointListTests.swift +++ b/Tests/XcodeProjTests/Project/XCBreakpointListTests.swift @@ -17,11 +17,11 @@ final class XCBreakpointListIntegrationTests: XCTestCase { } } - func test_write() { - testWrite(from: fixturePath(), - initModel: { try? XCBreakpointList(path: $0) }, - modify: { $0 }, - assertion: { assert(breakpointList: $1) }) + func test_write() throws { + try testWrite(from: fixturePath(), + initModel: { try? XCBreakpointList(path: $0) }, + modify: { $0 }, + assertion: { assert(breakpointList: $1) }) } // MARK: - Private diff --git a/Tests/XcodeProjTests/Project/XCUserDataTests.swift b/Tests/XcodeProjTests/Project/XCUserDataTests.swift index 1918584f1..36ab2db26 100644 --- a/Tests/XcodeProjTests/Project/XCUserDataTests.swift +++ b/Tests/XcodeProjTests/Project/XCUserDataTests.swift @@ -9,17 +9,17 @@ final class XCUserDataTests: XCTestCase { assert(userData: subject, userName: "username1") } - func test_write_userData() { - testWrite(from: userDataPath, - initModel: { try? XCUserData(path: $0) }, - modify: { userData in - // XCScheme's that are already in place (the removed element) should not be removed by a write - userData.schemes = userData.schemes.filter { $0.name != "iOS-other" } - return userData - }, - assertion: { - assert(userData: $1, userName: "copy") - }) + func test_write_userData() throws { + try testWrite(from: userDataPath, + initModel: { try? XCUserData(path: $0) }, + modify: { userData in + // XCScheme's that are already in place (the removed element) should not be removed by a write + userData.schemes = userData.schemes.filter { $0.name != "iOS-other" } + return userData + }, + assertion: { + assert(userData: $1, userName: "copy") + }) } func test_read_write_produces_no_diff() throws { diff --git a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift index 374515c8b..f3bd72571 100644 --- a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift @@ -1,111 +1,114 @@ -import Foundation -import PathKit -import XCTest -@testable import XcodeProj - -final class XcodeProjIntegrationTests: XCTestCase { - func test_write_xcode16Project() throws { - try testReadWriteProducesNoDiff(from: xcode16ProjectPath, - initModel: XcodeProj.init(path:)) +#if os(macOS) || (os(Linux) && compiler(>=6.1)) + import Foundation + import PathKit + import XCTest + @testable import XcodeProj + + final class XcodeProjIntegrationTests: XCTestCase { + func test_write_xcode16Project() throws { + try testReadWriteProducesNoDiff(from: xcode16ProjectPath, + initModel: XcodeProj.init(path:)) + } + + func test_read_iosXcodeProj() throws { + let subject = try XcodeProj(path: iosProjectPath) + assert(project: subject) + } + + func test_write_iosXcodeProj() throws { + try testWrite(from: iosProjectPath, + initModel: { try? XcodeProj(path: $0) }, + modify: { project in + // XCUserData that is already in place (the removed element) should not be removed by a write + _ = project.userData.removeLast() + return project + }, + assertion: { assert(project: $1) }) + } + + func test_read_write_produces_no_diff() throws { + try testReadWriteProducesNoDiff(from: iosProjectPath, + 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" + let pbxprojFromDisk = try PBXProj(path: pbxprojPath) + let pbxprojData = try Data(contentsOf: pbxprojPath.url) + + // When + let pbxprojFromData = try PBXProj(data: pbxprojData) + try pbxprojFromData.updateProjectName(path: pbxprojPath) + + // Then + XCTAssertEqual(pbxprojFromData, pbxprojFromDisk) + } + + func test_write_includes_workspace_settings() throws { + // Define workspace settings that should be written + let workspaceSettings = WorkspaceSettings(buildSystem: .new, derivedDataLocationStyle: .default, autoCreateSchemes: false) + + try testWrite(from: iosProjectPath, + initModel: { try? XcodeProj(path: $0) }, + modify: { project in + project.sharedData?.workspaceSettings = workspaceSettings + return project + }, + assertion: { + /** + * Expect that the workspace settings read from file are equal to the + * workspace settings we expected to write. + */ + XCTAssertEqual($1.sharedData?.workspaceSettings, workspaceSettings) + }) + } + + // MARK: - Private + + private func assert(project: XcodeProj) { + // Workspace + XCTAssertEqual(project.workspace.data.children.count, 1) + + // Project + XCTAssertEqual(project.pbxproj.objects.buildFiles.count, 13) + + // Shared Data + XCTAssertNotNil(project.sharedData) + XCTAssertEqual(project.sharedData?.schemes.count, 1) + XCTAssertNotNil(project.sharedData?.breakpoints) + XCTAssertNil(project.sharedData?.workspaceSettings) + + // User Data + XCTAssertEqual(project.userData.count, 3) + + XCTAssertEqual(project.userData[0].userName, "username1") + XCTAssertEqual(project.userData[0].schemes.count, 3) + XCTAssertEqual(project.userData[0].breakpoints?.breakpoints.count, 2) + XCTAssertNotNil(project.userData[0].schemeManagement) + + XCTAssertEqual(project.userData[1].userName, "username2") + XCTAssertEqual(project.userData[1].schemes.count, 1) + XCTAssertNil(project.userData[1].breakpoints?.breakpoints) + XCTAssertNil(project.userData[1].schemeManagement) + } + + private var iosProjectPath: Path { + fixturesPath() + "iOS/Project.xcodeproj" + } + + private var xcode16ProjectPath: Path { + fixturesPath() + "Xcode16/Test.xcodeproj" + } + + private var synchronizedRootGroupsFixturePath: Path { + fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj" + } } - func test_read_iosXcodeProj() throws { - let subject = try XcodeProj(path: iosProjectPath) - assert(project: subject) - } - - func test_write_iosXcodeProj() { - testWrite(from: iosProjectPath, - initModel: { try? XcodeProj(path: $0) }, - modify: { project in - // XCUserData that is already in place (the removed element) should not be removed by a write - _ = project.userData.removeLast() - return project - }, - assertion: { assert(project: $1) }) - } - - func test_read_write_produces_no_diff() throws { - try testReadWriteProducesNoDiff(from: iosProjectPath, - 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" - let pbxprojFromDisk = try PBXProj(path: pbxprojPath) - let pbxprojData = try Data(contentsOf: pbxprojPath.url) - - // When - let pbxprojFromData = try PBXProj(data: pbxprojData) - try pbxprojFromData.updateProjectName(path: pbxprojPath) - - // Then - XCTAssertEqual(pbxprojFromData, pbxprojFromDisk) - } - - func test_write_includes_workspace_settings() throws { - // Define workspace settings that should be written - let workspaceSettings = WorkspaceSettings(buildSystem: .new, derivedDataLocationStyle: .default, autoCreateSchemes: false) - - testWrite(from: iosProjectPath, - initModel: { try? XcodeProj(path: $0) }, - modify: { project in - project.sharedData?.workspaceSettings = workspaceSettings - return project - }, - assertion: { - /** - * Expect that the workspace settings read from file are equal to the - * workspace settings we expected to write. - */ - XCTAssertEqual($1.sharedData?.workspaceSettings, workspaceSettings) - }) - } - - // MARK: - Private - - private func assert(project: XcodeProj) { - // Workspace - XCTAssertEqual(project.workspace.data.children.count, 1) - - // Project - XCTAssertEqual(project.pbxproj.objects.buildFiles.count, 13) - - // Shared Data - XCTAssertNotNil(project.sharedData) - XCTAssertEqual(project.sharedData?.schemes.count, 1) - XCTAssertNotNil(project.sharedData?.breakpoints) - XCTAssertNil(project.sharedData?.workspaceSettings) - - // User Data - XCTAssertEqual(project.userData.count, 3) - - XCTAssertEqual(project.userData[0].userName, "username1") - XCTAssertEqual(project.userData[0].schemes.count, 3) - XCTAssertEqual(project.userData[0].breakpoints?.breakpoints.count, 2) - XCTAssertNotNil(project.userData[0].schemeManagement) - - XCTAssertEqual(project.userData[1].userName, "username2") - XCTAssertEqual(project.userData[1].schemes.count, 1) - XCTAssertNil(project.userData[1].breakpoints?.breakpoints) - XCTAssertNil(project.userData[1].schemeManagement) - } - - private var iosProjectPath: Path { - fixturesPath() + "iOS/Project.xcodeproj" - } - - private var xcode16ProjectPath: Path { - fixturesPath() + "Xcode16/Test.xcodeproj" - } - - private var synchronizedRootGroupsFixturePath: Path { - fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj" - } -} +#endif diff --git a/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift b/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift index 9d57bf8b1..70058b778 100644 --- a/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift +++ b/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift @@ -9,11 +9,11 @@ final class XCSchemeIntegrationTests: XCTestCase { assert(scheme: subject) } - func test_write_iosScheme() { - testWrite(from: iosSchemePath, - initModel: { try? XCScheme(path: $0) }, - modify: { $0 }, - assertion: { assert(scheme: $1) }) + func test_write_iosScheme() throws { + try testWrite(from: iosSchemePath, + initModel: { try? XCScheme(path: $0) }, + modify: { $0 }, + assertion: { assert(scheme: $1) }) } func test_read_write_produces_no_diff() throws { @@ -42,11 +42,11 @@ final class XCSchemeIntegrationTests: XCTestCase { XCTAssertEqual(remoteRunnable.remotePath, "/var/containers/Bundle/Application/018F0933-05E8-4359-9955-39E0523C4246/Ava.app") } - func test_write_runnableWithoutBuildableReferenceScheme() { - testWrite(from: runnableWithoutBuildableReferenceSchemePath, - initModel: { try? XCScheme(path: $0) }, - modify: { $0 }, - assertion: { assert(runnableWithoutBuildableReferenceScheme: $1) }) + func test_write_runnableWithoutBuildableReferenceScheme() throws { + try testWrite(from: runnableWithoutBuildableReferenceSchemePath, + initModel: { try? XCScheme(path: $0) }, + modify: { $0 }, + assertion: { assert(runnableWithoutBuildableReferenceScheme: $1) }) } func test_read_minimalScheme() { @@ -58,11 +58,11 @@ final class XCSchemeIntegrationTests: XCTestCase { } } - func test_write_minimalScheme() { - testWrite(from: minimalSchemePath, - initModel: { try? XCScheme(path: $0) }, - modify: { $0 }, - assertion: { assert(minimalScheme: $1) }) + func test_write_minimalScheme() throws { + try testWrite(from: minimalSchemePath, + initModel: { try? XCScheme(path: $0) }, + modify: { $0 }, + assertion: { assert(minimalScheme: $1) }) } func test_write_testableReferenceDefaultAttributesValuesAreOmitted() { @@ -85,7 +85,7 @@ final class XCSchemeIntegrationTests: XCTestCase { XCTAssertNil(subject.attributes["useTestSelectionWhitelist"]) } - func test_write_testableReferenceAttributesValues_allParallelizable() { + func test_write_testableReferenceAttributesValues_allParallelization() { let reference = XCScheme.TestableReference( skipped: false, parallelization: .all, @@ -107,7 +107,7 @@ final class XCSchemeIntegrationTests: XCTestCase { XCTAssertEqual(subject.attributes["testExecutionOrdering"], "random") } - func test_write_testableReferenceAttributesValues_noneParallelizable() { + func test_write_testableReferenceAttributesValues_noneParallelization() { let reference = XCScheme.TestableReference( skipped: false, parallelization: .none, @@ -129,10 +129,10 @@ final class XCSchemeIntegrationTests: XCTestCase { XCTAssertEqual(subject.attributes["testExecutionOrdering"], "random") } - func test_write_testableReferenceAttributesValues_trueParallelizable() { + func test_write_testableReferenceAttributesValues_swiftTestingOnlyParallelization() { let reference = XCScheme.TestableReference( skipped: false, - parallelizable: true, + parallelization: .swiftTestingOnly, randomExecutionOrdering: true, buildableReference: XCScheme.BuildableReference( referencedContainer: "", @@ -151,32 +151,10 @@ final class XCSchemeIntegrationTests: XCTestCase { XCTAssertEqual(subject.attributes["testExecutionOrdering"], "random") } - func test_write_testableReferenceAttributesValues_falseParallelizable() { - let reference = XCScheme.TestableReference( - skipped: false, - parallelizable: false, - randomExecutionOrdering: true, - buildableReference: XCScheme.BuildableReference( - referencedContainer: "", - blueprint: PBXObject(), - buildableName: "", - blueprintName: "" - ), - skippedTests: [], - selectedTests: [], - useTestSelectionWhitelist: true - ) - let subject = reference.xmlElement() - XCTAssertEqual(subject.attributes["skipped"], "NO") - XCTAssertEqual(subject.attributes["parallelizable"], "NO") - XCTAssertEqual(subject.attributes["useTestSelectionWhitelist"], "YES") - XCTAssertEqual(subject.attributes["testExecutionOrdering"], "random") - } - - func test_computed_parallelizable_testableReference_false() { + func test_computed_parallelization_testableReference_none() { let reference = XCScheme.TestableReference( skipped: false, - parallelizable: false, + parallelization: .none, randomExecutionOrdering: true, buildableReference: XCScheme.BuildableReference( referencedContainer: "", @@ -189,30 +167,9 @@ final class XCSchemeIntegrationTests: XCTestCase { useTestSelectionWhitelist: true ) - XCTAssertEqual(reference.parallelizable, false) XCTAssertEqual(reference.parallelization, .none) } - func test_computed_parallelizable_testableReference_true() { - let reference = XCScheme.TestableReference( - skipped: false, - parallelizable: true, - randomExecutionOrdering: true, - buildableReference: XCScheme.BuildableReference( - referencedContainer: "", - blueprint: PBXObject(), - buildableName: "", - blueprintName: "" - ), - skippedTests: [], - selectedTests: [], - useTestSelectionWhitelist: true - ) - - XCTAssertEqual(reference.parallelizable, true) - XCTAssertEqual(reference.parallelization, .swiftTestingOnly) - } - func test_write_testableReferenceSelectedTests() { // Given let reference = XCScheme.TestableReference( @@ -503,7 +460,6 @@ final class XCSchemeIntegrationTests: XCTestCase { XCTAssertEqual(scheme.testAction?.codeCoverageEnabled, true) XCTAssertEqual(scheme.testAction?.onlyGenerateCoverageForSpecifiedTargets, true) XCTAssertEqual(scheme.testAction?.testables.first?.skipped, false) - XCTAssertEqual(scheme.testAction?.testables.first?.parallelizable, true) XCTAssertEqual(scheme.testAction?.testables.first?.parallelization, .swiftTestingOnly) XCTAssertEqual(scheme.testAction?.testables.first?.randomExecutionOrdering, false) XCTAssertEqual(scheme.testAction?.testables.first?.useTestSelectionWhitelist, false) diff --git a/Tests/XcodeProjTests/Tests/Fixtures.swift b/Tests/XcodeProjTests/Tests/Fixtures.swift index 24deecb17..7fbb031de 100644 --- a/Tests/XcodeProjTests/Tests/Fixtures.swift +++ b/Tests/XcodeProjTests/Tests/Fixtures.swift @@ -3,7 +3,7 @@ import PathKit @testable import XcodeProj func fixturesPath() -> Path { - Path(#file).parent().parent().parent().parent() + "Fixtures" + Path(#filePath).parent().parent().parent().parent() + "Fixtures" } func synchronizedRootGroupsFixture() throws -> Data { diff --git a/Tests/XcodeProjTests/Tests/testWrite.swift b/Tests/XcodeProjTests/Tests/testWrite.swift index 44d719d58..5362af16d 100644 --- a/Tests/XcodeProjTests/Tests/testWrite.swift +++ b/Tests/XcodeProjTests/Tests/testWrite.swift @@ -6,29 +6,29 @@ import XCTest func testWrite(file _: StaticString = #file, line _: UInt = #line, from path: Path, - initModel: (Path) -> T?, - modify: (T) -> T) + initModel: (Path) throws -> T?, + modify: (T) -> T) throws { - testWrite(from: path, initModel: initModel, modify: modify, assertion: { XCTAssertEqual($0, $1) }) + try testWrite(from: path, initModel: initModel, modify: modify, assertion: { XCTAssertEqual($0, $1) }) } -func testWrite(file: StaticString = #file, +func testWrite(file: StaticString = #filePath, line: UInt = #line, from path: Path, - initModel: (Path) -> T?, + initModel: (Path) throws -> T?, modify: (T) -> T, - assertion: (_ before: T, _ after: T) -> Void) + assertion: (_ before: T, _ after: T) -> Void) throws { let copyPath = path.parent() + "copy.\(path.extension!)" try? copyPath.delete() try? path.copy(copyPath) - let got = initModel(copyPath) + let got = try initModel(copyPath) XCTAssertNotNil(got, file: file, line: line) if let got { let modified = modify(got) do { try modified.write(path: copyPath, override: true) - let gotAfterWriting = initModel(copyPath) + let gotAfterWriting = try initModel(copyPath) XCTAssertNotNil(gotAfterWriting, file: file, line: line) if let gotAfterWriting { assertion(got, gotAfterWriting) diff --git a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift index 7630c5f10..747bbb375 100644 --- a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift +++ b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift @@ -9,9 +9,7 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .application, swift: true) - - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -23,7 +21,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_iosFramework() { @@ -32,9 +33,7 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .framework, swift: true) - - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "", "CURRENT_PROJECT_VERSION": "1", "DEFINES_MODULE": "YES", @@ -55,7 +54,10 @@ class BuildSettingProviderTests: XCTestCase { "TARGETED_DEVICE_FAMILY": "1,2", "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_iosExtension() { @@ -64,9 +66,7 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .appExtension, swift: true) - - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", @@ -77,7 +77,9 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2", - ]) + ] + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_macOSAplication() { @@ -87,8 +89,7 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", "CODE_SIGN_IDENTITY": "-", @@ -100,7 +101,9 @@ class BuildSettingProviderTests: XCTestCase { "SDKROOT": "macosx", "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - ]) + ] + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_tvOSAplication() { @@ -110,8 +113,7 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", "ENABLE_PREVIEWS": "YES", "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "LaunchImage", @@ -123,7 +125,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "3", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_watchOSAplication() { @@ -133,8 +138,7 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", "LD_RUNPATH_SEARCH_PATHS": ["$(inherited)", "@executable_path/Frameworks"], @@ -143,7 +147,9 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_watchOSFramework() { @@ -154,7 +160,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "APPLICATION_EXTENSION_API_ONLY": "YES", "CODE_SIGN_IDENTITY": "", "CURRENT_PROJECT_VERSION": "1", @@ -176,7 +182,9 @@ class BuildSettingProviderTests: XCTestCase { "TARGETED_DEVICE_FAMILY": "4", "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_watchOSExtension() { @@ -187,7 +195,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", "@executable_path/Frameworks", @@ -198,7 +206,9 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_visionOSAplication() { @@ -209,7 +219,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", @@ -221,7 +231,9 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_visionOSFramework() { @@ -232,7 +244,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "", "CURRENT_PROJECT_VERSION": "1", "DEFINES_MODULE": "YES", @@ -253,7 +265,9 @@ class BuildSettingProviderTests: XCTestCase { "TARGETED_DEVICE_FAMILY": "1,2,7", "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_visionOSExtension() { @@ -264,7 +278,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", @@ -275,7 +289,9 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_iOSUnitTests() { @@ -285,8 +301,7 @@ class BuildSettingProviderTests: XCTestCase { product: .unitTests, swift: true) - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "iphoneos", "LD_RUNPATH_SEARCH_PATHS": [ @@ -298,7 +313,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_iOSUITests() { @@ -307,9 +325,7 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .uiTests, swift: true) - - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "iphoneos", "LD_RUNPATH_SEARCH_PATHS": [ @@ -321,7 +337,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_macOSUnitTests() { @@ -331,8 +350,7 @@ class BuildSettingProviderTests: XCTestCase { product: .unitTests, swift: true) - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "-", "SDKROOT": "macosx", "LD_RUNPATH_SEARCH_PATHS": [ @@ -343,7 +361,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_tvOSUnitTests() { @@ -352,9 +373,7 @@ class BuildSettingProviderTests: XCTestCase { platform: .tvOS, product: .unitTests, swift: true) - - // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "SDKROOT": "appletvos", "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", @@ -365,7 +384,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "3", - ]) + ] + + // Then + XCTAssertEqual(results, expected) } func test_targetSettings_visionOSUnitTests() { @@ -376,7 +398,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "xros", "LD_RUNPATH_SEARCH_PATHS": [ @@ -388,7 +410,9 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", - ]) + ] + + XCTAssertEqual(results, expected) } func test_targetSettings_visionOSUITests() { @@ -399,7 +423,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "xros", "LD_RUNPATH_SEARCH_PATHS": [ @@ -411,15 +435,8 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", - ]) - } - - // MARK: - Helpers + ] - func assertEqualSettings(_ lhs: BuildSettings, _ rhs: BuildSettings, file: StaticString = #file, line: UInt = #line) { - XCTAssertEqual(lhs as NSDictionary, - rhs as NSDictionary, - file: file, - line: line) + XCTAssertEqual(results, expected) } } diff --git a/Tests/XcodeProjTests/Utils/XCConfigTests.swift b/Tests/XcodeProjTests/Utils/XCConfigTests.swift index afb249cd4..a23cca1ee 100644 --- a/Tests/XcodeProjTests/Utils/XCConfigTests.swift +++ b/Tests/XcodeProjTests/Utils/XCConfigTests.swift @@ -14,7 +14,7 @@ final class XCConfigTests: XCTestCase { (Path("testB"), configB), ], buildSettings: ["a": "b"]) - XCTAssertEqual(config.buildSettings as! [String: String], ["a": "b"]) + XCTAssertEqual(config.buildSettings, ["a": "b"]) XCTAssertEqual(config.includes[0].config, configA) XCTAssertEqual(config.includes[1].config, configB) } @@ -28,8 +28,8 @@ final class XCConfigTests: XCTestCase { ], buildSettings: ["b": "3"]) let buildSettings = config.flattenedBuildSettings() - XCTAssertEqual(buildSettings["a"] as? String, "2") - XCTAssertEqual(buildSettings["b"] as? String, "3") + XCTAssertEqual(buildSettings["a"], "2") + XCTAssertEqual(buildSettings["b"], "3") } func test_xcconfig_settingRegex() { @@ -86,10 +86,10 @@ final class XCConfigIntegrationTests: XCTestCase { } } - func test_write_writesTheContentProperly() { - testWrite(from: childrenPath(), - initModel: { try? XCConfig(path: $0) }, - modify: { $0 }) + func test_write_writesTheContentProperly() throws { + try testWrite(from: childrenPath(), + initModel: { try? XCConfig(path: $0) }, + modify: { $0 }) } private func childrenPath() -> Path { @@ -101,16 +101,16 @@ final class XCConfigIntegrationTests: XCTestCase { } private func assert(config: XCConfig) { - XCTAssertEqual(config.buildSettings["CONFIGURATION_BUILD_DIR"] as? String, "Test/") - XCTAssertEqual(config.flattenedBuildSettings()["CONFIGURATION_BUILD_DIR"] as? String, "Test/") - XCTAssertEqual(config.buildSettings["GCC_PREPROCESSOR_DEFINITIONS"] as? String, "$(inherited)") - XCTAssertEqual(config.flattenedBuildSettings()["GCC_PREPROCESSOR_DEFINITIONS"] as? String, "$(inherited)") - XCTAssertEqual(config.buildSettings["WARNING_CFLAGS"] as? String, "-Wall -Wno-direct-ivar-access -Wno-objc-missing-property-synthesis -Wno-readonly-iboutlet-property -Wno-switch-enum -Wno-padded") - XCTAssertEqual(config.flattenedBuildSettings()["WARNING_CFLAGS"] as? String, "-Wall -Wno-direct-ivar-access -Wno-objc-missing-property-synthesis -Wno-readonly-iboutlet-property -Wno-switch-enum -Wno-padded") + XCTAssertEqual(config.buildSettings["CONFIGURATION_BUILD_DIR"], "Test/") + XCTAssertEqual(config.flattenedBuildSettings()["CONFIGURATION_BUILD_DIR"], "Test/") + XCTAssertEqual(config.buildSettings["GCC_PREPROCESSOR_DEFINITIONS"], "$(inherited)") + XCTAssertEqual(config.flattenedBuildSettings()["GCC_PREPROCESSOR_DEFINITIONS"], "$(inherited)") + XCTAssertEqual(config.buildSettings["WARNING_CFLAGS"], "-Wall -Wno-direct-ivar-access -Wno-objc-missing-property-synthesis -Wno-readonly-iboutlet-property -Wno-switch-enum -Wno-padded") + XCTAssertEqual(config.flattenedBuildSettings()["WARNING_CFLAGS"], "-Wall -Wno-direct-ivar-access -Wno-objc-missing-property-synthesis -Wno-readonly-iboutlet-property -Wno-switch-enum -Wno-padded") XCTAssertEqual(config.includes.count, 1) - XCTAssertEqual(config.flattenedBuildSettings()["OTHER_SWIFT_FLAGS_XCODE_0821"] as? String, "$(inherited)") - XCTAssertEqual(config.flattenedBuildSettings()["OTHER_SWIFT_FLAGS_XCODE_0830"] as? String, "$(inherited) -enable-bridging-pch") - XCTAssertEqual(config.flattenedBuildSettings()["PRODUCT_NAME"] as? String, "$(TARGET_NAME)") - XCTAssertEqual(config.flattenedBuildSettings()["SWIFT_OPTIMIZATION_LEVEL"] as? String, "-Onone") + XCTAssertEqual(config.flattenedBuildSettings()["OTHER_SWIFT_FLAGS_XCODE_0821"], "$(inherited)") + XCTAssertEqual(config.flattenedBuildSettings()["OTHER_SWIFT_FLAGS_XCODE_0830"], "$(inherited) -enable-bridging-pch") + XCTAssertEqual(config.flattenedBuildSettings()["PRODUCT_NAME"], "$(TARGET_NAME)") + XCTAssertEqual(config.flattenedBuildSettings()["SWIFT_OPTIMIZATION_LEVEL"], "-Onone") } } diff --git a/Tests/XcodeProjTests/Workspace/XCWorkspaceDataTests.swift b/Tests/XcodeProjTests/Workspace/XCWorkspaceDataTests.swift index 420ea7ed2..d3076ca0b 100644 --- a/Tests/XcodeProjTests/Workspace/XCWorkspaceDataTests.swift +++ b/Tests/XcodeProjTests/Workspace/XCWorkspaceDataTests.swift @@ -39,8 +39,8 @@ final class XCWorkspaceDataIntegrationTests: XCTestCase { } catch {} } - func test_write() { - testWrite( + func test_write() throws { + try testWrite( from: fixturePath(), initModel: { try? XCWorkspaceData(path: $0) }, modify: {