From 156b8f33ec249f2eb65f16bf2b943ba616be680b Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Mon, 15 Jun 2026 09:10:36 -0700 Subject: [PATCH] feat(ios): support Swift package macros --- lib/services/ios/xcodebuild-args-service.ts | 24 ++++++++++++- test/services/ios/xcodebuild-args-service.ts | 37 ++++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/services/ios/xcodebuild-args-service.ts b/lib/services/ios/xcodebuild-args-service.ts index 8eee2ea366..fe0d5de642 100644 --- a/lib/services/ios/xcodebuild-args-service.ts +++ b/lib/services/ios/xcodebuild-args-service.ts @@ -167,11 +167,17 @@ export class XcodebuildArgsService implements IXcodebuildArgsService { // Introduced in Xcode 14+ // ref: https://forums.swift.org/t/telling-xcode-14-beta-4-to-trust-build-tool-plugins-programatically/59305/5 const skipPackageValidation = "-skipPackagePluginValidation"; - + // Introduced in Xcode 15+ to trust Swift macros (compiler plugins) + // non-interactively. Required for SPM packages that ship macros + // (e.g. apple/RealityKitScripting), otherwise the build fails with: + // "Macro '...' from package '...' must be enabled before it can be used" + // ref: https://developer.apple.com/documentation/xcode/writing-swift-macros + const skipMacroValidation = "-skipMacroValidation"; const extraArgs: string[] = [ "-scheme", projectData.projectName, skipPackageValidation, + skipMacroValidation, ]; const BUILD_SETTINGS_FILE_PATH = path.join( @@ -187,6 +193,22 @@ export class XcodebuildArgsService implements IXcodebuildArgsService { // references: https://medium.com/@iostechset/why-cocoapods-eats-app-icons-79fe729808d4 // https://github.com/CocoaPods/CocoaPods/issues/7003 + // Xcode 26 makes Swift "explicitly built modules" the default. A + // regression there prevents macro/compiler-plugin SPM targets from + // resolving their swift-syntax module dependencies, failing with: + // "Unable to resolve module dependency: 'SwiftSyntax'" (and SwiftParser, + // SwiftSyntaxMacros, SwiftCompilerPlugin, SwiftDiagnostics). + // Passed as a command-line build setting so it overrides ALL targets, + // including the package targets we don't control. + // ref: https://forums.swift.org/t/xcode-26-unable-to-find-module-dependency/80516 + const explicitModulesProperty = "SWIFT_ENABLE_EXPLICIT_MODULES"; + const explicitModulesValue = + this.$xcconfigService.readPropertyValue( + BUILD_SETTINGS_FILE_PATH, + explicitModulesProperty, + ) || "NO"; + extraArgs.push(`${explicitModulesProperty}=${explicitModulesValue}`); + const deployTargetProperty = "IPHONEOS_DEPLOYMENT_TARGET"; const deployTargetVersion = this.$xcconfigService.readPropertyValue( BUILD_SETTINGS_FILE_PATH, diff --git a/test/services/ios/xcodebuild-args-service.ts b/test/services/ios/xcodebuild-args-service.ts index 66fc46f482..d2f7455f7b 100644 --- a/test/services/ios/xcodebuild-args-service.ts +++ b/test/services/ios/xcodebuild-args-service.ts @@ -11,6 +11,7 @@ function createTestInjector(data: { logLevel: string; hasProjectWorkspace: boolean; connectedDevices?: any[]; + buildXcconfigContent?: string; }): IInjector { const injector = new Yok(); injector.register("devicePlatformsConstants", DevicePlatformsConstants); @@ -19,8 +20,11 @@ function createTestInjector(data: { getDevicesForPlatform: () => data.connectedDevices || [], }); injector.register("fs", { - exists: () => data.hasProjectWorkspace, - readText: () => "", + exists: (filePath: string) => + filePath.endsWith("build.xcconfig") + ? data.buildXcconfigContent !== undefined + : data.hasProjectWorkspace, + readText: () => data.buildXcconfigContent || "", }); injector.register("logger", { getLevel: () => data.logLevel, @@ -49,7 +53,13 @@ function getCommonArgs() { } function getXcodeProjectArgs(data?: { hasProjectWorkspace: boolean }) { - const extraArgs = ["-scheme", projectName, "-skipPackagePluginValidation"]; + const extraArgs = [ + "-scheme", + projectName, + "-skipPackagePluginValidation", + "-skipMacroValidation", + "SWIFT_ENABLE_EXPLICIT_MODULES=NO", + ]; return data && data.hasProjectWorkspace ? [ "-workspace", @@ -72,6 +82,27 @@ function getBuildLoggingArgs(logLevel: string): string[] { } describe("xcodebuildArgsService", () => { + describe("getXcodeProjectArgs", () => { + it("should allow SWIFT_ENABLE_EXPLICIT_MODULES to be overridden from build.xcconfig", () => { + const injector = createTestInjector({ + logLevel: "INFO", + hasProjectWorkspace: false, + buildXcconfigContent: "SWIFT_ENABLE_EXPLICIT_MODULES = YES", + }); + const xcodebuildArgsService: IXcodebuildArgsService = injector.resolve( + "xcodebuildArgsService", + ); + + const actualArgs = xcodebuildArgsService.getXcodeProjectArgs( + { projectRoot, normalizedPlatformName }, + { projectName, appResourcesDirectoryPath }, + ); + + assert.include(actualArgs, "SWIFT_ENABLE_EXPLICIT_MODULES=YES"); + assert.notInclude(actualArgs, "SWIFT_ENABLE_EXPLICIT_MODULES=NO"); + }); + }); + describe("getBuildForSimulatorArgs", () => { _.each([true, false], (hasProjectWorkspace) => { _.each(["INFO", "TRACE"], (logLevel) => {