Skip to content

Commit 61fff3f

Browse files
authored
Escape bundle identifiers when creating Apple frameworks and allow passing --apple-bundle-identifier (#316)
* Escapes library names to match a CFBundleIdentifier * Allow passing --apple-bundle-identifier
1 parent eca721e commit 61fff3f

File tree

6 files changed

+79
-11
lines changed

6 files changed

+79
-11
lines changed

.changeset/gold-beans-jump.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"cmake-rn": patch
3+
"ferric-cli": patch
4+
"react-native-node-api": patch
5+
---
6+
7+
Allow passing --apple-bundle-identifier to specify the bundle identifiers used when creating Apple frameworks.

.changeset/long-regions-yawn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-native-node-api": minor
3+
---
4+
5+
Ensure proper escaping when generating a bundle identifier while creating an Apple framework

packages/cmake-rn/src/platforms/apple.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,14 @@ const xcframeworkExtensionOption = new Option(
155155
"Don't rename the xcframework to .apple.node",
156156
).default(false);
157157

158+
const appleBundleIdentifierOption = new Option(
159+
"--apple-bundle-identifier <id>",
160+
"Unique CFBundleIdentifier used for Apple framework artifacts",
161+
).default(undefined, "com.callstackincubator.node-api.{libraryName}");
162+
158163
type AppleOpts = {
159164
xcframeworkExtension: boolean;
165+
appleBundleIdentifier?: string;
160166
};
161167

162168
function getBuildPath(baseBuildPath: string, triplet: Triplet) {
@@ -233,7 +239,9 @@ export const platform: Platform<Triplet[], AppleOpts> = {
233239
}
234240
},
235241
amendCommand(command) {
236-
return command.addOption(xcframeworkExtensionOption);
242+
return command
243+
.addOption(xcframeworkExtensionOption)
244+
.addOption(appleBundleIdentifierOption);
237245
},
238246
async configure(
239247
triplets,
@@ -284,7 +292,10 @@ export const platform: Platform<Triplet[], AppleOpts> = {
284292
}),
285293
);
286294
},
287-
async build({ spawn, triplet }, { build, target, configuration }) {
295+
async build(
296+
{ spawn, triplet },
297+
{ build, target, configuration, appleBundleIdentifier },
298+
) {
288299
// We expect the final application to sign these binaries
289300
if (target.length > 1) {
290301
throw new Error("Building for multiple targets is not supported yet");
@@ -368,10 +379,11 @@ export const platform: Platform<Triplet[], AppleOpts> = {
368379
"Expected exactly one artifact",
369380
);
370381
const [artifact] = artifacts;
371-
await createAppleFramework(
372-
path.join(buildPath, artifact.path),
373-
triplet.endsWith("-darwin"),
374-
);
382+
await createAppleFramework({
383+
libraryPath: path.join(buildPath, artifact.path),
384+
versioned: triplet.endsWith("-darwin"),
385+
bundleIdentifier: appleBundleIdentifier,
386+
});
375387
}
376388
},
377389
isSupportedByHost: function (): boolean | Promise<boolean> {

packages/ferric/src/build.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ const configurationOption = new Option(
107107
.choices(["debug", "release"])
108108
.default("debug");
109109

110+
const appleBundleIdentifierOption = new Option(
111+
"--apple-bundle-identifier <id>",
112+
"Unique CFBundleIdentifier used for Apple framework artifacts",
113+
).default(undefined, "com.callstackincubator.node-api.{libraryName}");
114+
110115
export const buildCommand = new Command("build")
111116
.description("Build Rust Node-API module")
112117
.addOption(targetOption)
@@ -116,6 +121,7 @@ export const buildCommand = new Command("build")
116121
.addOption(outputPathOption)
117122
.addOption(configurationOption)
118123
.addOption(xcframeworkExtensionOption)
124+
.addOption(appleBundleIdentifierOption)
119125
.action(
120126
wrapAction(
121127
async ({
@@ -126,6 +132,7 @@ export const buildCommand = new Command("build")
126132
output: outputPath,
127133
configuration,
128134
xcframeworkExtension,
135+
appleBundleIdentifier,
129136
}) => {
130137
const targets = new Set([...targetArg]);
131138
if (apple) {
@@ -239,7 +246,10 @@ export const buildCommand = new Command("build")
239246
const frameworkPaths = await Promise.all(
240247
libraryPaths.map((libraryPath) =>
241248
// TODO: Pass true as `versioned` argument for -darwin targets
242-
createAppleFramework(libraryPath),
249+
createAppleFramework({
250+
libraryPath,
251+
bundleIdentifier: appleBundleIdentifier,
252+
}),
243253
),
244254
);
245255
const xcframeworkFilename = determineXCFrameworkFilename(
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import assert from "node:assert/strict";
2+
import { describe, it } from "node:test";
3+
4+
import { escapeBundleIdentifier } from "./apple";
5+
6+
describe("escapeBundleIdentifier", () => {
7+
it("escapes and passes through values as expected", () => {
8+
assert.equal(
9+
escapeBundleIdentifier("abc-def-123-789.-"),
10+
"abc-def-123-789.-",
11+
);
12+
assert.equal(escapeBundleIdentifier("abc_def"), "abc-def");
13+
assert.equal(escapeBundleIdentifier("abc\ndef"), "abc-def");
14+
assert.equal(escapeBundleIdentifier("\0abc"), "-abc");
15+
assert.equal(escapeBundleIdentifier("🤷"), "--"); // An emoji takes up two chars
16+
});
17+
});

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,25 @@ type XCframeworkOptions = {
1414
autoLink: boolean;
1515
};
1616

17-
export async function createAppleFramework(
18-
libraryPath: string,
17+
/**
18+
* Escapes any input to match a CFBundleIdentifier
19+
* See https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundleidentifier
20+
*/
21+
export function escapeBundleIdentifier(input: string) {
22+
return input.replace(/[^A-Za-z0-9-.]/g, "-");
23+
}
24+
25+
type CreateAppleFrameworkOptions = {
26+
libraryPath: string;
27+
versioned?: boolean;
28+
bundleIdentifier?: string;
29+
};
30+
31+
export async function createAppleFramework({
32+
libraryPath,
1933
versioned = false,
20-
) {
34+
bundleIdentifier,
35+
}: CreateAppleFrameworkOptions) {
2136
if (versioned) {
2237
// TODO: Add support for generating a Versions/Current/Resources/Info.plist convention framework
2338
throw new Error("Creating versioned frameworks is not supported yet");
@@ -39,7 +54,9 @@ export async function createAppleFramework(
3954
plist.build({
4055
CFBundleDevelopmentRegion: "en",
4156
CFBundleExecutable: libraryName,
42-
CFBundleIdentifier: `com.callstackincubator.node-api.${libraryName}`,
57+
CFBundleIdentifier: escapeBundleIdentifier(
58+
bundleIdentifier ?? `com.callstackincubator.node-api.${libraryName}`,
59+
),
4360
CFBundleInfoDictionaryVersion: "6.0",
4461
CFBundleName: libraryName,
4562
CFBundlePackageType: "FMWK",

0 commit comments

Comments
 (0)