Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/gold-beans-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"cmake-rn": patch
"ferric-cli": patch
"react-native-node-api": patch
---

Allow passing --apple-bundle-identifier to specify the bundle identifiers used when creating Apple frameworks.
5 changes: 5 additions & 0 deletions .changeset/long-regions-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-node-api": minor
---

Ensure proper escaping when generating a bundle identifier while creating an Apple framework
24 changes: 18 additions & 6 deletions packages/cmake-rn/src/platforms/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,14 @@ const xcframeworkExtensionOption = new Option(
"Don't rename the xcframework to .apple.node",
).default(false);

const appleBundleIdentifierOption = new Option(
"--apple-bundle-identifier <id>",
"Unique CFBundleIdentifier used for Apple framework artifacts",
).default(undefined, "com.callstackincubator.node-api.{libraryName}");

type AppleOpts = {
xcframeworkExtension: boolean;
appleBundleIdentifier?: string;
};

function getBuildPath(baseBuildPath: string, triplet: Triplet) {
Expand Down Expand Up @@ -233,7 +239,9 @@ export const platform: Platform<Triplet[], AppleOpts> = {
}
},
amendCommand(command) {
return command.addOption(xcframeworkExtensionOption);
return command
.addOption(xcframeworkExtensionOption)
.addOption(appleBundleIdentifierOption);
},
async configure(
triplets,
Expand Down Expand Up @@ -284,7 +292,10 @@ export const platform: Platform<Triplet[], AppleOpts> = {
}),
);
},
async build({ spawn, triplet }, { build, target, configuration }) {
async build(
{ spawn, triplet },
{ build, target, configuration, appleBundleIdentifier },
) {
// We expect the final application to sign these binaries
if (target.length > 1) {
throw new Error("Building for multiple targets is not supported yet");
Expand Down Expand Up @@ -368,10 +379,11 @@ export const platform: Platform<Triplet[], AppleOpts> = {
"Expected exactly one artifact",
);
const [artifact] = artifacts;
await createAppleFramework(
path.join(buildPath, artifact.path),
triplet.endsWith("-darwin"),
);
await createAppleFramework({
libraryPath: path.join(buildPath, artifact.path),
versioned: triplet.endsWith("-darwin"),
bundleIdentifier: appleBundleIdentifier,
});
}
},
isSupportedByHost: function (): boolean | Promise<boolean> {
Expand Down
12 changes: 11 additions & 1 deletion packages/ferric/src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ const configurationOption = new Option(
.choices(["debug", "release"])
.default("debug");

const appleBundleIdentifierOption = new Option(
"--apple-bundle-identifier <id>",
"Unique CFBundleIdentifier used for Apple framework artifacts",
).default(undefined, "com.callstackincubator.node-api.{libraryName}");

export const buildCommand = new Command("build")
.description("Build Rust Node-API module")
.addOption(targetOption)
Expand All @@ -116,6 +121,7 @@ export const buildCommand = new Command("build")
.addOption(outputPathOption)
.addOption(configurationOption)
.addOption(xcframeworkExtensionOption)
.addOption(appleBundleIdentifierOption)
.action(
wrapAction(
async ({
Expand All @@ -126,6 +132,7 @@ export const buildCommand = new Command("build")
output: outputPath,
configuration,
xcframeworkExtension,
appleBundleIdentifier,
}) => {
const targets = new Set([...targetArg]);
if (apple) {
Expand Down Expand Up @@ -239,7 +246,10 @@ export const buildCommand = new Command("build")
const frameworkPaths = await Promise.all(
libraryPaths.map((libraryPath) =>
// TODO: Pass true as `versioned` argument for -darwin targets
createAppleFramework(libraryPath),
createAppleFramework({
libraryPath,
bundleIdentifier: appleBundleIdentifier,
}),
),
);
const xcframeworkFilename = determineXCFrameworkFilename(
Expand Down
17 changes: 17 additions & 0 deletions packages/host/src/node/prebuilds/apple.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { escapeBundleIdentifier } from "./apple";

describe("escapeBundleIdentifier", () => {
it("escapes and passes through values as expected", () => {
assert.equal(
escapeBundleIdentifier("abc-def-123-789.-"),
"abc-def-123-789.-",
);
assert.equal(escapeBundleIdentifier("abc_def"), "abc-def");
assert.equal(escapeBundleIdentifier("abc\ndef"), "abc-def");
assert.equal(escapeBundleIdentifier("\0abc"), "-abc");
assert.equal(escapeBundleIdentifier("🤷"), "--"); // An emoji takes up two chars
});
});
25 changes: 21 additions & 4 deletions packages/host/src/node/prebuilds/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,25 @@ type XCframeworkOptions = {
autoLink: boolean;
};

export async function createAppleFramework(
libraryPath: string,
/**
* 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");
Expand All @@ -39,7 +54,9 @@ export async function createAppleFramework(
plist.build({
CFBundleDevelopmentRegion: "en",
CFBundleExecutable: libraryName,
CFBundleIdentifier: `com.callstackincubator.node-api.${libraryName}`,
CFBundleIdentifier: escapeBundleIdentifier(
bundleIdentifier ?? `com.callstackincubator.node-api.${libraryName}`,
),
CFBundleInfoDictionaryVersion: "6.0",
CFBundleName: libraryName,
CFBundlePackageType: "FMWK",
Expand Down
Loading