From 312b4ff75b0a2407b2819d01b5ef00d7d8d3b4ff Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Thu, 8 Oct 2026 01:19:37 -0400 Subject: [PATCH] fix(ios): prefer native CocoaPods before Rosetta --- lib/services/cocoapods-service.ts | 31 ++++++-------- test/cocoapods-service.ts | 69 +++++++++++++++++++++++++++++-- 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/lib/services/cocoapods-service.ts b/lib/services/cocoapods-service.ts index 17e433feae..6ef410ce08 100644 --- a/lib/services/cocoapods-service.ts +++ b/lib/services/cocoapods-service.ts @@ -57,32 +57,27 @@ export class CocoaPodsService implements ICocoaPodsService { public async executePodInstall( projectRoot: string, - xcodeProjPath: string + xcodeProjPath: string, ): Promise { this.$logger.info("Installing pods..."); let podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod"; const args = ["install"]; if (process.platform === "darwin" && process.arch === "arm64") { - // check if pod is installed as an x86_64 binary or a native arm64 one - // we run the following: - // arch -x86_64 pod --version - // if it's an arm64 binary, we'll get something like this as a result: - // arch: posix_spawnp: pod: Bad CPU type in executable - // in which case, we should run it natively. - const res: string = await this.$childProcess - .exec("arch -x86_64 pod --version", null, { + try { + await this.$childProcess.exec(`${podTool} --version`, null, { showStderr: true, - }) - .then((res) => res.stdout + " " + res.stderr) - .catch((err) => err.message); - - if (!res.includes("Bad CPU type in executable")) { + }); + } catch (err) { + if ( + !/Bad CPU type in executable|Exec format error/i.test(err.message) + ) { + throw err; + } this.$logger.trace( - "Running on arm64 but pod is installed under rosetta2 - running pod through rosetta2" + "Native pod execution is unavailable - running pod through rosetta2", ); - args.unshift(podTool); - args.unshift("-x86_64"); + args.unshift("-x86_64", podTool); podTool = "arch"; } } @@ -92,7 +87,7 @@ export class CocoaPodsService implements ICocoaPodsService { args, "close", { cwd: projectRoot, stdio: ["pipe", process.stdout, process.stdout] }, - { throwError: false } + { throwError: false }, ); if (podInstallResult.exitCode !== 0) { diff --git a/test/cocoapods-service.ts b/test/cocoapods-service.ts index 24281b5636..af2f50b65a 100644 --- a/test/cocoapods-service.ts +++ b/test/cocoapods-service.ts @@ -820,10 +820,10 @@ end`, options?: any, execOptions?: IExecOptions ): Promise => { - if (command === "arch -x86_64 pod --version") { - // This is the command that is used to check if cocoapods is installed under Rosetta 2 + if (command.endsWith("pod --version")) { + // Native CocoaPods is available. return { - stdout: "Bad CPU type in executable", + stdout: "1.17.0", stderr: "", exitCode: 0, }; @@ -872,6 +872,69 @@ end`, }); }); + describe("on Apple Silicon", () => { + let platform: PropertyDescriptor; + let arch: PropertyDescriptor; + beforeEach(() => { + platform = Object.getOwnPropertyDescriptor(process, "platform"); + arch = Object.getOwnPropertyDescriptor(process, "arch"); + Object.defineProperty(process, "platform", { value: "darwin" }); + Object.defineProperty(process, "arch", { value: "arm64" }); + }); + afterEach(() => { + Object.defineProperty(process, "platform", platform); + Object.defineProperty(process, "arch", arch); + }); + ["pod", "sandbox-pod"].forEach((podTool) => { + [false, true].forEach((needsRosetta) => { + it(`uses ${podTool} ${needsRosetta ? "through Rosetta after a native CPU failure" : "natively even when Rosetta is installed"}`, async () => { + testInjector.resolve("config").USE_POD_SANDBOX = + podTool === "sandbox-pod"; + const childProcess = + testInjector.resolve("childProcess"); + const probes: string[] = []; + childProcess.exec = async (command: string): Promise => { + probes.push(command); + if (needsRosetta) { + throw new Error("Bad CPU type in executable"); + } + return { stdout: "1.17.0", stderr: "", exitCode: 0 }; + }; + childProcess.spawnFromEvent = async ( + command: string, + args: string[], + ): Promise => { + assert.equal(command, needsRosetta ? "arch" : podTool); + assert.deepStrictEqual( + args, + needsRosetta ? ["-x86_64", podTool, "install"] : ["install"], + ); + return { stdout: "", stderr: "", exitCode: 0 }; + }; + await cocoapodsService.executePodInstall( + projectRoot, + xcodeProjPath, + ); + assert.deepStrictEqual(probes, [`${podTool} --version`]); + }); + }); + }); + it("preserves unrelated native probe failures", async () => { + const childProcess = + testInjector.resolve("childProcess"); + childProcess.exec = async (): Promise => { + throw new Error("pod: command not found"); + }; + childProcess.spawnFromEvent = async (): Promise => { + throw new Error("unexpected Rosetta fallback"); + }; + await assert.isRejected( + cocoapodsService.executePodInstall(projectRoot, xcodeProjPath), + "pod: command not found", + ); + }); + }); + it("calls pod install spawnFromEvent with correct arguments", async () => { const childProcess = testInjector.resolve("childProcess"); let commandCalled = "";