← Back to Devlog
September 6, 2026 • DevOps & Engine Architecture

Synchronizing Headless Godot Exports with Xcode Swift Package Manager & Cross-Platform Ad-Hoc Codesigning

The Cross-Platform Release Impedance: When pushing a major release with upgraded AdMob v5.0.0 dependencies, our CI/CD pipelines ran into two severe roadblocks. On the iOS side, Xcode reported complete absence of Swift Package Manager (SPM) targets despite successful editor exports. On the itch.io desktop build side, exporting a macOS app bundle on our Ubuntu Linux runner crashed due to missing Xcode signing tools.

1. Godot's Headless Main-Loop and Silent Deferral Failures

In standard graphical development, the Godot editor runs continuously. When an EditorExportPlugin finishes generating an Xcode project, it schedules post-processing tasks (such as appending SPM package structures to project.pbxproj) using call_deferred().

However, when Godot is run from a headless command-line interface (such as a GitHub Actions worker) using:

godot --headless --path . --export-release "iOS" build/ios/MazePoint.xcodeproj

The engine immediately terminates the main loop and exits the process as soon as _export_end() returns. The scheduled deferred frames are silently discarded and never executed. To resolve this, we modified the iOS export plugin to execute the SPM pbxproj patching logic synchronously inside the main thread before Godot exits:

# Force synchronous patching during headless command line runs
func _export_end() -> void:
	if _spm_applied or _pending_export_path.is_empty():
		return
	_apply_spm(_pending_export_path, false) # Moved defer_patch to false

2. Forcing the -ObjC Linker Flag for Static Categories

Google Mobile Ads (GAD) utilizes Objective-C static libraries with extensive categories. When linked via SPM inside a clean build agent, the compiler stripped out these category symbols, leading to a long list of _OBJC_CLASS_$_GAD... undefined symbol linker errors. We solved this by using a regular expression patch on the runner to insert the -ObjC linker flag into the Xcode project’s main target:

# Force -ObjC linker flag on the generated Xcode project
find build/ios -name "project.pbxproj" -exec perl -pi -e 's/OTHER_LDFLAGS = \(/OTHER_LDFLAGS = \(\n\t\t\t\t\t"-ObjC",/g' {} +

3. Bypassing macOS Compiler Dependencies on Linux Runners

To build multi-platform packages for itch.io, our workflow runs on a cost-effective Ubuntu container. However, Godot's default macOS export preset had code-signing set to Xcode (codesign/codesign=3). This crashed the Linux container because /usr/bin/codesign is exclusive to Apple hardware.

We bypassed this dependency by switching the macOS preset's signing engine to Built-In / Ad-Hoc (codesign/codesign=1). This utilizes Godot's integrated cross-platform signing engine, which performs ad-hoc Mach-O signing directly in memory on Linux, freeing the pipeline from Mac compilation requirements.

By shifting from deferred to synchronous pipeline scripting, employing global linker overrides, and employing cross-platform ad-hoc codesigning, we successfully achieved fully automated, headless store-deployments across all three platforms.