From d46b24930dde1e17f9b99f243c18a8865400ca0d Mon Sep 17 00:00:00 2001 From: yuhan-chen Date: Fri, 18 Sep 2026 10:00:40 +0900 Subject: [PATCH 1/2] Write an empty destination path for the bundle copy phase The generated "Copy Bundle Resources" phase omitted `dstPath`, and converting such a project to the Xcode 27.2 JSON format fails with "A copy build phase is missing a required value for destination path". Xcode-written projects carry the key with an empty path when no subpath is needed, and every other phase XcodeGen generates already does. Co-Authored-By: Claude Opus 5 (1M context) --- Sources/XcodeGenKit/PBXProjGenerator.swift | 2 ++ Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj | 1 + Tests/XcodeGenKitTests/ProjectGeneratorTests.swift | 5 +++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Sources/XcodeGenKit/PBXProjGenerator.swift b/Sources/XcodeGenKit/PBXProjGenerator.swift index 604ff473..c2a6b6a4 100644 --- a/Sources/XcodeGenKit/PBXProjGenerator.swift +++ b/Sources/XcodeGenKit/PBXProjGenerator.swift @@ -1221,6 +1221,8 @@ public class PBXProjGenerator { if !copyBundlesReferences.isEmpty { let copyBundlesPhase = addObject(PBXCopyFilesBuildPhase( + // Xcode expects the key to be present, even when the path is empty. + dstPath: "", dstSubfolderSpec: .resources, name: "Copy Bundle Resources", files: copyBundlesReferences diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj index bbfb8e41..36e8ef57 100644 --- a/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj +++ b/Tests/Fixtures/TestProject/Project.xcodeproj/project.pbxproj @@ -616,6 +616,7 @@ E8BC0F358D693454E5027ECC /* Copy Bundle Resources */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; + dstPath = ""; dstSubfolderSpec = 7; files = ( 3BBCA6F76E5F212E9C55FB78 /* BundleX.bundle in Copy Bundle Resources */, diff --git a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift index 600e4d61..17600c84 100644 --- a/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift +++ b/Tests/XcodeGenKitTests/ProjectGeneratorTests.swift @@ -998,6 +998,7 @@ class ProjectGeneratorTests: XCTestCase { let copyBundles = (copyBundlesPhase?.files ?? []) .compactMap { $0.file?.nameOrPath } try expect(Set(copyBundles)) == expectedBundlesFiles + try expect(copyBundlesPhase?.dstPath) == "" } try expect(copyFilesPhases.count) == expectedCopyFilesPhasesCount } @@ -3724,7 +3725,7 @@ class ProjectGeneratorTests: XCTestCase { // then /// XcodeGen ignores embed: false for bundles - try expectCopyPhase(in: pbxProject, withFilePaths: ["bundleA.bundle", "bundleB.bundle"], toSubFolder: .resources) + try expectCopyPhase(in: pbxProject, withFilePaths: ["bundleA.bundle", "bundleB.bundle"], toSubFolder: .resources, dstPath: "") } $0.it("ignores custom copy phase spec") { @@ -3740,7 +3741,7 @@ class ProjectGeneratorTests: XCTestCase { // then /// XcodeGen ignores embed: false for bundles - try expectCopyPhase(in: pbxProject, withFilePaths: ["bundleA.bundle", "bundleB.bundle"], toSubFolder: .resources) + try expectCopyPhase(in: pbxProject, withFilePaths: ["bundleA.bundle", "bundleB.bundle"], toSubFolder: .resources, dstPath: "") } } } From 5a4c860f308a6fdfdf1459eb57d73f3e4103d5fa Mon Sep 17 00:00:00 2001 From: yuhan-chen Date: Fri, 18 Sep 2026 11:04:07 +0900 Subject: [PATCH 2/2] Add changelog entry for #1654 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ff4ff8..483f29a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Fixed - Fix `syncedFolder` source paths being relative to the spec directory instead of the project directory when they differ, which caused Xcode to treat the synced folder as empty #1636 @Ckitakishi - Fix nested target attributes (e.g. `attributes.SystemCapabilities`) being serialized as a stringified Swift `Dictionary` description instead of a proper nested plist dictionary, which also caused non-deterministic key ordering in generated `project.pbxproj` files across runs #1639 @imadaan @sergeyospanov +- Fix generated "Copy Bundle Resources" phases missing `dstPath`, which caused Xcode 27.2 project conversion to fail #1654 @Ckitakishi ### Internal - Use a dedicated local package in the SPM fixture so generated fixtures don't depend on the checkout directory name, such as when running tests from a git worktree @yonaskolb