Skip to content

Commit c2ed982

Browse files
committed
Add command to patch xcode project
1 parent 6c13df1 commit c2ed982

File tree

4 files changed

+85
-25
lines changed

4 files changed

+85
-25
lines changed

packages/host/react-native-node-api.podspec

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,7 @@ require "json"
33
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
44

55
require_relative "./scripts/patch-hermes"
6-
7-
NODE_PATH ||= `which node`.strip
8-
CLI_COMMAND ||= "'#{NODE_PATH}' '#{File.join(__dir__, "dist/node/cli/run.js")}'"
9-
COPY_FRAMEWORKS_COMMAND ||= "#{CLI_COMMAND} link --apple '#{Pod::Config.instance.installation_root}'"
10-
11-
# We need to run this now to ensure the xcframeworks are copied vendored_frameworks are considered
12-
XCFRAMEWORKS_DIR ||= File.join(__dir__, "xcframeworks")
13-
unless defined?(@xcframeworks_copied)
14-
puts "Executing #{COPY_FRAMEWORKS_COMMAND}"
15-
system(COPY_FRAMEWORKS_COMMAND) or raise "Failed to copy xcframeworks"
16-
# Setting a flag to avoid running this command on every require
17-
@xcframeworks_copied = true
18-
end
6+
require_relative "./scripts/patch-xcode-project"
197

208
if ENV['RCT_NEW_ARCH_ENABLED'] == '0'
219
Pod::UI.warn "React Native Node-API doesn't support the legacy architecture (but RCT_NEW_ARCH_ENABLED == '0')"
@@ -35,16 +23,6 @@ Pod::Spec.new do |s|
3523

3624
s.dependency "weak-node-api"
3725

38-
s.vendored_frameworks = "auto-linked/apple/*.xcframework"
39-
s.script_phase = {
40-
:name => 'Copy Node-API xcframeworks',
41-
:execution_position => :before_compile,
42-
:script => <<-CMD
43-
set -e
44-
#{COPY_FRAMEWORKS_COMMAND}
45-
CMD
46-
}
47-
4826
# Use install_modules_dependencies helper to install the dependencies (requires React Native version >=0.71.0).
4927
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
5028
if respond_to?(:install_modules_dependencies, true)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
unless defined?(@exit_hooks_installed)
2+
# Setting a flag to avoid running this command on every require
3+
@exit_hooks_installed = true
4+
5+
NODE_PATH ||= `which node`.strip
6+
CLI_COMMAND ||= "'#{NODE_PATH}' '#{File.join(__dir__, "../dist/node/cli/run.js")}'"
7+
PATCH_XCODE_PROJECT_COMMAND ||= "#{CLI_COMMAND} patch-xcode-project '#{Pod::Config.instance.installation_root}'"
8+
9+
# Using an at_exit hook to ensure the command is executed after the pod install is complete
10+
at_exit do
11+
system(PATCH_XCODE_PROJECT_COMMAND) or raise "Failed to patch the Xcode project"
12+
end
13+
end

packages/host/src/node/cli/apple.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,64 @@ import path from "node:path";
33
import fs from "node:fs";
44

55
import plist from "@expo/plist";
6+
import * as xcode from "@bacons/xcode";
7+
import * as xcodeJson from "@bacons/xcode/json";
68
import * as zod from "zod";
79

8-
import { spawn } from "@react-native-node-api/cli-utils";
10+
import { chalk, spawn } from "@react-native-node-api/cli-utils";
911

1012
import { getLatestMtime, getLibraryName } from "../path-utils.js";
1113
import {
1214
getLinkedModuleOutputPath,
1315
LinkModuleOptions,
1416
LinkModuleResult,
1517
} from "./link-modules.js";
18+
import { findXcodeProject } from "./xcode-helpers.js";
19+
20+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
21+
const CLI_PATH = path.resolve(PACKAGE_ROOT, "bin", "react-native-node-api.mjs");
22+
const BUILD_PHASE_PREFIX = "[Node-API]";
23+
const BUILD_PHASE_NAME = `${BUILD_PHASE_PREFIX} Copy, rename and sign frameworks`;
24+
25+
export async function ensureXcodeBuildPhase(fromPath: string) {
26+
// Locate the app's Xcode project
27+
const xcodeProjectPath = await findXcodeProject(fromPath);
28+
const pbxprojPath = path.join(xcodeProjectPath, "project.pbxproj");
29+
assert(
30+
fs.existsSync(pbxprojPath),
31+
`Expected a project.pbxproj file at '${pbxprojPath}'`,
32+
);
33+
const xcodeProject = xcode.XcodeProject.open(pbxprojPath);
34+
// Create a build phase on the main target to stage and rename the addon Xcframeworks
35+
const mainTarget = xcodeProject.rootObject.getMainAppTarget();
36+
assert(mainTarget, "Unable to find a main target");
37+
38+
const existingBuildPhases = mainTarget.props.buildPhases.filter((phase) =>
39+
phase.getDisplayName().startsWith(BUILD_PHASE_PREFIX),
40+
);
41+
42+
for (const existingBuildPhase of existingBuildPhases) {
43+
console.log(
44+
"Removing existing build phase:",
45+
chalk.dim(existingBuildPhase.getDisplayName()),
46+
);
47+
existingBuildPhase.removeFromProject();
48+
}
49+
50+
mainTarget.createBuildPhase(xcode.PBXShellScriptBuildPhase, {
51+
name: BUILD_PHASE_NAME,
52+
shellScript: [
53+
"set -e",
54+
`'${process.execPath}' '${CLI_PATH}' link --apple '${fromPath}'`,
55+
].join("\n"),
56+
});
57+
58+
await fs.promises.writeFile(
59+
pbxprojPath,
60+
xcodeJson.build(xcodeProject.toJSON()),
61+
"utf8",
62+
);
63+
}
1664

1765
/**
1866
* Reads and parses a plist file, converting it to XML format if needed.

packages/host/src/node/cli/program.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
import { command as vendorHermes } from "./hermes";
2727
import { packageNameOption, pathSuffixOption } from "./options";
2828
import { linkModules, pruneLinkedModules, ModuleLinker } from "./link-modules";
29-
import { linkXcframework } from "./apple";
29+
import { ensureXcodeBuildPhase, linkXcframework } from "./apple";
3030
import { linkAndroidDir } from "./android";
3131

3232
// We're attaching a lot of listeners when spawning in parallel
@@ -249,3 +249,24 @@ program
249249
});
250250
}),
251251
);
252+
253+
program
254+
.command("patch-xcode-project")
255+
.description("Patch the Xcode project to include the Node-API build phase")
256+
.argument("[path]", "Some path inside the app package", process.cwd())
257+
.action(
258+
wrapAction(async (pathInput) => {
259+
const resolvedPath = path.resolve(process.cwd(), pathInput);
260+
console.log(
261+
"Patching Xcode project in",
262+
prettyPath(resolvedPath),
263+
"to include a build phase to copy, rename and sign Node-API frameworks",
264+
);
265+
assert.equal(
266+
process.platform,
267+
"darwin",
268+
"Patching Xcode project is only supported on macOS",
269+
);
270+
await ensureXcodeBuildPhase(resolvedPath);
271+
}),
272+
);

0 commit comments

Comments
 (0)