-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathapple.ts
More file actions
173 lines (162 loc) · 5.31 KB
/
apple.ts
File metadata and controls
173 lines (162 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import plist from "@expo/plist";
import { spawn } from "@react-native-node-api/cli-utils";
import { determineLibraryBasename } from "../path-utils.js";
type XCframeworkOptions = {
frameworkPaths: string[];
outputPath: string;
autoLink: boolean;
};
/**
* Escapes any input to match a CFBundleIdentifier
* See https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleidentifier
*/
export function escapeBundleIdentifier(input: string) {
return input.replace(/[^A-Za-z0-9-.]/g, "-");
}
type CreateAppleFrameworkOptions = {
libraryPath: string;
versioned?: boolean;
bundleIdentifier?: string;
};
export async function createAppleFramework({
libraryPath,
versioned = false,
bundleIdentifier,
}: CreateAppleFrameworkOptions) {
if (versioned) {
// TODO: Add support for generating a Versions/Current/Resources/Info.plist convention framework
throw new Error("Creating versioned frameworks is not supported yet");
}
assert(fs.existsSync(libraryPath), `Library not found: ${libraryPath}`);
// Write a info.plist file to the framework
const libraryName = path.basename(libraryPath, path.extname(libraryPath));
const frameworkPath = path.join(
path.dirname(libraryPath),
`${libraryName}.framework`,
);
// Create the framework from scratch
await fs.promises.rm(frameworkPath, { recursive: true, force: true });
await fs.promises.mkdir(frameworkPath);
await fs.promises.mkdir(path.join(frameworkPath, "Headers"));
// Create an empty Info.plist file
await fs.promises.writeFile(
path.join(frameworkPath, "Info.plist"),
plist.build({
CFBundleDevelopmentRegion: "en",
CFBundleExecutable: libraryName,
CFBundleIdentifier: escapeBundleIdentifier(
bundleIdentifier ?? `com.callstackincubator.node-api.${libraryName}`,
),
CFBundleInfoDictionaryVersion: "6.0",
CFBundleName: libraryName,
CFBundlePackageType: "FMWK",
CFBundleShortVersionString: "1.0",
CFBundleVersion: "1",
NSPrincipalClass: "",
}),
"utf8",
);
const newLibraryPath = path.join(frameworkPath, libraryName);
// TODO: Consider copying the library instead of renaming it
await fs.promises.rename(libraryPath, newLibraryPath);
// Update the name of the library
await spawn(
"install_name_tool",
["-id", `@rpath/${libraryName}.framework/${libraryName}`, newLibraryPath],
{
outputMode: "buffered",
},
);
return frameworkPath;
}
export async function createXCframework({
frameworkPaths,
outputPath,
autoLink,
}: XCframeworkOptions) {
// Delete any existing xcframework to prevent the error:
// - A library with the identifier 'macos-arm64' already exists.
// Ideally, it would only be necessary to delete the specific platform+arch, to allow selectively building from source.
fs.rmSync(outputPath, { recursive: true, force: true });
// Xcodebuild requires the output path to end with ".xcframework"
const xcodeOutputPath =
path.extname(outputPath) === ".xcframework"
? outputPath
: `${outputPath}.xcframework`;
await spawn(
"xcodebuild",
[
"-create-xcframework",
...frameworkPaths.flatMap((frameworkPath) => {
const debugSymbolPath = frameworkPath + ".dSYM";
if (fs.existsSync(debugSymbolPath)) {
return [
"-framework",
frameworkPath,
"-debug-symbols",
debugSymbolPath,
];
} else {
return ["-framework", frameworkPath];
}
}),
"-output",
xcodeOutputPath,
],
{
outputMode: "buffered",
},
);
if (xcodeOutputPath !== outputPath) {
// Rename the xcframework to the original output path
await fs.promises.rename(xcodeOutputPath, outputPath);
}
if (autoLink) {
// Write a file to mark the xcframework is a Node-API module
// TODO: Consider including this in the Info.plist file instead
fs.writeFileSync(
path.join(outputPath, "react-native-node-api-module"),
"",
"utf8",
);
}
}
/**
* Determine the filename of the xcframework based on the framework paths.
* Ensuring that all framework paths have the same base name.
*/
export function determineXCFrameworkFilename(
frameworkPaths: string[],
extension: ".xcframework" | ".apple.node" = ".xcframework",
) {
const name = determineLibraryBasename(frameworkPaths);
return `${name}${extension}`;
}
export async function createUniversalAppleLibrary(libraryPaths: string[]) {
assert(
libraryPaths.length > 0,
"Expected at least one library to create a universal library",
);
// Determine the output path
const filenames = new Set(libraryPaths.map((p) => path.basename(p)));
assert(
filenames.size === 1,
`Expected libraries to have the same name, but got: ${[...filenames].join(
", ",
)}`,
);
const [filename] = filenames;
const lipoParentPath = fs.realpathSync(
fs.mkdtempSync(path.join(os.tmpdir(), "ferric-lipo-output-")),
);
const outputPath = path.join(lipoParentPath, filename);
await spawn("lipo", ["-create", "-output", outputPath, ...libraryPaths], {
outputMode: "buffered",
});
assert(fs.existsSync(outputPath), "Expected lipo output path to exist");
return outputPath;
}