From 9a8e010e8261a01bce82c61f57ee91067bcdc788 Mon Sep 17 00:00:00 2001 From: Michalis Karagiorgos Date: Wed, 26 Nov 2025 16:34:24 +0200 Subject: [PATCH 1/3] fix https://github.com/tuist/XcodeProj/issues/921 --- .../Test.xcodeproj/project.pbxproj | 61 +++++++++---------- .../xcshareddata/xcschemes/App.xcscheme | 2 +- .../BuildPhase/PBXShellScriptBuildPhase.swift | 6 +- Sources/XcodeProj/Utils/Either.swift | 31 ++++++++++ .../PBXShellScriptBuildPhaseTests.swift | 10 --- .../Objects/Project/PBXProjEncoderTests.swift | 4 ++ Tests/XcodeProjTests/Tests/Fixtures.swift | 5 ++ 7 files changed, 73 insertions(+), 46 deletions(-) create mode 100644 Sources/XcodeProj/Utils/Either.swift diff --git a/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/project.pbxproj b/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/project.pbxproj index 86898189c..9194eb0ae 100644 --- a/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/project.pbxproj +++ b/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 90; objects = { /* Begin PBXBuildFile section */ @@ -48,15 +48,13 @@ /* Begin PBXCopyFilesBuildPhase section */ BF31C3A479ECD7CBA0640A2F /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; dstPath = ""; - dstSubfolderSpec = 10; + dstSubfolder = Frameworks; files = ( C2317A164A6A4F635FE4CCD7 /* Framework1.framework in Embed Frameworks */, 3108C05C7B0C4FD6AC077294 /* Framework2.framework in Embed Frameworks */, ); name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -67,18 +65,15 @@ 6D728BF5C49E97251DA378AE /* Framework2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Framework2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 87A3E8A0727A99EE88ED4E64 /* Framework1.xcodeproj */ = {isa = PBXFileReference; explicitFileType = "wrapper.pb-project"; includeInIndex = 0; name = Framework1.xcodeproj; path = ../Framework1/Framework1.xcodeproj; sourceTree = SOURCE_ROOT; }; E203E65EDD1A90FCBAA42C9E /* App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = App.app; sourceTree = BUILT_PRODUCTS_DIR; }; - F09268C492FF78A6C82868C6 /* App-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "App-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ B27C7CF4B617049554976EFD /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( 5D8C48ABB7D5D013DDBD67AA /* Framework1.framework in Frameworks */, C57CB036110A3D5DCC9E437A /* Framework2.framework in Frameworks */, ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ @@ -100,14 +95,6 @@ name = Products; sourceTree = ""; }; - 20C83B4860AC239B5E5FDF3C /* InfoPlists */ = { - isa = PBXGroup; - children = ( - F09268C492FF78A6C82868C6 /* App-Info.plist */, - ); - path = InfoPlists; - sourceTree = ""; - }; 30CD57449DD0BAD1F51809C3 /* Products */ = { isa = PBXGroup; children = ( @@ -160,6 +147,7 @@ F1BC716AD69EACCBD2A2DDBC /* Resources */, BF31C3A479ECD7CBA0640A2F /* Embed Frameworks */, B27C7CF4B617049554976EFD /* Frameworks */, + 0579081E2ED74460006A6AFB /* custom script */, ); buildRules = ( ); @@ -168,8 +156,6 @@ CD591349E38A6A3EB5AA81DD /* PBXTargetDependency */, ); name = App; - packageProductDependencies = ( - ); productName = App; productReference = E203E65EDD1A90FCBAA42C9E /* App.app */; productType = "com.apple.product-type.application"; @@ -181,9 +167,9 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1640; }; buildConfigurationList = C94346B8B75719D8319921C2 /* Build configuration list for PBXProject "Test" */; - compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -191,6 +177,7 @@ en, ); mainGroup = 0767F0D2E9F7C0B23673A1B1; + preferredProjectObjectVersion = 90; productRefGroup = 73B13978C93862E5AD69BCC8 /* Products */; projectDirPath = ""; projectReferences = ( @@ -230,21 +217,31 @@ /* Begin PBXResourcesBuildPhase section */ F1BC716AD69EACCBD2A2DDBC /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 0579081E2ED74460006A6AFB /* custom script */ = { + isa = PBXShellScriptBuildPhase; + name = "custom script"; + shellPath = /bin/sh; + shellScript = ( + "# comment", + "ls -la", + "ls -la", + "", + ); + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 2053EE4C6238D6C3AEB2ADB3 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( D2AE54DED6608D5B956A5E45 /* AppDelegate.swift in Sources */, ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ @@ -262,7 +259,7 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 0B98A64D6F621FDCEEA0CE44 /* Debug */ = { + 0B98A64D6F621FDCEEA0CE44 /* Debug configuration for PBXNativeTarget "App" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -287,7 +284,7 @@ }; name = Debug; }; - 58BAFCCFAE3AD62113BBEDEF /* Debug */ = { + 58BAFCCFAE3AD62113BBEDEF /* Debug configuration for PBXProject "Test" */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -326,6 +323,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -346,7 +344,7 @@ }; name = Debug; }; - 5904C1CCA86A83838723C772 /* Release */ = { + 5904C1CCA86A83838723C772 /* Release configuration for PBXProject "Test" */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -385,6 +383,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -399,7 +398,7 @@ }; name = Release; }; - 5E15950A8DD3B82ED1ED7849 /* Release */ = { + 5E15950A8DD3B82ED1ED7849 /* Release configuration for PBXNativeTarget "App" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -429,19 +428,17 @@ BAA38452720711B406475F28 /* Build configuration list for PBXNativeTarget "App" */ = { isa = XCConfigurationList; buildConfigurations = ( - 0B98A64D6F621FDCEEA0CE44 /* Debug */, - 5E15950A8DD3B82ED1ED7849 /* Release */, + 0B98A64D6F621FDCEEA0CE44 /* Debug configuration for PBXNativeTarget "App" */, + 5E15950A8DD3B82ED1ED7849 /* Release configuration for PBXNativeTarget "App" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C94346B8B75719D8319921C2 /* Build configuration list for PBXProject "Test" */ = { isa = XCConfigurationList; buildConfigurations = ( - 58BAFCCFAE3AD62113BBEDEF /* Debug */, - 5904C1CCA86A83838723C772 /* Release */, + 58BAFCCFAE3AD62113BBEDEF /* Debug configuration for PBXProject "Test" */, + 5904C1CCA86A83838723C772 /* Release configuration for PBXProject "Test" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ diff --git a/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/xcshareddata/xcschemes/App.xcscheme b/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/xcshareddata/xcschemes/App.xcscheme index aefdccd72..fc8b076c1 100644 --- a/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/xcshareddata/xcschemes/App.xcscheme +++ b/Fixtures/Xcode16ProjectReferenceOrder/Test.xcodeproj/xcshareddata/xcschemes/App.xcscheme @@ -1,6 +1,6 @@ ? /// Show environment variables in the logs. public var showEnvVarsInLog: Bool @@ -54,7 +54,7 @@ public final class PBXShellScriptBuildPhase: PBXBuildPhase { inputFileListPaths: [String]? = nil, outputFileListPaths: [String]? = nil, shellPath: String = "/bin/sh", - shellScript: String? = nil, + shellScript: Either? = nil, buildActionMask: UInt = defaultBuildActionMask, runOnlyForDeploymentPostprocessing: Bool = false, showEnvVarsInLog: Bool = true, @@ -122,7 +122,7 @@ extension PBXShellScriptBuildPhase: PlistSerializable { } dictionary["outputPaths"] = .array(outputPaths.map { .string(CommentedString($0)) }) if let shellScript { - dictionary["shellScript"] = .string(CommentedString(shellScript)) + dictionary["shellScript"] = .string(CommentedString(shellScript.toString())) } if let dependencyFile { dictionary["dependencyFile"] = .string(CommentedString(dependencyFile)) diff --git a/Sources/XcodeProj/Utils/Either.swift b/Sources/XcodeProj/Utils/Either.swift new file mode 100644 index 000000000..f8c853add --- /dev/null +++ b/Sources/XcodeProj/Utils/Either.swift @@ -0,0 +1,31 @@ +import Foundation + +public enum Either { + case left(L) + case right(R) +} + +extension Either: Decodable where L: Decodable, R: Decodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let leftValue = try? container.decode(L.self) { + self = .left(leftValue) + } else { + let rightValue = try container.decode(R.self) + self = .right(rightValue) + } + } +} + +extension Either where L == String, R == [String] { + public func toString() -> String { + switch self { + case .left(let string): + return string + case .right(let stringArray): + return stringArray.joined(separator: "\n") + } + } +} + +extension Either: Equatable where L: Equatable, R: Equatable {} diff --git a/Tests/XcodeProjTests/Objects/BuildPhase/PBXShellScriptBuildPhaseTests.swift b/Tests/XcodeProjTests/Objects/BuildPhase/PBXShellScriptBuildPhaseTests.swift index 18de54b2c..8a9282387 100644 --- a/Tests/XcodeProjTests/Objects/BuildPhase/PBXShellScriptBuildPhaseTests.swift +++ b/Tests/XcodeProjTests/Objects/BuildPhase/PBXShellScriptBuildPhaseTests.swift @@ -73,14 +73,4 @@ final class PBXShellScriptBuildPhaseTests: XCTestCase { XCTAssertNotEqual(noDiscovery, discovery) } - - private func testDictionary() -> [String: Any] { - [ - "files": ["files"], - "inputPaths": ["input"], - "outputPaths": ["output"], - "shellPath": "shellPath", - "shellScript": "shellScript", - ] - } } diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index 61bf70205..9233b5b16 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -554,6 +554,10 @@ private func loadProjectWithWrongProjectReferencesOrder() throws { proj = try PBXProj(data: projectWithWrongProjectReferencesOrder()) } + + private func loadProjectWithShellScriptBuildPhase() throws { + proj = try PBXProj(data: projectWithCustomShellScript()) + } } // MARK: - Line validations diff --git a/Tests/XcodeProjTests/Tests/Fixtures.swift b/Tests/XcodeProjTests/Tests/Fixtures.swift index ed9dfe919..32a0e24fd 100644 --- a/Tests/XcodeProjTests/Tests/Fixtures.swift +++ b/Tests/XcodeProjTests/Tests/Fixtures.swift @@ -50,3 +50,8 @@ func projectWithWrongProjectReferencesOrder() throws -> Data { let iosProjectWithProjectReferences = fixturesPath() + "Xcode16ProjectReferenceOrder/Wrong.xcodeproj/project.pbxproj" return try Data(contentsOf: iosProjectWithProjectReferences.url) } + +func projectWithCustomShellScript() throws -> Data { + let iosProjectWithShellScript = fixturesPath() + "Xcode16ProjectReferenceOrder/Test.xcodeproj/project.pbxproj" + return try Data(contentsOf: iosProjectWithShellScript.url) +} From 07ed2fc633fd3b35a38d9b625ba98ae430e32b6c Mon Sep 17 00:00:00 2001 From: Michalis Karagiorgos Date: Wed, 26 Nov 2025 17:02:37 +0200 Subject: [PATCH 2/3] fix wrong indentation --- .../XcodeProj/Objects/BuildPhase/PBXShellScriptBuildPhase.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XcodeProj/Objects/BuildPhase/PBXShellScriptBuildPhase.swift b/Sources/XcodeProj/Objects/BuildPhase/PBXShellScriptBuildPhase.swift index d2957bcdb..d2bb24f9b 100644 --- a/Sources/XcodeProj/Objects/BuildPhase/PBXShellScriptBuildPhase.swift +++ b/Sources/XcodeProj/Objects/BuildPhase/PBXShellScriptBuildPhase.swift @@ -122,7 +122,7 @@ extension PBXShellScriptBuildPhase: PlistSerializable { } dictionary["outputPaths"] = .array(outputPaths.map { .string(CommentedString($0)) }) if let shellScript { - dictionary["shellScript"] = .string(CommentedString(shellScript.toString())) + dictionary["shellScript"] = .string(CommentedString(shellScript.toString())) } if let dependencyFile { dictionary["dependencyFile"] = .string(CommentedString(dependencyFile)) From a3bbc5220b8bda19bf8fe12cb2395fdc2b1ecffb Mon Sep 17 00:00:00 2001 From: Michalis Karagiorgos Date: Wed, 26 Nov 2025 17:18:55 +0200 Subject: [PATCH 3/3] improve formatting for Either.swift --- Sources/XcodeProj/Utils/Either.swift | 30 +++++++++---------- .../Objects/Project/PBXProjEncoderTests.swift | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Sources/XcodeProj/Utils/Either.swift b/Sources/XcodeProj/Utils/Either.swift index f8c853add..3df65d6d3 100644 --- a/Sources/XcodeProj/Utils/Either.swift +++ b/Sources/XcodeProj/Utils/Either.swift @@ -6,26 +6,26 @@ public enum Either { } extension Either: Decodable where L: Decodable, R: Decodable { - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if let leftValue = try? container.decode(L.self) { - self = .left(leftValue) - } else { - let rightValue = try container.decode(R.self) - self = .right(rightValue) - } + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let leftValue = try? container.decode(L.self) { + self = .left(leftValue) + } else { + let rightValue = try container.decode(R.self) + self = .right(rightValue) } + } } extension Either where L == String, R == [String] { - public func toString() -> String { - switch self { - case .left(let string): - return string - case .right(let stringArray): - return stringArray.joined(separator: "\n") - } + public func toString() -> String { + switch self { + case .left(let string): + string + case .right(let stringArray): + stringArray.joined(separator: "\n") } + } } extension Either: Equatable where L: Equatable, R: Equatable {} diff --git a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift index 9233b5b16..af5233ce0 100644 --- a/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift +++ b/Tests/XcodeProjTests/Objects/Project/PBXProjEncoderTests.swift @@ -557,7 +557,7 @@ private func loadProjectWithShellScriptBuildPhase() throws { proj = try PBXProj(data: projectWithCustomShellScript()) - } + } } // MARK: - Line validations