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 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: "") } } }