EvanBacon/xcode

Swift Package Manager seemingly not supported

hesselbom opened this issue · 3 comments

Problem

After running npx expo prebuild --clean with an Apple Watch target using EvanBacon/expo-apple-targets I tried adding Supabase as a Swift package. This worked, but when running npx expo prebuild I get this error:

Error: [ios.xcodeProjectBeta2]: withIosXcodeProjectBeta2BaseMod: Unable to serialize object: 'AC9C55BA2BD8FD4500041977'
    at PBXBuildFile.toJSON (.../node_modules/@bacons/xcode/build/api/AbstractObject.js:119:27)
    at XcodeProject.toJSON (.../node_modules/@bacons/xcode/build/api/XcodeProject.js:195:38)
    at write (.../node_modules/@bacons/apple-targets/build/withXcparse.js:68:66)

It seems like it gets messed up when with productRef

Context

ExpoModulesProvider.swift which is a fileRef works correctly while Supabase which is a productRef causes the error.

[file: project.pbxproj]

/* Begin PBXBuildFile section */
[...]
AC9C55BB2BD8FD4500041977 /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = AC9C55BA2BD8FD4500041977 /* Supabase */; };
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
[...]
/* End PBXBuildFile section */

If I add a console.log in AbstractObject's toJSON I can see that fileRef appears to be converted to PBXFileReference while productRef is just a string ("AC9C55BA2BD8FD4500041977" in this case) so my guess is that it never converts to a valid object.

Solution

I've tried looking through the code to see how to patch this, and hopefully also add the ability to add packages like this, but am unable to find where.

Please let me know if you need additional context.

Created a barebones reproduction repo here: https://github.com/hesselbom/xcode-spm-repro

Ok, actually found the issue for the error, looks like XCSwiftPackageProductDependency and XCRemoteSwiftPackageReference were missing from KNOWN_ISA from this file:

[json.ISA.PBXReferenceProxy]: () =>
require("./PBXReferenceProxy")
.PBXReferenceProxy as typeof import("./PBXReferenceProxy").PBXReferenceProxy,
[json.ISA.PBXRezBuildPhase]: () =>
require("./PBXSourcesBuildPhase")
.PBXRezBuildPhase as typeof import("./PBXSourcesBuildPhase").PBXRezBuildPhase,
} as const;

If I add them to the end of that list, like so:

   [json.ISA.XCSwiftPackageProductDependency]: () => 
     require("./XCSwiftPackageProductDependency") 
       .XCSwiftPackageProductDependency as typeof import("./XCSwiftPackageProductDependency").XCSwiftPackageProductDependency, 
   [json.ISA.XCRemoteSwiftPackageReference]: () => 
     require("./XCRemoteSwiftPackageReference") 
       .XCRemoteSwiftPackageReference as typeof import("./XCRemoteSwiftPackageReference").XCRemoteSwiftPackageReference,

Then I don't get the error and the SPM package is still there in the Watch target, so it doesn't get overwritten. Although it changes:

AC9C55CD2BD92CD000041977 /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = AC9C55CC2BD92CD000041977 /* Supabase */; };

into "null" name:

AC9C55CD2BD92CD000041977 /* null in Frameworks */ = {isa = PBXBuildFile; productRef = AC9C55CC2BD92CD000041977 /* XCSwiftPackageProductDependency */; };

But XCode seems to build with it regardless.

Found the issue with the null name, turns out "comments" doesn't account for productRef when looking for name. Here's my complete patch so far:

patches/@bacons+xcode+1.0.0-alpha.12.patch

diff --git a/node_modules/@bacons/xcode/build/api/XcodeProject.js b/node_modules/@bacons/xcode/build/api/XcodeProject.js
index 16e8daf..59cf212 100644
--- a/node_modules/@bacons/xcode/build/api/XcodeProject.js
+++ b/node_modules/@bacons/xcode/build/api/XcodeProject.js
@@ -84,6 +84,10 @@ const KNOWN_ISA = {
         .PBXReferenceProxy,
     [json.ISA.PBXRezBuildPhase]: () => require("./PBXSourcesBuildPhase")
         .PBXRezBuildPhase,
+    [json.ISA.XCSwiftPackageProductDependency]: () => require("./XCSwiftPackageProductDependency")
+        .XCSwiftPackageProductDependency,
+    [json.ISA.XCRemoteSwiftPackageReference]: () => require("./XCRemoteSwiftPackageReference")
+        .XCRemoteSwiftPackageReference,
 };
 class XcodeProject extends Map {
     constructor(filePath, props) {
diff --git a/node_modules/@bacons/xcode/build/json/comments.js b/node_modules/@bacons/xcode/build/json/comments.js
index 9c57be3..499e86c 100644
--- a/node_modules/@bacons/xcode/build/json/comments.js
+++ b/node_modules/@bacons/xcode/build/json/comments.js
@@ -32,7 +32,7 @@ function createReferenceList(project) {
     }
     function getPBXBuildFileComment(id, buildFile) {
         const buildPhaseName = getBuildPhaseNameContainingFile(id) ?? "[missing build phase]";
-        const name = getCommentForObject(buildFile.fileRef, objects[buildFile.fileRef]);
+        const name = getCommentForObject(buildFile.fileRef || buildFile.productRef, objects[buildFile.fileRef || buildFile.productRef]);
         return `${name} in ${buildPhaseName}`;
     }
     function getCommentForObject(id, object) {
@@ -55,7 +55,7 @@ function createReferenceList(project) {
             referenceCache[id] = getBuildPhaseName(object) ?? "";
         }
         else {
-            referenceCache[id] = object.name ?? object.path ?? object.isa ?? null;
+            referenceCache[id] = object.name ?? object.productName ?? object.path ?? object.isa ?? null;
         }
         return referenceCache[id] ?? null;
     }