From e52015ac6075101c265ede8a119fdf7290c3f517 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 20 Jul 2024 16:02:06 -0400 Subject: [PATCH 01/41] Introduce `BuildSetting` enum to enable strongly typed setting serialization --- .../Objects/Configuration/BuildSettings.swift | 39 +++++++++++++++++++ .../Configuration/BuildSettingTests.swift | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 066dbb50d..9599770af 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -2,3 +2,42 @@ import Foundation /// Build settings. public typealias BuildSettings = [String: Any] +public enum BuildSetting: Sendable, Equatable { + case string(String) + case array([String]) +} + +extension BuildSetting: 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 .string(let string): + try container.encode(string) + case .array(let array): + try container.encode(array) + } + } +} + +extension BuildSetting: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension BuildSetting: ExpressibleByStringLiteral { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift new file mode 100644 index 000000000..3d9ac09d9 --- /dev/null +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift @@ -0,0 +1,39 @@ +// +// BuildSettingTests.swift +// XcodeProj +// +// Created by Michael Simons on 7/20/24. +// + +import XCTest +@testable import XcodeProj + +final class BuildSettingTests: XCTestCase { + + func test_BuildSettings_Encode_to_JSON() throws { + let expectedJSON = #"{"one":"one","two":["two","two"]}"# + + let settings: BuildSettings = [ + "one": .string("one"), + "two": .array(["two", "two"]) + ] + + let result = try JSONEncoder().encode(settings) + + XCTAssertEqual(result, expectedJSON.data(using: .utf8)) + } + + func test_buildSettings_decodes_from_JSON() 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)!) + + XCTAssertEqual(result, expectedSettings) + } + +} From dc0187ca3d0e545d205be521aa47eb50c9f25f20 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 20 Jul 2024 16:12:09 -0400 Subject: [PATCH 02/41] Migrate `BuildSettings` to use `BuildSetting` enum. --- .../Objects/Configuration/BuildSettings.swift | 55 +-- .../Configuration/XCBuildConfiguration.swift | 48 +-- .../Utils/BuildSettingsProvider.swift | 2 +- Sources/XcodeProj/Utils/XCConfig.swift | 4 +- .../Utils/BuildSettingsProviderTests.swift | 321 ++++++++++-------- 5 files changed, 228 insertions(+), 202 deletions(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 9599770af..fc74a7ab5 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -1,43 +1,44 @@ import Foundation /// Build settings. -public typealias BuildSettings = [String: Any] +public typealias BuildSettings = [String: BuildSetting] + public enum BuildSetting: Sendable, Equatable { - case string(String) - case array([String]) + case string(String) + case array([String]) } extension BuildSetting: 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 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 .string(let string): - try container.encode(string) - case .array(let array): - try container.encode(array) + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let string): + try container.encode(string) + case .array(let array): + try container.encode(array) + } } - } } extension BuildSetting: ExpressibleByArrayLiteral { - public init(arrayLiteral elements: String...) { - self = .array(elements) - } + public init(arrayLiteral elements: String...) { + self = .array(elements) + } } extension BuildSetting: ExpressibleByStringLiteral { - public init(stringLiteral value: StringLiteralType) { - self = .string(value) - } + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } } diff --git a/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift b/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift index 71e05c3dc..4fc328927 100644 --- a/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift +++ b/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift @@ -43,26 +43,26 @@ public final class XCBuildConfiguration: PBXObject { } // MARK: - Decodable - - fileprivate enum CodingKeys: String, CodingKey { - case baseConfigurationReference - case buildSettings - case name - } - - public required init(from decoder: Decoder) throws { - let objects = decoder.context.objects - let objectReferenceRepository = decoder.context.objectReferenceRepository - let container = try decoder.container(keyedBy: CodingKeys.self) - if let baseConfigurationReference: String = try container.decodeIfPresent(.baseConfigurationReference) { - self.baseConfigurationReference = objectReferenceRepository.getOrCreate(reference: baseConfigurationReference, objects: objects) - } else { - baseConfigurationReference = nil - } - buildSettings = try container.decode([String: Any].self, forKey: .buildSettings) - name = try container.decode(.name) - try super.init(from: decoder) + + fileprivate enum CodingKeys: String, CodingKey { + case baseConfigurationReference + case buildSettings + case name + } + + public required init(from decoder: Decoder) throws { + let objects = decoder.context.objects + let objectReferenceRepository = decoder.context.objectReferenceRepository + let container = try decoder.container(keyedBy: CodingKeys.self) + if let baseConfigurationReference: String = try container.decodeIfPresent(.baseConfigurationReference) { + self.baseConfigurationReference = objectReferenceRepository.getOrCreate(reference: baseConfigurationReference, objects: objects) + } else { + baseConfigurationReference = nil } + buildSettings = try container.decode(BuildSettings.self, forKey: .buildSettings) + name = try container.decode(.name) + try super.init(from: decoder) + } // MARK: - Public @@ -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/Utils/BuildSettingsProvider.swift b/Sources/XcodeProj/Utils/BuildSettingsProvider.swift index f348d2ad1..dee8b0570 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 }) diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift index e1e585ea6..b3bcb59ce 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 } } diff --git a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift index 7630c5f10..3837b5223 100644 --- a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift +++ b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift @@ -9,21 +9,22 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .application, swift: true) - + let expected: BuildSettings = [ + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", + "ENABLE_PREVIEWS": "YES", + "CODE_SIGN_IDENTITY": "iPhone Developer", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + ], + "SDKROOT": "iphoneos", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "1,2", + ] + // Then - assertEqualSettings(results, [ - "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", - "ENABLE_PREVIEWS": "YES", - "CODE_SIGN_IDENTITY": "iPhone Developer", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - ], - "SDKROOT": "iphoneos", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "1,2", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_iosFramework() { @@ -32,30 +33,31 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .framework, swift: true) - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "", + "CURRENT_PROJECT_VERSION": "1", + "DEFINES_MODULE": "YES", + "DYLIB_COMPATIBILITY_VERSION": "1", + "DYLIB_CURRENT_VERSION": "1", + "DYLIB_INSTALL_NAME_BASE": "@rpath", + "INSTALL_PATH": "$(LOCAL_LIBRARY_DIR)/Frameworks", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "PRODUCT_NAME": "$(TARGET_NAME:c99extidentifier)", + "SDKROOT": "iphoneos", + "SKIP_INSTALL": "YES", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "1,2", + "VERSIONING_SYSTEM": "apple-generic", + "VERSION_INFO_PREFIX": "", + ] + // Then - assertEqualSettings(results, [ - "CODE_SIGN_IDENTITY": "", - "CURRENT_PROJECT_VERSION": "1", - "DEFINES_MODULE": "YES", - "DYLIB_COMPATIBILITY_VERSION": "1", - "DYLIB_CURRENT_VERSION": "1", - "DYLIB_INSTALL_NAME_BASE": "@rpath", - "INSTALL_PATH": "$(LOCAL_LIBRARY_DIR)/Frameworks", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "PRODUCT_NAME": "$(TARGET_NAME:c99extidentifier)", - "SDKROOT": "iphoneos", - "SKIP_INSTALL": "YES", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "1,2", - "VERSIONING_SYSTEM": "apple-generic", - "VERSION_INFO_PREFIX": "", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_iosExtension() { @@ -64,20 +66,20 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .appExtension, swift: true) - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "iPhone Developer", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ], + "SDKROOT": "iphoneos", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "1,2", + ] // Then - assertEqualSettings(results, [ - "CODE_SIGN_IDENTITY": "iPhone Developer", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ], - "SDKROOT": "iphoneos", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "1,2", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_macOSAplication() { @@ -87,20 +89,21 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) + let expected: BuildSettings = [ + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", + "ENABLE_PREVIEWS": "YES", + "CODE_SIGN_IDENTITY": "-", + "COMBINE_HIDPI_IMAGES": "YES", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/../Frameworks", + ], + "SDKROOT": "macosx", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + ] // Then - assertEqualSettings(results, [ - "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", - "ENABLE_PREVIEWS": "YES", - "CODE_SIGN_IDENTITY": "-", - "COMBINE_HIDPI_IMAGES": "YES", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/../Frameworks", - ], - "SDKROOT": "macosx", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_tvOSAplication() { @@ -110,20 +113,22 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) + let expected: BuildSettings = [ + "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", + "ENABLE_PREVIEWS": "YES", + "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "LaunchImage", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + ], + "SDKROOT": "appletvos", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "3", + ] + // Then - assertEqualSettings(results, [ - "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", - "ENABLE_PREVIEWS": "YES", - "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "LaunchImage", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - ], - "SDKROOT": "appletvos", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "3", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_watchOSAplication() { @@ -132,9 +137,8 @@ class BuildSettingProviderTests: XCTestCase { platform: .watchOS, 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,9 +147,12 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "wholemodule", "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", - ]) + ] + + XCTAssertEqual(results, expected) + } - + func test_targetSettings_watchOSFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, @@ -154,7 +161,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 +183,10 @@ class BuildSettingProviderTests: XCTestCase { "TARGETED_DEVICE_FAMILY": "4", "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", - ]) + ] + + XCTAssertEqual(results, expected) + } func test_targetSettings_watchOSExtension() { @@ -187,7 +197,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", "@executable_path/Frameworks", @@ -198,7 +208,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 +221,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 +233,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 +246,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "", "CURRENT_PROJECT_VERSION": "1", "DEFINES_MODULE": "YES", @@ -253,7 +267,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 +280,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "LD_RUNPATH_SEARCH_PATHS": [ "$(inherited)", @@ -275,7 +291,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,20 +303,22 @@ class BuildSettingProviderTests: XCTestCase { product: .unitTests, swift: true) + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "iPhone Developer", + "SDKROOT": "iphoneos", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "TARGETED_DEVICE_FAMILY": "1,2", + ] + // Then - assertEqualSettings(results, [ - "CODE_SIGN_IDENTITY": "iPhone Developer", - "SDKROOT": "iphoneos", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "TARGETED_DEVICE_FAMILY": "1,2", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_iOSUITests() { @@ -307,21 +327,23 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .uiTests, swift: true) - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "iPhone Developer", + "SDKROOT": "iphoneos", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "TARGETED_DEVICE_FAMILY": "1,2", + ] + + // Then - assertEqualSettings(results, [ - "CODE_SIGN_IDENTITY": "iPhone Developer", - "SDKROOT": "iphoneos", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "TARGETED_DEVICE_FAMILY": "1,2", - ]) + XCTAssertEqual(results, expected ) } func test_targetSettings_macOSUnitTests() { @@ -331,19 +353,21 @@ class BuildSettingProviderTests: XCTestCase { product: .unitTests, swift: true) + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "-", + "SDKROOT": "macosx", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + ] + // Then - assertEqualSettings(results, [ - "CODE_SIGN_IDENTITY": "-", - "SDKROOT": "macosx", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_tvOSUnitTests() { @@ -352,20 +376,21 @@ class BuildSettingProviderTests: XCTestCase { platform: .tvOS, product: .unitTests, swift: true) + let expected: BuildSettings = [ + "SDKROOT": "appletvos", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "TARGETED_DEVICE_FAMILY": "3", + ] // Then - assertEqualSettings(results, [ - "SDKROOT": "appletvos", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "TARGETED_DEVICE_FAMILY": "3", - ]) + XCTAssertEqual(results, expected) } func test_targetSettings_visionOSUnitTests() { @@ -416,10 +441,10 @@ class BuildSettingProviderTests: XCTestCase { // 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) - } +// func XCTAssertEqual(_ lhs: BuildSettings, _ rhs: BuildSettings, file: StaticString = #file, line: UInt = #line) { +// XCTAssertEqual(lhs as NSDictionary, +// rhs as NSDictionary, +// file: file, +// line: line) +// } } From 80c6dee6e42be1f83bdb2f1cd457dbd2eef7b43b Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Mon, 17 Feb 2025 12:39:27 -0500 Subject: [PATCH 03/41] =?UTF-8?q?Remove=20unused=20method=20that=20was=20t?= =?UTF-8?q?he=20result=20of=20another=20PR=E2=80=99s=20rebase.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/XcodeProj/Utils/BuildSettingsProvider.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Sources/XcodeProj/Utils/BuildSettingsProvider.swift b/Sources/XcodeProj/Utils/BuildSettingsProvider.swift index dee8b0570..71a4d59ca 100644 --- a/Sources/XcodeProj/Utils/BuildSettingsProvider.swift +++ b/Sources/XcodeProj/Utils/BuildSettingsProvider.swift @@ -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 From 4c49b9ca48be3f708256502a18ec2d9369f341a6 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Wed, 24 Jul 2024 21:26:44 -0400 Subject: [PATCH 04/41] Fix equality on `XCBuildConfiguration` --- Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift index b8087736f..643fdda57 100644 --- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift +++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift @@ -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) } From 0d546298bf6cbe98900aaf72795e6568fb124ef5 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Mon, 22 Jul 2024 21:08:09 -0400 Subject: [PATCH 05/41] Update `PlistValue` to write `BuildSetting` correctly --- Sources/XcodeProj/Utils/PlistValue.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/XcodeProj/Utils/PlistValue.swift b/Sources/XcodeProj/Utils/PlistValue.swift index 92e2e8efe..4a90631af 100644 --- a/Sources/XcodeProj/Utils/PlistValue.swift +++ b/Sources/XcodeProj/Utils/PlistValue.swift @@ -85,6 +85,22 @@ extension PlistValue: Equatable { // MARK: - Dictionary Extension (PlistValue) + +extension Dictionary where Key == String, Value == BuildSetting { + func plist() -> PlistValue { + var dictionary: [CommentedString: PlistValue] = [:] + forEach { key, value in + switch value { + case .string(let stringValue): + dictionary[CommentedString(key)] = PlistValue.string(CommentedString(stringValue)) + case .array(let arrayValue): + dictionary[CommentedString(key)] = arrayValue.plist() + } + } + return .dictionary(dictionary) + } +} + extension Dictionary where Key == String { func plist() -> PlistValue { var dictionary: [CommentedString: PlistValue] = [:] From dd33958791ebde50171186dc3d95c75bc84ee993 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Mon, 22 Jul 2024 20:13:33 -0400 Subject: [PATCH 06/41] Fix Tests --- Sources/XcodeProj/Utils/XCConfig.swift | 4 +-- .../Configuration/BuildSettingTests.swift | 5 +++- .../XCBuildConfigurationTests.swift | 10 +++---- .../XcodeProjTests/Utils/XCConfigTests.swift | 26 +++++++++---------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift index b3bcb59ce..800308a6c 100644 --- a/Sources/XcodeProj/Utils/XCConfig.swift +++ b/Sources/XcodeProj/Utils/XCConfig.swift @@ -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/Objects/Configuration/BuildSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift index 3d9ac09d9..575f3b13d 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift @@ -18,7 +18,10 @@ final class BuildSettingTests: XCTestCase { "two": .array(["two", "two"]) ] - let result = try JSONEncoder().encode(settings) + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + let result = try encoder.encode(settings) XCTAssertEqual(result, expectedJSON.data(using: .utf8)) } 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/Utils/XCConfigTests.swift b/Tests/XcodeProjTests/Utils/XCConfigTests.swift index afb249cd4..9cd58ffd5 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() { @@ -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") } } From 091d6c49c89234eaf4e30427f468e3f8d9f43bc6 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Wed, 24 Jul 2024 21:11:22 -0400 Subject: [PATCH 07/41] Fix tests This was fun. `NSDictionary(buildSettings)` is not able to be compared because we wrap swift values so we must use `==` on the swift types since they are not concrete and equatable. --- .../XcodeProj/Objects/Configuration/BuildSettings.swift | 9 +++++++++ Sources/XcodeProj/Utils/XCConfig.swift | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index fc74a7ab5..5b626eabf 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -6,6 +6,15 @@ public typealias BuildSettings = [String: BuildSetting] public enum BuildSetting: Sendable, Equatable { case string(String) case array([String]) + + var valueForWriting: String { + switch self { + case .string(let string): + return string + case .array(let array): + return array.joined(separator: " ") + } + } } extension BuildSetting: Codable { diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift index 800308a6c..592e74344 100644 --- a/Sources/XcodeProj/Utils/XCConfig.swift +++ b/Sources/XcodeProj/Utils/XCConfig.swift @@ -114,7 +114,7 @@ extension XCConfig: Equatable { return false } } - return NSDictionary(dictionary: lhs.buildSettings).isEqual(to: rhs.buildSettings) + return lhs.buildSettings == rhs.buildSettings } } @@ -174,7 +174,7 @@ extension XCConfig: Writable { private func writeBuildSettings() -> String { var content = "" for (key, value) in buildSettings { - content.append("\(key) = \(value)\n") + content.append("\(key) = \(value.valueForWriting)\n") } content.append("\n") return content From 2dfe8f257467d72d49cb6b992199346a9164d1e5 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Mon, 17 Feb 2025 13:13:36 -0500 Subject: [PATCH 08/41] Fix tests --- .../Utils/BuildSettingsProviderTests.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift index 3837b5223..66e102344 100644 --- a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift +++ b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift @@ -401,7 +401,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "xros", "LD_RUNPATH_SEARCH_PATHS": [ @@ -413,7 +413,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", - ]) + ] + + XCTAssertEqual(results, expected) + } func test_targetSettings_visionOSUITests() { @@ -424,7 +427,7 @@ class BuildSettingProviderTests: XCTestCase { swift: true) // Then - assertEqualSettings(results, [ + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "xros", "LD_RUNPATH_SEARCH_PATHS": [ @@ -436,7 +439,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", - ]) + ] + + XCTAssertEqual(results, expected) + } // MARK: - Helpers From 9e2a6cb69dca91e5dd0d48b9ae1f7bb3203a7098 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Mon, 17 Feb 2025 13:27:59 -0500 Subject: [PATCH 09/41] Linting --- .../Objects/Configuration/BuildSettings.swift | 18 +- .../Configuration/XCBuildConfiguration.swift | 38 +-- Sources/XcodeProj/Utils/PlistValue.swift | 7 +- .../Configuration/BuildSettingTests.swift | 56 ++-- .../Utils/BuildSettingsProviderTests.swift | 283 +++++++++--------- 5 files changed, 197 insertions(+), 205 deletions(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 5b626eabf..d891c05c2 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -6,13 +6,13 @@ public typealias BuildSettings = [String: BuildSetting] public enum BuildSetting: Sendable, Equatable { case string(String) case array([String]) - + var valueForWriting: String { switch self { - case .string(let string): - return string - case .array(let array): - return array.joined(separator: " ") + case let .string(string): + string + case let .array(array): + array.joined(separator: " ") } } } @@ -20,7 +20,7 @@ public enum BuildSetting: Sendable, Equatable { extension BuildSetting: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() - do { + do { let string = try container.decode(String.self) self = .string(string) } catch { @@ -28,13 +28,13 @@ extension BuildSetting: Codable { self = .array(array) } } - + public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { - case .string(let string): + case let .string(string): try container.encode(string) - case .array(let array): + case let .array(array): try container.encode(array) } } diff --git a/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift b/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift index 4fc328927..159dc201c 100644 --- a/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift +++ b/Sources/XcodeProj/Objects/Configuration/XCBuildConfiguration.swift @@ -43,26 +43,26 @@ public final class XCBuildConfiguration: PBXObject { } // MARK: - Decodable - - fileprivate enum CodingKeys: String, CodingKey { - case baseConfigurationReference - case buildSettings - case name - } - - public required init(from decoder: Decoder) throws { - let objects = decoder.context.objects - let objectReferenceRepository = decoder.context.objectReferenceRepository - let container = try decoder.container(keyedBy: CodingKeys.self) - if let baseConfigurationReference: String = try container.decodeIfPresent(.baseConfigurationReference) { - self.baseConfigurationReference = objectReferenceRepository.getOrCreate(reference: baseConfigurationReference, objects: objects) - } else { - baseConfigurationReference = nil + + fileprivate enum CodingKeys: String, CodingKey { + case baseConfigurationReference + case buildSettings + case name + } + + public required init(from decoder: Decoder) throws { + let objects = decoder.context.objects + let objectReferenceRepository = decoder.context.objectReferenceRepository + let container = try decoder.container(keyedBy: CodingKeys.self) + if let baseConfigurationReference: String = try container.decodeIfPresent(.baseConfigurationReference) { + self.baseConfigurationReference = objectReferenceRepository.getOrCreate(reference: baseConfigurationReference, objects: objects) + } else { + baseConfigurationReference = nil + } + buildSettings = try container.decode(BuildSettings.self, forKey: .buildSettings) + name = try container.decode(.name) + try super.init(from: decoder) } - buildSettings = try container.decode(BuildSettings.self, forKey: .buildSettings) - name = try container.decode(.name) - try super.init(from: decoder) - } // MARK: - Public diff --git a/Sources/XcodeProj/Utils/PlistValue.swift b/Sources/XcodeProj/Utils/PlistValue.swift index 4a90631af..78251568c 100644 --- a/Sources/XcodeProj/Utils/PlistValue.swift +++ b/Sources/XcodeProj/Utils/PlistValue.swift @@ -85,15 +85,14 @@ extension PlistValue: Equatable { // MARK: - Dictionary Extension (PlistValue) - -extension Dictionary where Key == String, Value == BuildSetting { +extension [String: BuildSetting] { func plist() -> PlistValue { var dictionary: [CommentedString: PlistValue] = [:] forEach { key, value in switch value { - case .string(let stringValue): + case let .string(stringValue): dictionary[CommentedString(key)] = PlistValue.string(CommentedString(stringValue)) - case .array(let arrayValue): + case let .array(arrayValue): dictionary[CommentedString(key)] = arrayValue.plist() } } diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift index 575f3b13d..54744f678 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift @@ -9,34 +9,32 @@ import XCTest @testable import XcodeProj final class BuildSettingTests: XCTestCase { - - func test_BuildSettings_Encode_to_JSON() throws { - let expectedJSON = #"{"one":"one","two":["two","two"]}"# - - let settings: BuildSettings = [ - "one": .string("one"), - "two": .array(["two", "two"]) - ] - - let encoder = JSONEncoder() - encoder.outputFormatting = .sortedKeys - - let result = try encoder.encode(settings) - - XCTAssertEqual(result, expectedJSON.data(using: .utf8)) - } - - func test_buildSettings_decodes_from_JSON() 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)!) - - XCTAssertEqual(result, expectedSettings) - } + func test_BuildSettings_Encode_to_JSON() throws { + let expectedJSON = #"{"one":"one","two":["two","two"]}"# + let settings: BuildSettings = [ + "one": .string("one"), + "two": .array(["two", "two"]), + ] + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + let result = try encoder.encode(settings) + + XCTAssertEqual(result, expectedJSON.data(using: .utf8)) + } + + func test_buildSettings_decodes_from_JSON() 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)!) + + XCTAssertEqual(result, expectedSettings) + } } diff --git a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift index 66e102344..0dfaa5898 100644 --- a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift +++ b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift @@ -9,20 +9,20 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .application, swift: true) - let expected: BuildSettings = [ - "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", - "ENABLE_PREVIEWS": "YES", - "CODE_SIGN_IDENTITY": "iPhone Developer", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - ], - "SDKROOT": "iphoneos", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "1,2", - ] - + let expected: BuildSettings = [ + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", + "ENABLE_PREVIEWS": "YES", + "CODE_SIGN_IDENTITY": "iPhone Developer", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + ], + "SDKROOT": "iphoneos", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "1,2", + ] + // Then XCTAssertEqual(results, expected) } @@ -33,29 +33,29 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .framework, swift: true) - let expected: BuildSettings = [ - "CODE_SIGN_IDENTITY": "", - "CURRENT_PROJECT_VERSION": "1", - "DEFINES_MODULE": "YES", - "DYLIB_COMPATIBILITY_VERSION": "1", - "DYLIB_CURRENT_VERSION": "1", - "DYLIB_INSTALL_NAME_BASE": "@rpath", - "INSTALL_PATH": "$(LOCAL_LIBRARY_DIR)/Frameworks", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "PRODUCT_NAME": "$(TARGET_NAME:c99extidentifier)", - "SDKROOT": "iphoneos", - "SKIP_INSTALL": "YES", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "1,2", - "VERSIONING_SYSTEM": "apple-generic", - "VERSION_INFO_PREFIX": "", - ] - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "", + "CURRENT_PROJECT_VERSION": "1", + "DEFINES_MODULE": "YES", + "DYLIB_COMPATIBILITY_VERSION": "1", + "DYLIB_CURRENT_VERSION": "1", + "DYLIB_INSTALL_NAME_BASE": "@rpath", + "INSTALL_PATH": "$(LOCAL_LIBRARY_DIR)/Frameworks", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "PRODUCT_NAME": "$(TARGET_NAME:c99extidentifier)", + "SDKROOT": "iphoneos", + "SKIP_INSTALL": "YES", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "1,2", + "VERSIONING_SYSTEM": "apple-generic", + "VERSION_INFO_PREFIX": "", + ] + // Then XCTAssertEqual(results, expected) } @@ -66,18 +66,18 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .appExtension, swift: true) - let expected: BuildSettings = [ - "CODE_SIGN_IDENTITY": "iPhone Developer", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ], - "SDKROOT": "iphoneos", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "1,2", - ] + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "iPhone Developer", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ], + "SDKROOT": "iphoneos", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "1,2", + ] // Then XCTAssertEqual(results, expected) } @@ -89,19 +89,19 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) - let expected: BuildSettings = [ - "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", - "ENABLE_PREVIEWS": "YES", - "CODE_SIGN_IDENTITY": "-", - "COMBINE_HIDPI_IMAGES": "YES", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/../Frameworks", - ], - "SDKROOT": "macosx", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - ] + let expected: BuildSettings = [ + "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", + "ENABLE_PREVIEWS": "YES", + "CODE_SIGN_IDENTITY": "-", + "COMBINE_HIDPI_IMAGES": "YES", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/../Frameworks", + ], + "SDKROOT": "macosx", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + ] // Then XCTAssertEqual(results, expected) } @@ -113,20 +113,20 @@ class BuildSettingProviderTests: XCTestCase { product: .application, swift: true) - let expected: BuildSettings = [ - "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", - "ENABLE_PREVIEWS": "YES", - "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "LaunchImage", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - ], - "SDKROOT": "appletvos", - "SWIFT_COMPILATION_MODE": "wholemodule", - "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", - "TARGETED_DEVICE_FAMILY": "3", - ] - + let expected: BuildSettings = [ + "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", + "ENABLE_PREVIEWS": "YES", + "ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME": "LaunchImage", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + ], + "SDKROOT": "appletvos", + "SWIFT_COMPILATION_MODE": "wholemodule", + "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", + "TARGETED_DEVICE_FAMILY": "3", + ] + // Then XCTAssertEqual(results, expected) } @@ -137,7 +137,7 @@ class BuildSettingProviderTests: XCTestCase { platform: .watchOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", @@ -148,11 +148,10 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", ] - + XCTAssertEqual(results, expected) - } - + func test_targetSettings_watchOSFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, @@ -184,9 +183,8 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - - XCTAssertEqual(results, expected) + XCTAssertEqual(results, expected) } func test_targetSettings_watchOSExtension() { @@ -234,7 +232,7 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } @@ -268,7 +266,7 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + XCTAssertEqual(results, expected) } @@ -292,7 +290,7 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } @@ -303,20 +301,20 @@ class BuildSettingProviderTests: XCTestCase { product: .unitTests, swift: true) - let expected: BuildSettings = [ - "CODE_SIGN_IDENTITY": "iPhone Developer", - "SDKROOT": "iphoneos", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "TARGETED_DEVICE_FAMILY": "1,2", - ] - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "iPhone Developer", + "SDKROOT": "iphoneos", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "TARGETED_DEVICE_FAMILY": "1,2", + ] + // Then XCTAssertEqual(results, expected) } @@ -327,23 +325,22 @@ class BuildSettingProviderTests: XCTestCase { platform: .iOS, product: .uiTests, swift: true) - let expected: BuildSettings = [ - "CODE_SIGN_IDENTITY": "iPhone Developer", - "SDKROOT": "iphoneos", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "TARGETED_DEVICE_FAMILY": "1,2", - ] - - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "iPhone Developer", + "SDKROOT": "iphoneos", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "TARGETED_DEVICE_FAMILY": "1,2", + ] + // Then - XCTAssertEqual(results, expected ) + XCTAssertEqual(results, expected) } func test_targetSettings_macOSUnitTests() { @@ -353,19 +350,19 @@ class BuildSettingProviderTests: XCTestCase { product: .unitTests, swift: true) - let expected: BuildSettings = [ - "CODE_SIGN_IDENTITY": "-", - "SDKROOT": "macosx", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - ] - + let expected: BuildSettings = [ + "CODE_SIGN_IDENTITY": "-", + "SDKROOT": "macosx", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + ] + // Then XCTAssertEqual(results, expected) } @@ -376,18 +373,18 @@ class BuildSettingProviderTests: XCTestCase { platform: .tvOS, product: .unitTests, swift: true) - let expected: BuildSettings = [ - "SDKROOT": "appletvos", - "LD_RUNPATH_SEARCH_PATHS": [ - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ], - "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], - "SWIFT_COMPILATION_MODE": "singlefile", - "SWIFT_OPTIMIZATION_LEVEL": "-Onone", - "TARGETED_DEVICE_FAMILY": "3", - ] + let expected: BuildSettings = [ + "SDKROOT": "appletvos", + "LD_RUNPATH_SEARCH_PATHS": [ + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ], + "SWIFT_ACTIVE_COMPILATION_CONDITIONS": ["$(inherited)", "DEBUG"], + "SWIFT_COMPILATION_MODE": "singlefile", + "SWIFT_OPTIMIZATION_LEVEL": "-Onone", + "TARGETED_DEVICE_FAMILY": "3", + ] // Then XCTAssertEqual(results, expected) @@ -414,9 +411,8 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) - } func test_targetSettings_visionOSUITests() { @@ -440,9 +436,8 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - - XCTAssertEqual(results, expected) + XCTAssertEqual(results, expected) } // MARK: - Helpers From f0b621ca407000828027e3dfc7a8bf796099f7d8 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Mon, 17 Feb 2025 20:48:00 -0500 Subject: [PATCH 10/41] Allow string literal interpolation --- Sources/XcodeProj/Objects/Configuration/BuildSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index d891c05c2..adf392595 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -46,7 +46,7 @@ extension BuildSetting: ExpressibleByArrayLiteral { } } -extension BuildSetting: ExpressibleByStringLiteral { +extension BuildSetting: ExpressibleByStringInterpolation { public init(stringLiteral value: StringLiteralType) { self = .string(value) } From 34d06743517c1982e2acd42caa77664b5fcb13a6 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Tue, 18 Feb 2025 08:57:08 -0500 Subject: [PATCH 11/41] Convert `PBXBuildFile` settings to strong types. The settings here are constrained to two cases, one as a string and one as a string array as defined here: https://buck.build/javadoc/com/facebook/buck/apple/xcode/xcodeproj/PBXBuildFile.html Given the narrow use case we should constraint the available types here to fit the need. --- .../Objects/BuildPhase/PBXBuildFile.swift | 53 +++++++++++++++++-- Sources/XcodeProj/Utils/PlistValue.swift | 15 ++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift index 5bf89bc25..5899ad164 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"] { + return attributes + } else { + return 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"] { + return compilerFlags + } else { + return 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) @@ -195,3 +213,32 @@ final class PBXBuildPhaseFile: PlistSerializable, Equatable { lhs.buildFile == rhs.buildFile && lhs.buildPhase == rhs.buildPhase } } + + +public enum BuildFileSetting: Sendable, Equatable { + case string(String) + case array([String]) +} + +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) + } + } +} diff --git a/Sources/XcodeProj/Utils/PlistValue.swift b/Sources/XcodeProj/Utils/PlistValue.swift index 78251568c..c80e2eb25 100644 --- a/Sources/XcodeProj/Utils/PlistValue.swift +++ b/Sources/XcodeProj/Utils/PlistValue.swift @@ -100,6 +100,21 @@ extension [String: BuildSetting] { } } +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 Dictionary where Key == String { func plist() -> PlistValue { var dictionary: [CommentedString: PlistValue] = [:] From 098cdb57e81fb4733fb26073e5532e2549421097 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Tue, 18 Feb 2025 09:14:25 -0500 Subject: [PATCH 12/41] Add tests --- .../BuildPhase/PBXBuildFileTests.swift | 18 ++++++++++ .../Configuration/BuildFileSettingTests.swift | 33 +++++++++++++++++++ .../Configuration/BuildSettingTests.swift | 7 ---- 3 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift diff --git a/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift b/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift index 5dbd1cd22..072dcbe82 100644 --- a/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift +++ b/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift @@ -13,4 +13,22 @@ 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..503d9eb13 --- /dev/null +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift @@ -0,0 +1,33 @@ +import XCTest +@testable import XcodeProj + +final class BuildFileSettingTests: XCTestCase { + func test_BuildSettings_Encode_to_JSON() 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) + + XCTAssertEqual(result, expectedJSON.data(using: .utf8)) + } + + func test_buildSettings_decodes_from_JSON() 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)!) + + XCTAssertEqual(result, expectedSettings) + } +} diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift index 54744f678..c6eaae675 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift @@ -1,10 +1,3 @@ -// -// BuildSettingTests.swift -// XcodeProj -// -// Created by Michael Simons on 7/20/24. -// - import XCTest @testable import XcodeProj From 519c51197333814fe810c86231c0bf55129be7e8 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Tue, 18 Feb 2025 09:14:49 -0500 Subject: [PATCH 13/41] Linting --- .../XcodeProj/Objects/BuildPhase/PBXBuildFile.swift | 11 +++++------ .../Objects/BuildPhase/PBXBuildFileTests.swift | 6 ++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift index 5899ad164..0dbe421d3 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift @@ -32,22 +32,22 @@ public final class PBXBuildFile: PBXObject { /// Element settings 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"] { - return attributes + attributes } else { - return nil + 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"] { - return compilerFlags + compilerFlags } else { - return nil + nil } } @@ -214,7 +214,6 @@ final class PBXBuildPhaseFile: PlistSerializable, Equatable { } } - public enum BuildFileSetting: Sendable, Equatable { case string(String) case array([String]) diff --git a/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift b/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift index 072dcbe82..63686a756 100644 --- a/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift +++ b/Tests/XcodeProjTests/Objects/BuildPhase/PBXBuildFileTests.swift @@ -13,7 +13,7 @@ final class PBXBuildFileTests: XCTestCase { ) XCTAssertEqual(pbxBuildFile.platformFilter, "platformFilter") } - + func test_platformCompilerFlagsIsSet() { let expected = "flagValue" let pbxBuildFile = PBXBuildFile( @@ -21,7 +21,7 @@ final class PBXBuildFileTests: XCTestCase { ) XCTAssertEqual(pbxBuildFile.compilerFlags, expected) } - + func test_platformAttributesIsSet() { let expected = ["Public"] let pbxBuildFile = PBXBuildFile( @@ -29,6 +29,4 @@ final class PBXBuildFileTests: XCTestCase { ) XCTAssertEqual(pbxBuildFile.attributes, expected) } - - } From 8e32b3960e558ee189ecea0383bc3a4ade293999 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Tue, 18 Feb 2025 20:31:37 -0500 Subject: [PATCH 14/41] Fix pbxbuildfile equality --- Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift index 643fdda57..d9f240fe2 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) From f1182449269fb11eabd62f5c7621a745d3e7ba13 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 09:16:42 -0500 Subject: [PATCH 15/41] Add easy access to build setting values --- .../Objects/Configuration/BuildSettings.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index adf392595..563db7c3c 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -15,6 +15,22 @@ public enum BuildSetting: Sendable, Equatable { array.joined(separator: " ") } } + + 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 BuildSetting: Codable { From 024210ca8497963b4f4a43400efd80499f75a978 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 09:20:16 -0500 Subject: [PATCH 16/41] Strongly typed classes tho these appear to always be empty --- Sources/XcodeProj/Objects/Project/PBXProj.swift | 12 ++++++------ .../Objects/Project/PBXProj+Fixtures.swift | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/XcodeProj/Objects/Project/PBXProj.swift b/Sources/XcodeProj/Objects/Project/PBXProj.swift index 78e6da101..983ed4f99 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.objectVersion == rhs.objectVersion && - equalClasses && + lhs.classes == rhs.classes && lhs.objects == rhs.objects } } 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, From b72b60efe4b9da711666951a98eec6d7b627f1af Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 10:23:16 -0500 Subject: [PATCH 17/41] Strongly typed attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit XcodeGen introduced the ability to include whole `PBXObject` values within these dictionaries which end up being written to the project as their reference string value. In order to simplify the attributes interface, i’m removing that capability and consumers will need to do their own unwrapping. --- .../Objects/Project/PBXProject.swift | 101 +++++++++++++++--- .../Objects/Sourcery/Equality.generated.swift | 4 +- Sources/XcodeProj/Utils/PlistValue.swift | 19 ++++ .../Objects/Project/PBXProjectTests.swift | 4 +- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift index 8e8ddcab5..1b61645b4 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,18 @@ 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() @@ -602,3 +606,66 @@ extension PBXProject: PlistSerializable { }) } } + + +public enum ProjectAttribute: Sendable, Equatable { + case string(String) + case array([String]) + 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: Codable { + 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) + } + } + + 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) + case let .attributeDictionary(attributes): + try container.encode(attributes) + } + } +} + +extension ProjectAttribute: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension ProjectAttribute: ExpressibleByStringInterpolation { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} + diff --git a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift index d9f240fe2..cd69699a9 100644 --- a/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift +++ b/Sources/XcodeProj/Objects/Sourcery/Equality.generated.swift @@ -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 } diff --git a/Sources/XcodeProj/Utils/PlistValue.swift b/Sources/XcodeProj/Utils/PlistValue.swift index c80e2eb25..fa69a0cd3 100644 --- a/Sources/XcodeProj/Utils/PlistValue.swift +++ b/Sources/XcodeProj/Utils/PlistValue.swift @@ -115,6 +115,23 @@ extension [String: BuildFileSetting] { } } +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() + } + } + return .dictionary(dictionary) + } +} + extension Dictionary where Key == String { func plist() -> PlistValue { var dictionary: [CommentedString: PlistValue] = [:] @@ -125,6 +142,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 c = value as? PlistValue { + dictionary[CommentedString(key)] = c } } return .dictionary(dictionary) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift index ba48a65f1..198586d42 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift @@ -18,7 +18,7 @@ final class PBXProjectTests: XCTestCase { attributes: ["LastUpgradeCheck": "0940"], targetAttributes: [target: ["TestTargetID": "123"]]) - project.setTargetAttributes(["custom": "abc", "TestTargetID": testTarget], target: target) + project.setTargetAttributes(["custom": "abc", "TestTargetID": .string(testTarget.reference.value)], target: target) let plist = try project.plistKeyAndValue(proj: PBXProj(), reference: "") let attributes = plist.value.dictionary?["attributes"]?.dictionary ?? [:] @@ -48,7 +48,7 @@ final class PBXProjectTests: XCTestCase { minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) - project.setTargetAttributes(["custom": "abc", "TestTargetID": testTarget], target: target) + project.setTargetAttributes(["custom": "abc", "TestTargetID": .string(testTarget.reference.value)], target: target) // When let plist = try project.plistKeyAndValue(proj: PBXProj(), reference: "") From e6c2170050b084249bc89c858fc7d52d77fe9092 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 10:46:37 -0500 Subject: [PATCH 18/41] linting --- .../Objects/Configuration/BuildSettings.swift | 4 ++-- Sources/XcodeProj/Objects/Project/PBXProj.swift | 2 +- Sources/XcodeProj/Objects/Project/PBXProject.swift | 11 ++++------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 563db7c3c..15798fe64 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -15,7 +15,7 @@ public enum BuildSetting: Sendable, Equatable { array.joined(separator: " ") } } - + public var stringValue: String? { if case let .string(value) = self { value @@ -23,7 +23,7 @@ public enum BuildSetting: Sendable, Equatable { nil } } - + public var arrayValue: [String]? { if case let .array(value) = self { value diff --git a/Sources/XcodeProj/Objects/Project/PBXProj.swift b/Sources/XcodeProj/Objects/Project/PBXProj.swift index 983ed4f99..c8faeb274 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProj.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProj.swift @@ -262,7 +262,7 @@ extension PBXProj { extension PBXProj: Equatable { public static func == (lhs: PBXProj, rhs: PBXProj) -> Bool { - return lhs.archiveVersion == rhs.archiveVersion && + lhs.archiveVersion == rhs.archiveVersion && lhs.objectVersion == rhs.objectVersion && 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 1b61645b4..b87a4af7e 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProject.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift @@ -571,8 +571,7 @@ extension PBXProject: PlistSerializable { for (reference, value) in targetAttributeReferences { plistTargetAttributes[reference.value] = value } - - + if !plistTargetAttributes.isEmpty { plistAttributes[PBXProject.targetAttributesKey] = .attributeDictionary(plistTargetAttributes) } @@ -607,12 +606,11 @@ extension PBXProject: PlistSerializable { } } - public enum ProjectAttribute: Sendable, Equatable { case string(String) case array([String]) case attributeDictionary([String: [String: ProjectAttribute]]) - + public var stringValue: String? { if case let .string(value) = self { value @@ -620,7 +618,7 @@ public enum ProjectAttribute: Sendable, Equatable { nil } } - + public var arrayValue: [String]? { if case let .array(value) = self { value @@ -633,7 +631,7 @@ public enum ProjectAttribute: Sendable, Equatable { extension ProjectAttribute: Codable { 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) { @@ -668,4 +666,3 @@ extension ProjectAttribute: ExpressibleByStringInterpolation { self = .string(value) } } - From d3d0bf30c221380d4c63802a453231e648c75850 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 10:46:46 -0500 Subject: [PATCH 19/41] Convenience accessors for BuildFileSetting --- .../Objects/BuildPhase/PBXBuildFile.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift index 0dbe421d3..12c080dce 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift @@ -217,6 +217,22 @@ final class PBXBuildPhaseFile: PlistSerializable, Equatable { public enum BuildFileSetting: Sendable, Equatable { 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 { From 364b8ae13e07d93d7ae10930fc9eac76ccd41c9b Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 11:24:08 -0500 Subject: [PATCH 20/41] Remove deprecated `parallelizable` --- .../Scheme/XCScheme+TestableReference.swift | 41 +++----------- .../XcodeProjTests/Scheme/XCSchemeTests.swift | 56 ++----------------- 2 files changed, 14 insertions(+), 83 deletions(-) diff --git a/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift b/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift index da8f87c44..514d869d6 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" @@ -136,16 +111,16 @@ public extension XCScheme { } // MARK: - Equatable - + public static func == (lhs: TestableReference, rhs: TestableReference) -> Bool { lhs.skipped == rhs.skipped && - lhs.parallelizable == rhs.parallelizable && - lhs.randomExecutionOrdering == rhs.randomExecutionOrdering && - lhs.buildableReference == rhs.buildableReference && - lhs.locationScenarioReference == rhs.locationScenarioReference && - lhs.useTestSelectionWhitelist == rhs.useTestSelectionWhitelist && - lhs.skippedTests == rhs.skippedTests && - lhs.selectedTests == rhs.selectedTests + lhs.parallelization == rhs.parallelization && + lhs.randomExecutionOrdering == rhs.randomExecutionOrdering && + lhs.buildableReference == rhs.buildableReference && + lhs.locationScenarioReference == rhs.locationScenarioReference && + lhs.useTestSelectionWhitelist == rhs.useTestSelectionWhitelist && + lhs.skippedTests == rhs.skippedTests && + lhs.selectedTests == rhs.selectedTests } } } diff --git a/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift b/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift index 9d57bf8b1..0c8c634cf 100644 --- a/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift +++ b/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift @@ -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) From bc6a9df84d9f19884e8fc50813878178f9b7e0ca Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sat, 22 Feb 2025 11:24:15 -0500 Subject: [PATCH 21/41] Silence sendability warnings --- Sources/XcodeProj/Utils/Decoders.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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() From fd8f8982a26af831a5a67c51b3d849f33483fa29 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 11:31:03 -0500 Subject: [PATCH 22/41] Conform `BuildSetting` to `CustomStringConvertible` --- .../Objects/Configuration/BuildSettings.swift | 22 ++++++++++--------- Sources/XcodeProj/Utils/XCConfig.swift | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 15798fe64..03d9d3fcd 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -6,16 +6,7 @@ public typealias BuildSettings = [String: BuildSetting] public enum BuildSetting: Sendable, Equatable { case string(String) case array([String]) - - var valueForWriting: String { - switch self { - case let .string(string): - string - case let .array(array): - array.joined(separator: " ") - } - } - + public var stringValue: String? { if case let .string(value) = self { value @@ -33,6 +24,17 @@ public enum BuildSetting: Sendable, Equatable { } } +extension BuildSetting: CustomStringConvertible { + public var description: String { + switch self { + case let .string(string): + string + case let .array(array): + array.joined(separator: " ") + } + } +} + extension BuildSetting: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() diff --git a/Sources/XcodeProj/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift index 592e74344..922da864d 100644 --- a/Sources/XcodeProj/Utils/XCConfig.swift +++ b/Sources/XcodeProj/Utils/XCConfig.swift @@ -174,7 +174,7 @@ extension XCConfig: Writable { private func writeBuildSettings() -> String { var content = "" for (key, value) in buildSettings { - content.append("\(key) = \(value.valueForWriting)\n") + content.append("\(key) = \(value)\n") } content.append("\n") return content From fef1fb17fed09ebddfb03864b693d4b37660b267 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 11:59:10 -0500 Subject: [PATCH 23/41] Extract `BuildFileSettings` to own file and convert tests to SwiftTesting --- .../Objects/BuildPhase/BuildFileSetting.swift | 44 +++++++++++++++++++ .../Objects/BuildPhase/PBXBuildFile.swift | 43 ------------------ .../Configuration/BuildFileSettingTests.swift | 19 ++++---- 3 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift diff --git a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift new file mode 100644 index 000000000..ddce0e50f --- /dev/null +++ b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift @@ -0,0 +1,44 @@ + +public enum BuildFileSetting: Sendable, Equatable { + 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) + } + } +} diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift index 12c080dce..54d862137 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift @@ -214,46 +214,3 @@ final class PBXBuildPhaseFile: PlistSerializable, Equatable { } } -public enum BuildFileSetting: Sendable, Equatable { - 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) - } - } -} diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift index 503d9eb13..82a4bd5d0 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift @@ -1,8 +1,10 @@ -import XCTest +import Testing +import Foundation @testable import XcodeProj -final class BuildFileSettingTests: XCTestCase { - func test_BuildSettings_Encode_to_JSON() throws { +struct BuildFileSettingTests { + + @Test func test_BuildSettings_encodes_to_JSON() async throws { let expectedJSON = #"{"one":"one","two":["two","two"]}"# let settings: [String: BuildFileSetting] = [ @@ -14,11 +16,11 @@ final class BuildFileSettingTests: XCTestCase { encoder.outputFormatting = .sortedKeys let result = try encoder.encode(settings) - - XCTAssertEqual(result, expectedJSON.data(using: .utf8)) + + #expect(result == expectedJSON.data(using: .utf8)) } - - func test_buildSettings_decodes_from_JSON() throws { + + @Test func test_buildSettings_decodes_from_JSON() async throws { let json = #"{"one":"one","two":["two","two"]}"# let expectedSettings: [String: BuildFileSetting] = [ @@ -28,6 +30,7 @@ final class BuildFileSettingTests: XCTestCase { let result = try JSONDecoder().decode([String: BuildFileSetting].self, from: json.data(using: .utf8)!) - XCTAssertEqual(result, expectedSettings) + #expect(result == expectedSettings) } + } From 5ff523d46cb3c157d8c28ebeefafd9b68608c6b8 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 12:13:35 -0500 Subject: [PATCH 24/41] Extract ProjecteAttributes to own file --- .../Objects/Project/PBXProject.swift | 60 ------------------- .../Objects/Project/ProjectAttribute.swift | 60 +++++++++++++++++++ 2 files changed, 60 insertions(+), 60 deletions(-) create mode 100644 Sources/XcodeProj/Objects/Project/ProjectAttribute.swift diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift index b87a4af7e..7c905f851 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProject.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift @@ -606,63 +606,3 @@ extension PBXProject: PlistSerializable { } } -public enum ProjectAttribute: Sendable, Equatable { - case string(String) - case array([String]) - 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: Codable { - 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) - } - } - - 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) - case let .attributeDictionary(attributes): - try container.encode(attributes) - } - } -} - -extension ProjectAttribute: ExpressibleByArrayLiteral { - public init(arrayLiteral elements: String...) { - self = .array(elements) - } -} - -extension ProjectAttribute: ExpressibleByStringInterpolation { - public init(stringLiteral value: StringLiteralType) { - self = .string(value) - } -} diff --git a/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift b/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift new file mode 100644 index 000000000..d5de9398d --- /dev/null +++ b/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift @@ -0,0 +1,60 @@ +public enum ProjectAttribute: Sendable, Equatable { + case string(String) + case array([String]) + 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: Codable { + 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) + } + } + + 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) + case let .attributeDictionary(attributes): + try container.encode(attributes) + } + } +} + +extension ProjectAttribute: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension ProjectAttribute: ExpressibleByStringInterpolation { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} From d56c55a4be201c89fd42a0fcbd0285c32a40e132 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 12:13:43 -0500 Subject: [PATCH 25/41] Delete commented code --- .../Utils/BuildSettingsProviderTests.swift | 95 +++++++++---------- 1 file changed, 43 insertions(+), 52 deletions(-) diff --git a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift index 0dfaa5898..3236b1c2d 100644 --- a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift +++ b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift @@ -22,11 +22,11 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_iosFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, @@ -55,11 +55,11 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_iosExtension() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, @@ -81,14 +81,14 @@ class BuildSettingProviderTests: XCTestCase { // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_macOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .macOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", @@ -105,14 +105,14 @@ class BuildSettingProviderTests: XCTestCase { // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_tvOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .tvOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", "ENABLE_PREVIEWS": "YES", @@ -126,18 +126,18 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "3", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_watchOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .watchOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", @@ -148,17 +148,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_watchOSFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .watchOS, product: .framework, swift: true) - + // Then let expected: BuildSettings = [ "APPLICATION_EXTENSION_API_ONLY": "YES", @@ -183,17 +183,17 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_watchOSExtension() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .watchOS, product: .appExtension, swift: true) - + // Then let expected: BuildSettings = [ "LD_RUNPATH_SEARCH_PATHS": [ @@ -207,17 +207,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .visionOS, product: .application, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -232,17 +232,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .visionOS, product: .framework, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "", @@ -266,17 +266,17 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSExtension() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .visionOS, product: .appExtension, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -290,17 +290,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_iOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .iOS, product: .unitTests, swift: true) - + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "iphoneos", @@ -314,11 +314,11 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_iOSUITests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, @@ -338,18 +338,18 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_macOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .macOS, product: .unitTests, swift: true) - + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "-", "SDKROOT": "macosx", @@ -362,11 +362,11 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_tvOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, @@ -385,18 +385,18 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "3", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .visionOS, product: .unitTests, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -411,17 +411,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSUITests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .visionOS, product: .uiTests, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -436,16 +436,7 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - - // MARK: - Helpers - -// func XCTAssertEqual(_ lhs: BuildSettings, _ rhs: BuildSettings, file: StaticString = #file, line: UInt = #line) { -// XCTAssertEqual(lhs as NSDictionary, -// rhs as NSDictionary, -// file: file, -// line: line) -// } } From 7785310675c92c0eb832c494a85e1a0681d35fec Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 12:52:10 -0500 Subject: [PATCH 26/41] Linting --- .../Objects/BuildPhase/BuildFileSetting.swift | 1 - .../Objects/BuildPhase/PBXBuildFile.swift | 1 - .../Objects/Configuration/BuildSettings.swift | 2 +- .../Objects/Project/PBXProject.swift | 1 - .../Scheme/XCScheme+TestableReference.swift | 16 ++-- .../Configuration/BuildFileSettingTests.swift | 8 +- .../Utils/BuildSettingsProviderTests.swift | 86 +++++++++---------- 7 files changed, 55 insertions(+), 60 deletions(-) diff --git a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift index ddce0e50f..8b7f1ff95 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift @@ -1,4 +1,3 @@ - public enum BuildFileSetting: Sendable, Equatable { case string(String) case array([String]) diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift index 54d862137..094132d79 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXBuildFile.swift @@ -213,4 +213,3 @@ final class PBXBuildPhaseFile: PlistSerializable, Equatable { lhs.buildFile == rhs.buildFile && lhs.buildPhase == rhs.buildPhase } } - diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index 03d9d3fcd..aecb83601 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -6,7 +6,7 @@ public typealias BuildSettings = [String: BuildSetting] public enum BuildSetting: Sendable, Equatable { case string(String) case array([String]) - + public var stringValue: String? { if case let .string(value) = self { value diff --git a/Sources/XcodeProj/Objects/Project/PBXProject.swift b/Sources/XcodeProj/Objects/Project/PBXProject.swift index 7c905f851..85b3cae41 100644 --- a/Sources/XcodeProj/Objects/Project/PBXProject.swift +++ b/Sources/XcodeProj/Objects/Project/PBXProject.swift @@ -605,4 +605,3 @@ extension PBXProject: PlistSerializable { }) } } - diff --git a/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift b/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift index 514d869d6..f6b6a4b42 100644 --- a/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift +++ b/Sources/XcodeProj/Scheme/XCScheme+TestableReference.swift @@ -111,16 +111,16 @@ public extension XCScheme { } // MARK: - Equatable - + public static func == (lhs: TestableReference, rhs: TestableReference) -> Bool { lhs.skipped == rhs.skipped && - lhs.parallelization == rhs.parallelization && - lhs.randomExecutionOrdering == rhs.randomExecutionOrdering && - lhs.buildableReference == rhs.buildableReference && - lhs.locationScenarioReference == rhs.locationScenarioReference && - lhs.useTestSelectionWhitelist == rhs.useTestSelectionWhitelist && - lhs.skippedTests == rhs.skippedTests && - lhs.selectedTests == rhs.selectedTests + lhs.parallelization == rhs.parallelization && + lhs.randomExecutionOrdering == rhs.randomExecutionOrdering && + lhs.buildableReference == rhs.buildableReference && + lhs.locationScenarioReference == rhs.locationScenarioReference && + lhs.useTestSelectionWhitelist == rhs.useTestSelectionWhitelist && + lhs.skippedTests == rhs.skippedTests && + lhs.selectedTests == rhs.selectedTests } } } diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift index 82a4bd5d0..5064f56e2 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildFileSettingTests.swift @@ -1,9 +1,8 @@ -import Testing 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"]}"# @@ -16,10 +15,10 @@ struct BuildFileSettingTests { 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"]}"# @@ -32,5 +31,4 @@ struct BuildFileSettingTests { #expect(result == expectedSettings) } - } diff --git a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift index 3236b1c2d..747bbb375 100644 --- a/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift +++ b/Tests/XcodeProjTests/Utils/BuildSettingsProviderTests.swift @@ -22,11 +22,11 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_iosFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, @@ -55,11 +55,11 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_iosExtension() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, @@ -81,14 +81,14 @@ class BuildSettingProviderTests: XCTestCase { // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_macOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .macOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", @@ -105,14 +105,14 @@ class BuildSettingProviderTests: XCTestCase { // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_tvOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .tvOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "App Icon & Top Shelf Image", "ENABLE_PREVIEWS": "YES", @@ -126,18 +126,18 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "3", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_watchOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .watchOS, product: .application, swift: true) - + let expected: BuildSettings = [ "ASSETCATALOG_COMPILER_APPICON_NAME": "AppIcon", "ENABLE_PREVIEWS": "YES", @@ -148,17 +148,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_watchOSFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .watchOS, product: .framework, swift: true) - + // Then let expected: BuildSettings = [ "APPLICATION_EXTENSION_API_ONLY": "YES", @@ -183,17 +183,17 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_watchOSExtension() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .watchOS, product: .appExtension, swift: true) - + // Then let expected: BuildSettings = [ "LD_RUNPATH_SEARCH_PATHS": [ @@ -207,17 +207,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "4", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSAplication() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .visionOS, product: .application, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -232,17 +232,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSFramework() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .visionOS, product: .framework, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "", @@ -266,17 +266,17 @@ class BuildSettingProviderTests: XCTestCase { "VERSIONING_SYSTEM": "apple-generic", "VERSION_INFO_PREFIX": "", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSExtension() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .release, platform: .visionOS, product: .appExtension, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -290,17 +290,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Owholemodule", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_iOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .iOS, product: .unitTests, swift: true) - + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", "SDKROOT": "iphoneos", @@ -314,11 +314,11 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_iOSUITests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, @@ -338,18 +338,18 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_macOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .macOS, product: .unitTests, swift: true) - + let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "-", "SDKROOT": "macosx", @@ -362,11 +362,11 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_COMPILATION_MODE": "singlefile", "SWIFT_OPTIMIZATION_LEVEL": "-Onone", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_tvOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, @@ -385,18 +385,18 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "3", ] - + // Then XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSUnitTests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .visionOS, product: .unitTests, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -411,17 +411,17 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } - + func test_targetSettings_visionOSUITests() { // Given / When let results = BuildSettingsProvider.targetDefault(variant: .debug, platform: .visionOS, product: .uiTests, swift: true) - + // Then let expected: BuildSettings = [ "CODE_SIGN_IDENTITY": "iPhone Developer", @@ -436,7 +436,7 @@ class BuildSettingProviderTests: XCTestCase { "SWIFT_OPTIMIZATION_LEVEL": "-Onone", "TARGETED_DEVICE_FAMILY": "1,2,7", ] - + XCTAssertEqual(results, expected) } } From f2098b7065a7c5406460889be5ffbed283e35577 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 13:30:22 -0500 Subject: [PATCH 27/41] Add bool bridging to `BuildSetting` --- .../Objects/Configuration/BuildSettings.swift | 33 ++++++++---- .../Configuration/BuildSettingTests.swift | 52 ++++++++++++------- 2 files changed, 56 insertions(+), 29 deletions(-) diff --git a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift index aecb83601..5ca94d202 100644 --- a/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift +++ b/Sources/XcodeProj/Objects/Configuration/BuildSettings.swift @@ -3,6 +3,9 @@ import Foundation /// Build settings. public typealias BuildSettings = [String: BuildSetting] +private let yes = "YES" +private let no = "NO" + public enum BuildSetting: Sendable, Equatable { case string(String) case array([String]) @@ -15,6 +18,18 @@ public enum BuildSetting: Sendable, Equatable { } } + 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 @@ -35,7 +50,7 @@ extension BuildSetting: CustomStringConvertible { } } -extension BuildSetting: Codable { +extension BuildSetting: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { @@ -46,16 +61,6 @@ extension BuildSetting: Codable { 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 BuildSetting: ExpressibleByArrayLiteral { @@ -69,3 +74,9 @@ extension BuildSetting: ExpressibleByStringInterpolation { self = .string(value) } } + +extension BuildSetting: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .string(value ? yes : no) + } +} diff --git a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift index c6eaae675..5842a0a7c 100644 --- a/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift +++ b/Tests/XcodeProjTests/Objects/Configuration/BuildSettingTests.swift @@ -1,24 +1,14 @@ -import XCTest +import Foundation +import Testing @testable import XcodeProj -final class BuildSettingTests: XCTestCase { - func test_BuildSettings_Encode_to_JSON() throws { - let expectedJSON = #"{"one":"one","two":["two","two"]}"# - - let settings: BuildSettings = [ - "one": .string("one"), - "two": .array(["two", "two"]), - ] - - let encoder = JSONEncoder() - encoder.outputFormatting = .sortedKeys - - let result = try encoder.encode(settings) - - XCTAssertEqual(result, expectedJSON.data(using: .utf8)) +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") } - func test_buildSettings_decodes_from_JSON() throws { + @Test func test_buildSettings_decodes_from_JSON() async throws { let json = #"{"one":"one","two":["two","two"]}"# let expectedSettings: BuildSettings = [ @@ -28,6 +18,32 @@ final class BuildSettingTests: XCTestCase { let result = try JSONDecoder().decode(BuildSettings.self, from: json.data(using: .utf8)!) - XCTAssertEqual(result, expectedSettings) + #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) } } From 6dac103fedaddcd38c3b9462bca120194cfebdd4 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 13:43:14 -0500 Subject: [PATCH 28/41] Update to Swift 6 --- Package.swift | 2 +- Sources/XcodeProj/Extensions/Path+Extras.swift | 2 +- Sources/XcodeProj/Utils/XCConfig.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/Utils/XCConfig.swift b/Sources/XcodeProj/Utils/XCConfig.swift index 922da864d..639f6c1b0 100644 --- a/Sources/XcodeProj/Utils/XCConfig.swift +++ b/Sources/XcodeProj/Utils/XCConfig.swift @@ -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 { From 2fea426f7db5b45accb9c448ba2dcbd5a31b17f3 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 13:58:49 -0500 Subject: [PATCH 29/41] Update from `#file` to `#filePath` for swift 6 --- Tests/XcodeProjTests/Extensions/XCTestCase+Assertions.swift | 2 +- Tests/XcodeProjTests/Tests/Fixtures.swift | 2 +- Tests/XcodeProjTests/Tests/testWrite.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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..ab2917f7c 100644 --- a/Tests/XcodeProjTests/Tests/testWrite.swift +++ b/Tests/XcodeProjTests/Tests/testWrite.swift @@ -12,7 +12,7 @@ func testWrite(file _: StaticString = #file, 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?, From 16e941246a5c56b5202b355fa95556c4bb9e20b0 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 14:03:19 -0500 Subject: [PATCH 30/41] Try to get the right swift version on linux --- .github/workflows/xcodeproj.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml index dd16df6b0..c2e237e1d 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.1" - 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.1" - run: | git config --global user.email 'xcodeproj@tuist.dev' git config --global user.name 'xcodeproj' From 0f3a60eb261f6ac81b2a33a7cfbe2a026b7cc5fb Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 14:21:29 -0500 Subject: [PATCH 31/41] Maybe 6.0.3 fixes the issue --- .github/workflows/xcodeproj.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml index c2e237e1d..1a6203beb 100644 --- a/.github/workflows/xcodeproj.yml +++ b/.github/workflows/xcodeproj.yml @@ -32,7 +32,7 @@ jobs: - uses: jdx/mise-action@v2 - uses: swift-actions/setup-swift@v2 with: - swift-version: "6.0.1" + swift-version: "6.0.3" - name: Build run: swift build --configuration release test: @@ -52,7 +52,7 @@ jobs: - uses: jdx/mise-action@v2 - uses: swift-actions/setup-swift@v2 with: - swift-version: "6.0.1" + swift-version: "6.0.3" - run: | git config --global user.email 'xcodeproj@tuist.dev' git config --global user.name 'xcodeproj' From 7f7aacac4d140bf2b44e87b95081466207f858df Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 16:23:29 -0500 Subject: [PATCH 32/41] Migrate to `XCTUnwrap` to avoid `!` which will crash the test suite and not give an accurate failure count --- .../Project/PBXOutputSettingsTests.swift | 76 +++++++++---------- .../Project/PBXProjIntegrationTests.swift | 10 +-- Tests/XcodeProjTests/Tests/testWrite.swift | 14 ++-- 3 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift index 9e4813b95..ded4d7195 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift @@ -5,29 +5,29 @@ import XCTest 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() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_iosProject() throws { + let iosProject = try 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() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_fileSharedAcrossTargetsProject() throws { + let fileSharedAcrossTargetsProject = try 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() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_nil_name_and_path_when_iosProject() throws { + let iosProject = try iosProject() iosProject.buildFileAssets.file?.name = nil iosProject.buildFileMain.file?.name = nil @@ -37,8 +37,8 @@ class PBXOutputSettingsTests: XCTestCase { XCTAssertTrue(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) } - func test_PBXFileOrder_PBXBuildFile_by_filename_when_no_file_when_iosProject() { - let iosProject = iosProject() + func test_PBXFileOrder_PBXBuildFile_by_filename_when_no_file_when_iosProject() throws { + let iosProject = try iosProject() let ref1 = iosProject.buildFileAssets.reference let ref2 = iosProject.buildFileMain.reference @@ -50,15 +50,15 @@ class PBXOutputSettingsTests: XCTestCase { // MARK: - PBXFileOrder - PBXBuildPhaseFile - func test_PBXFileOrder_PBXBuildPhaseFile_by_uuid_when_iosProject() { - let iosProject = iosProject() + 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() { - let iosProject = iosProject() + 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)) @@ -66,29 +66,29 @@ class PBXOutputSettingsTests: XCTestCase { // MARK: - PBXFileOrder - PBXFileReference - func test_PBXFileOrder_PBXFileReference_by_uuid_when_iosProject() { - let iosProject = iosProject() + 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() { - let iosProject = iosProject() + 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() { - let fileSharedAcrossTargetsProject = fileSharedAcrossTargetsProject() + 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() { - let iosProject = iosProject() + 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 @@ -100,15 +100,15 @@ class PBXOutputSettingsTests: XCTestCase { // MARK: - PBXFileOrder - Other - func test_PBXFileOrder_Other_by_uuid_when_iosProject() { - let iosProject = iosProject() + 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() { - let iosProject = iosProject() + 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)) @@ -120,8 +120,8 @@ class PBXOutputSettingsTests: XCTestCase { XCTAssertNil(PBXNavigatorFileOrder.unsorted.sort) } - func test_PBXNavigatorFileOrder_by_filename_when_iosProject() { - let iosProject = iosProject() + func test_PBXNavigatorFileOrder_by_filename_when_iosProject() throws { + let iosProject = try iosProject() let sort: (PBXFileElement, PBXFileElement) -> Bool = PBXNavigatorFileOrder.byFilename.sort! let sorted = iosProject.navigatorFileGroup.children.sorted(by: sort).map { $0.fileName()! } @@ -140,8 +140,8 @@ class PBXOutputSettingsTests: XCTestCase { ], sorted) } - func test_PBXNavigatorFileOrder_by_filename_groups_first_when_iosProject() { - let iosProject = iosProject() + 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()! } @@ -166,8 +166,8 @@ class PBXOutputSettingsTests: XCTestCase { XCTAssertNil(PBXBuildPhaseFileOrder.unsorted.sort) } - func test_PBXBuildPhaseFileOrder_by_filename_when_iosProject() { - let iosProject = iosProject() + 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)) @@ -201,9 +201,9 @@ class PBXOutputSettingsTests: XCTestCase { var navigatorFileGroup: PBXGroup! } - private func iosProject() -> iOSProject { - let data = try! iosProjectData() - let proj = try! PBXProj(data: data) + 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" }! @@ -256,9 +256,9 @@ class PBXOutputSettingsTests: XCTestCase { var objectFileReferenceSameName: [(PBXObjectReference, PBXFileReference)]! } - func fileSharedAcrossTargetsProject() -> FileSharedAcrossTargetsProject { - let dic = try! fileSharedAcrossTargetsData() - let proj = try! PBXProj(data: dic) + 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) } diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift index fc442ca7d..395347ce8 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift @@ -4,8 +4,8 @@ import XCTest @testable import XcodeProj final class PBXProjIntegrationTests: XCTestCase { - func test_init_initializesTheProjCorrectly() { - let data = try! Data(contentsOf: fixturePath().url) + 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) @@ -14,10 +14,10 @@ final class PBXProjIntegrationTests: XCTestCase { } } - func test_write() { - testWrite(from: fixturePath(), + func test_write() throws { + try testWrite(from: fixturePath(), initModel: { path -> PBXProj? in - let data = try! Data(contentsOf: path.url) + let data = try XCTUnwrap(Data(contentsOf: path.url)) let decoder = XcodeprojPropertyListDecoder() return try? decoder.decode(PBXProj.self, from: data) }, diff --git a/Tests/XcodeProjTests/Tests/testWrite.swift b/Tests/XcodeProjTests/Tests/testWrite.swift index ab2917f7c..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 = #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) From 09b3facd8c9bf4ee0e1a392f2bb4cdae5a6b8056 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 16:26:30 -0500 Subject: [PATCH 33/41] Revert to 6.0.2 6.0.3 is not supported and also doesnt have a fix we need. --- .github/workflows/xcodeproj.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/xcodeproj.yml b/.github/workflows/xcodeproj.yml index 1a6203beb..591f2df6c 100644 --- a/.github/workflows/xcodeproj.yml +++ b/.github/workflows/xcodeproj.yml @@ -32,7 +32,7 @@ jobs: - uses: jdx/mise-action@v2 - uses: swift-actions/setup-swift@v2 with: - swift-version: "6.0.3" + swift-version: "6.0.2" - name: Build run: swift build --configuration release test: @@ -52,7 +52,7 @@ jobs: - uses: jdx/mise-action@v2 - uses: swift-actions/setup-swift@v2 with: - swift-version: "6.0.3" + swift-version: "6.0.2" - run: | git config --global user.email 'xcodeproj@tuist.dev' git config --global user.name 'xcodeproj' From 5cf9160f2841f691429f7c26e3ca179043210c0e Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 16:30:57 -0500 Subject: [PATCH 34/41] Fix tests --- .../Project/PBXProjIntegrationTests.swift | 12 ++--- .../Project/XCBreakpointListTests.swift | 10 ++--- .../Project/XCUserDataTests.swift | 22 +++++----- .../Project/XcodeProjIntegrationTests.swift | 44 +++++++++---------- .../XcodeProjTests/Scheme/XCSchemeTests.swift | 30 ++++++------- .../XcodeProjTests/Utils/XCConfigTests.swift | 8 ++-- .../Workspace/XCWorkspaceDataTests.swift | 4 +- 7 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift index 395347ce8..e9ea4eb0d 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift @@ -16,12 +16,12 @@ final class PBXProjIntegrationTests: XCTestCase { 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 }) + 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 }) } func test_write_produces_no_diff() throws { 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..c2e4b9906 100644 --- a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift @@ -14,15 +14,15 @@ final class XcodeProjIntegrationTests: XCTestCase { 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_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 { @@ -53,19 +53,19 @@ final class XcodeProjIntegrationTests: XCTestCase { // 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) - }) + 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 diff --git a/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift b/Tests/XcodeProjTests/Scheme/XCSchemeTests.swift index 0c8c634cf..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() { diff --git a/Tests/XcodeProjTests/Utils/XCConfigTests.swift b/Tests/XcodeProjTests/Utils/XCConfigTests.swift index 9cd58ffd5..a23cca1ee 100644 --- a/Tests/XcodeProjTests/Utils/XCConfigTests.swift +++ b/Tests/XcodeProjTests/Utils/XCConfigTests.swift @@ -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 { 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: { From 246129088ace2d86146951f54150e28e4c9642eb Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 16:40:38 -0500 Subject: [PATCH 35/41] Exclude tests that need `plist` serialization from linux This need a fix that is in swift 6.1 to pass on linux: https://github.com/swiftlang/swift-foundation/pull/1002 --- .../Objects/Project/PBXOutputSettingsTests.swift | 2 ++ .../XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift | 4 +++- .../Objects/Project/PBXProjIntegrationTests.swift | 3 +++ Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift | 3 +++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift index ded4d7195..95392c5ff 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift @@ -1,3 +1,4 @@ +#if os(macOS) || (os(Linux) && compiler(>=6.1)) import XCTest @testable import XcodeProj @@ -274,3 +275,4 @@ class PBXOutputSettingsTests: XCTestCase { ) } } +#endif diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index 2f0e49c81..a707053c2 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -1,4 +1,4 @@ - +#if os(macOS) || (os(Linux) && compiler(>=6.1)) import Foundation import XCTest @testable import XcodeProj @@ -616,3 +616,5 @@ private extension [String] { } } } + +#endif diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift index e9ea4eb0d..a4a39e53f 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift @@ -1,3 +1,4 @@ +#if os(macOS) || (os(Linux) && compiler(>=6.1)) import Foundation import PathKit import XCTest @@ -84,3 +85,5 @@ final class PBXProjIntegrationTests: XCTestCase { XCTAssertEqual(proj.objects.remoteSwiftPackageReferences.count, 1) } } + +#endif diff --git a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift index c2e4b9906..4b522e8d8 100644 --- a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift @@ -1,3 +1,4 @@ +#if os(macOS) || (os(Linux) && compiler(>=6.1)) import Foundation import PathKit import XCTest @@ -109,3 +110,5 @@ final class XcodeProjIntegrationTests: XCTestCase { fixturesPath() + "SynchronizedRootGroups/SynchronizedRootGroups.xcodeproj" } } + +#endif From 648909e306fba917dccbb30478089a5cdf8f6daf Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 17:30:52 -0500 Subject: [PATCH 36/41] Lint fixes --- .../Project/PBXOutputSettingsTests.swift | 518 ++++---- .../Objects/Project/PBXProjEncoderTests.swift | 1084 ++++++++--------- .../Project/PBXProjIntegrationTests.swift | 148 +-- .../Project/XcodeProjIntegrationTests.swift | 220 ++-- 4 files changed, 985 insertions(+), 985 deletions(-) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift index 95392c5ff..dfc935bed 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXOutputSettingsTests.swift @@ -1,278 +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() throws { - let iosProject = try 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() throws { - let iosProject = try 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() throws { - let fileSharedAcrossTargetsProject = try 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() throws { - let iosProject = try 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() throws { - let iosProject = try 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 - - 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() + XCTAssertFalse(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) + XCTAssertTrue(PBXFileOrder.byUUID.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) + } - 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)) - } - - // 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)) - } - - // MARK: - PBXNavigatorFileOrder - - func test_PBXNavigatorFileOrder_unsorted_when_iosProject() { - XCTAssertNil(PBXNavigatorFileOrder.unsorted.sort) - } + func test_PBXFileOrder_PBXBuildFile_by_filename_when_iosProject() throws { + let iosProject = try iosProject() - func test_PBXNavigatorFileOrder_by_filename_when_iosProject() throws { - let iosProject = try 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() 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(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileAssets, rhs: iosProject.objectBuildFileMain)) + XCTAssertFalse(PBXFileOrder.byFilename.sort(lhs: iosProject.objectBuildFileMain, rhs: iosProject.objectBuildFileAssets)) + } - XCTAssertTrue(PBXBuildPhaseFileOrder.byFilename.sort!(iosProject.buildFileAssets, iosProject.buildFileMain)) - XCTAssertFalse(PBXBuildPhaseFileOrder.byFilename.sort!(iosProject.buildFileMain, iosProject.buildFileAssets)) - } - - // MARK: - Private + func test_PBXFileOrder_PBXBuildFile_by_filename_when_fileSharedAcrossTargetsProject() throws { + let fileSharedAcrossTargetsProject = try fileSharedAcrossTargetsProject() - struct iOSProject { - var proj: PBXProj! - var buildFileAssets: PBXBuildFile! - var buildFileMain: PBXBuildFile! + let sameNameByFilename = fileSharedAcrossTargetsProject.objectBuildFileSameName.sorted(by: PBXFileOrder.byFilename.sort) + XCTAssertLessThan(sameNameByFilename.first!.1.uuid, sameNameByFilename.last!.1.uuid) + } - var objectBuildFileAssets: (PBXObjectReference, PBXBuildFile)! - var objectBuildFileMain: (PBXObjectReference, PBXBuildFile)! + func test_PBXFileOrder_PBXBuildFile_by_filename_when_nil_name_and_path_when_iosProject() throws { + let iosProject = try iosProject() - var objectBuildPhaseFileAssets: (PBXObjectReference, PBXBuildPhaseFile)! - var objectBuildPhaseFileMain: (PBXObjectReference, PBXBuildPhaseFile)! + 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)) + } - var fileReferenceAssets: PBXFileReference! - var fileReferenceCoreData: PBXFileReference! + func test_PBXFileOrder_PBXBuildFile_by_filename_when_no_file_when_iosProject() throws { + let iosProject = try iosProject() - var objectFileReferenceAssets: (PBXObjectReference, PBXFileReference)! - var objectFileReferenceCoreData: (PBXObjectReference, PBXFileReference)! + 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 + + 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)) + } - var groupFrameworks: PBXGroup! - var groupProducts: PBXGroup! + // 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)) + } - var objectGroupFrameworks: (PBXObjectReference, PBXGroup)! - var objectGroupProducts: (PBXObjectReference, PBXGroup)! - - var navigatorFileGroup: PBXGroup! - } + // MARK: - PBXNavigatorFileOrder + + func test_PBXNavigatorFileOrder_unsorted_when_iosProject() { + XCTAssertNil(PBXNavigatorFileOrder.unsorted.sort) + } - 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 test_PBXNavigatorFileOrder_by_filename_when_iosProject() throws { + let iosProject = try iosProject() - 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 - ) + 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/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index a707053c2..2eaa76eaf 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -1,620 +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]) - } - - // 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_fileSharedAcrossTargetsProject() throws { - try loadFileSharedAcrossTargetsProject() + let lines = lines(fromFile: encodeProject()) + XCTAssertEqual(583, lines.count) + XCTAssertEqual("// !$*UTF8*$!", lines[0]) + } - let settings = PBXOutputSettings(projFileListOrder: .byFilename) - let lines = lines(fromFile: encodeProject(settings: settings)) - var line = lines.validate(line: "/* Begin PBXBuildFile section */") + // 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) + } - 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) + 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) + } - lines.validate(line: "/* End PBXBuildFile section */", onLineAfter: line) - } + func test_buildFiles_in_filename_order_when_fileSharedAcrossTargetsProject() throws { + try loadFileSharedAcrossTargetsProject() - 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) - } + let settings = PBXOutputSettings(projFileListOrder: .byFilename) + let lines = lines(fromFile: encodeProject(settings: settings)) + var line = lines.validate(line: "/* Begin PBXBuildFile section */") - func test_file_references_in_default_uuid_order_when_fileSharedAcrossTargetsProject() throws { - try loadFileSharedAcrossTargetsProject() + 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) - 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 PBXBuildFile section */", 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_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_default_uuid_order_when_fileSharedAcrossTargetsProject() throws { + try loadFileSharedAcrossTargetsProject() - 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) - } + 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) - // 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) - } + lines.validate(line: "/* End PBXFileReference section */", onLineAfter: 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_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_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_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: - File system synchronized root groups + // 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_fileSystemSynchronizedRootGroups_when_projectWithFileSystemSynchronizedRootGroups() throws { - // Given - try loadSynchronizedRootGroups() - let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst) - let lines = lines(fromFile: encodeProject(settings: settings)) + 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) + } - 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) - } + 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 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 root groups - // 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) - } + func test_fileSystemSynchronizedRootGroups_when_projectWithFileSystemSynchronizedRootGroups() throws { + // Given + try loadSynchronizedRootGroups() + let settings = PBXOutputSettings(projNavigatorFileOrder: .byFilenameGroupsFirst) + let lines = lines(fromFile: encodeProject(settings: settings)) - // MARK: - Build phases + 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) + } - func test_build_phase_sources_unsorted_when_iOSProject() throws { - try loadiOSProject() + // 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) + } - 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) - } + // 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 + + 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) + } - 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 a4a39e53f..570c89e8f 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjIntegrationTests.swift @@ -1,89 +1,89 @@ #if os(macOS) || (os(Linux) && compiler(>=6.1)) -import Foundation -import PathKit -import XCTest -@testable import XcodeProj + import Foundation + import PathKit + import XCTest + @testable import XcodeProj -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) + 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() 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 }) - } - 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() + } + + let fixturePath = fixturePath().parent() + let xcodeprojPath = tmpDir + "Project.xcodeproj" + try fixturePath.copy(xcodeprojPath) - 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", - ]) + 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", + ]) - // Read/write the project - let project = try XcodeProj(path: xcodeprojPath) - try project.writePBXProj(path: xcodeprojPath, outputSettings: PBXOutputSettings()) + // 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) + 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/Project/XcodeProjIntegrationTests.swift b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift index 4b522e8d8..f3bd72571 100644 --- a/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift +++ b/Tests/XcodeProjTests/Project/XcodeProjIntegrationTests.swift @@ -1,114 +1,114 @@ #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:)) + 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() 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" - } -} - #endif From c4eb84417c201df0292185e09bd7c5dfe480cb4c Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Thu, 27 Feb 2025 19:16:12 -0500 Subject: [PATCH 37/41] moar linting --- .../Objects/Project/PBXProjEncoderTests.swift | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index 2eaa76eaf..4fb2b6ec0 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -288,14 +288,14 @@ // 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)) + // 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 @@ -318,13 +318,13 @@ } // 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) @@ -340,7 +340,7 @@ line = lines.validate(line: "};", after: line) line = lines.validate(line: "/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */", after: line) } - + // MARK: - Build phases func test_build_phase_sources_unsorted_when_iOSProject() throws { From b8aeb90569d4edd73236ca466d43965d82807d05 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Fri, 28 Feb 2025 08:26:24 -0500 Subject: [PATCH 38/41] Add specific `targetReference` to `ProjectAttribute` Also removed `Encodable` conformance as we have a custom `plist` method that is used for writing. --- .../Objects/Project/ProjectAttribute.swift | 23 +++++------ Sources/XcodeProj/Utils/PlistValue.swift | 6 ++- .../Objects/Project/PBXProjectTests.swift | 38 ++++++++++++++++++- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift b/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift index d5de9398d..02a78f1d7 100644 --- a/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift +++ b/Sources/XcodeProj/Objects/Project/ProjectAttribute.swift @@ -1,6 +1,7 @@ -public enum ProjectAttribute: Sendable, Equatable { +public enum ProjectAttribute: Equatable { case string(String) case array([String]) + case targetReference(PBXObject) case attributeDictionary([String: [String: ProjectAttribute]]) public var stringValue: String? { @@ -20,7 +21,7 @@ public enum ProjectAttribute: Sendable, Equatable { } } -extension ProjectAttribute: Codable { +extension ProjectAttribute: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() @@ -33,18 +34,6 @@ extension ProjectAttribute: Codable { self = .attributeDictionary(targetAttributes) } } - - 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) - case let .attributeDictionary(attributes): - try container.encode(attributes) - } - } } extension ProjectAttribute: ExpressibleByArrayLiteral { @@ -58,3 +47,9 @@ extension ProjectAttribute: ExpressibleByStringInterpolation { self = .string(value) } } + +extension ProjectAttribute: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, [String: ProjectAttribute])...) { + self = .attributeDictionary(Dictionary(uniqueKeysWithValues: elements)) + } +} diff --git a/Sources/XcodeProj/Utils/PlistValue.swift b/Sources/XcodeProj/Utils/PlistValue.swift index fa69a0cd3..432a04b29 100644 --- a/Sources/XcodeProj/Utils/PlistValue.swift +++ b/Sources/XcodeProj/Utils/PlistValue.swift @@ -126,6 +126,8 @@ extension [String: ProjectAttribute] { 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) @@ -142,8 +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 c = value as? PlistValue { - dictionary[CommentedString(key)] = c + } else if let plistValue = value as? PlistValue { + dictionary[CommentedString(key)] = plistValue } } return .dictionary(dictionary) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift index 198586d42..9875d1cc8 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift @@ -18,7 +18,7 @@ final class PBXProjectTests: XCTestCase { attributes: ["LastUpgradeCheck": "0940"], targetAttributes: [target: ["TestTargetID": "123"]]) - project.setTargetAttributes(["custom": "abc", "TestTargetID": .string(testTarget.reference.value)], 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 ?? [:] @@ -33,6 +33,40 @@ final class PBXProjectTests: XCTestCase { 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 ?? [:] + + let expectedAttributes: [CommentedString: PlistValue] = [ + "LastUpgradeCheck": "0940", + "TargetAttributes": ["app": [ + "custom": "abc", + "TestTargetID": "test", + ]], + ] + XCTAssertEqual(attributes, expectedAttributes) + } + + func test_plistKeyAndValue_doesntReturnTargetAttributes_when_itsEmpty() throws { // Given let target = PBXTarget(name: "") @@ -48,7 +82,7 @@ final class PBXProjectTests: XCTestCase { minimizedProjectReferenceProxies: nil, mainGroup: PBXGroup()) - project.setTargetAttributes(["custom": "abc", "TestTargetID": .string(testTarget.reference.value)], target: target) + project.setTargetAttributes(["custom": "abc", "TestTargetID": .targetReference(testTarget)], target: target) // When let plist = try project.plistKeyAndValue(proj: PBXProj(), reference: "") From 5d26cf5b0348f41d0ff7b2611e0e9f7441efd1a7 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Fri, 28 Feb 2025 08:27:40 -0500 Subject: [PATCH 39/41] Linting --- Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift index 9875d1cc8..4d767bd15 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjectTests.swift @@ -50,9 +50,9 @@ final class PBXProjectTests: XCTestCase { 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. + // 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 ?? [:] @@ -65,8 +65,7 @@ final class PBXProjectTests: XCTestCase { ] XCTAssertEqual(attributes, expectedAttributes) } - - + func test_plistKeyAndValue_doesntReturnTargetAttributes_when_itsEmpty() throws { // Given let target = PBXTarget(name: "") From d04f4ffe6616c6239f9e1f7ae1e2949f6f0033d6 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sun, 2 Mar 2025 11:35:53 -0500 Subject: [PATCH 40/41] Add Hashable and literal expressibility for Tuist Tests --- .../Objects/BuildPhase/BuildFileSetting.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift index 8b7f1ff95..ccfe6fa77 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift @@ -1,4 +1,4 @@ -public enum BuildFileSetting: Sendable, Equatable { +public enum BuildFileSetting: Sendable, Equatable, Hashable { case string(String) case array([String]) @@ -41,3 +41,16 @@ extension BuildFileSetting: Codable { } } } + + +extension BuildFileSetting: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: String...) { + self = .array(elements) + } +} + +extension BuildFileSetting: ExpressibleByStringInterpolation { + public init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} From 3945b8c543cbc4ab4c22a17529d07b3154b34373 Mon Sep 17 00:00:00 2001 From: Mike Simons Date: Sun, 9 Mar 2025 09:58:07 -0400 Subject: [PATCH 41/41] Linting --- Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift index ccfe6fa77..0c2a101f1 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/BuildFileSetting.swift @@ -42,7 +42,6 @@ extension BuildFileSetting: Codable { } } - extension BuildFileSetting: ExpressibleByArrayLiteral { public init(arrayLiteral elements: String...) { self = .array(elements)