XcodeProj icon indicating copy to clipboard operation
XcodeProj copied to clipboard

For some projects .pbxproj.invalidateUUIDs() on a loaded project results in corrupt .xcodeproj on .save()

Open grigorye opened this issue 5 years ago • 0 comments

Context 🕵️‍♀️

Using XcodeProj, I created a tool that enforces deterministic/changes-agnostic content of any given .xcodeproj (just by reading xcodeproj, invoking xcodeproj.pbxproj.invalidateUUIDs() and writing xcodeproj). See https://github.com/CocoaPods/CocoaPods/issues/9359#issuecomment-596304535 for the rationale behind creating this tool.

Here's the relevant code fragment:

let projectPath = Path(CommandLine.arguments[1])
let xcodeproj = try XcodeProj(path: projectPath)
xcodeproj.pbxproj.invalidateUUIDs()
try xcodeproj.write(path: projectPath)

What 🌱

While the tool works, at least in case of CocoaPods-generated Pods.xcodeproj, it produces invalid .xcodeproj where rootObject retains the original UUID, while the "updated" "Project object" has new UUID.

...<omitted>
/* Begin PBXProject section */
		78354C12450DCB4287EDC0E2 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 1100;
				LastUpgradeCheck = 1100;
				TargetAttributes = {
				};
			};
...<omitted>
	rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */;
}

This manifests itself like an error on attempt to reapply the tool 2nd time on the already "processed" xcodeproj:

Fatal error: Error raised at top level: PBXObject with reference "BFDFE7DC352907FC980B868725387E98" not found.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1100.8.280/swift/stdlib/public/core/ErrorType.swift, line 200
Illegal instruction: 4

If I manually change the rootObject reference in xcodeproj to the expected one, the tool succeeds/does not produce any change on the following invocations, as expected.

Update: it looks like it's indeed specific to something like Pods.xcodeproj generated by CococaPods. If I try to process .xcodeproj generated by XcodeGen, it does work (probably because XcodeGen itself uses tuist/XcodeProj).

Attached is a sample Podfile[.lock] + Pods/Pods.xcodeproj generated with the most recent version of CocoaPods (1.9.1), that causes the problem. To reproduce it, run the following twice:

mint run grigorye/[email protected] Pods/Pods.xcodeproj.

Pods-demo.tar.gz

Proposal 🎉

The workaround for this issue was to add xcodeproj.pbxproj.rootObject = xcodeproj.pbxproj.rootObject after the invocation of xcodeproj.pbxproj.invalidateUUIDs():

xcodeproj.pbxproj.invalidateUUIDs()
xcodeproj.pbxproj.rootObject = xcodeproj.pbxproj.rootObject
try xcodeproj.write(path: projectPath)

Apparently it should be no-op, but it's not, because setter for PBXProj.rootObject re-reads the reference from the rootObject (and "stores the object by that very reference"), that is now the "invalidated" one. Later it's regenerated as part of save, and then (I guess) written for rootObject reference with the new (expected) value.

I guess that the proper solution would be to something else, but I can not find the most appropriate place in the code to tackle it.

grigorye avatar Mar 10 '20 22:03 grigorye