-
Notifications
You must be signed in to change notification settings - Fork 4
Store Apple prebuilds as .apple.node
instead of .xcframework
#52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
20aba81
e7364fe
baea882
f608f14
a61f9fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Auto-linking | ||
|
||
The `react-native-node-api-modules` package (sometimes referred to as "the host package") has mechanisms to automatically find and link prebuilt binaries with Node-API modules. | ||
|
||
When auto-linking, prebuilt binaries are copied (sometimes referred to as vendored) from dependencies of the app into the host package. As they're copied, they get renamed to avoid conflicts in naming as the library files across multiple dependency packages will be sharing a namespace when building the app. | ||
|
||
## Naming scheme of libraries when linked into the host | ||
|
||
The name of the library when linked / copied into the host is based on two things: | ||
|
||
- The package name of the encapsulating package: The directory tree is walked from the original library path to the nearest `package.json` (this is the Node-API module's package root). | ||
- The relative path of the library to the package root: | ||
- Normalized (any "lib" prefix or file extension is stripped from the filename). | ||
- Escaped (any non-alphanumeric character is replaced with "-"). | ||
|
||
## How do I link Node-API module libraries into my app? | ||
|
||
Linking will run when you `pod install` and as part of building your app with Gradle as long as your app has a dependency on the `react-native-node-api-modules` package. | ||
|
||
You can also manually link by running the following in your app directory: | ||
|
||
```bash | ||
npx react-native-node-api-modules link --android --apple | ||
``` | ||
|
||
> [!NOTE] | ||
> Because vendored frameworks must be present when running `pod install`, you have to run `pod install` if you add or remove a dependency with a Node-API module (or after creation if you're doing active development on it). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,38 @@ | ||
# Prebuilds | ||
|
||
This document will codify the naming and directory structure of prebuilt binaries, expected by the `react-native-node-api-modules` package and tools. | ||
This document codifies the naming and directory structure of prebuilt binaries, expected by the auto-linking mechanism. | ||
|
||
To align with prior art and established patterns around the distribution of Node-API modules for Node.js (and other engines supporting this), | ||
At the time of writing, our auto-linking host package (`react-native-node-api-modules`) support two kinds of prebuilds: | ||
|
||
## Apple: XCFrameworks of dynamic libraries in frameworks | ||
## `*.android.node` (for Android) | ||
|
||
A jniLibs-like directory structure of CPU-architecture specific directories containing a single `.so` library file. | ||
|
||
The name of all the `.so` library files: | ||
|
||
- must be the same across all CPU-architectures | ||
- can have a "lib" prefix, but doesn't have to | ||
- must have an `.so` or `.node` file extension | ||
|
||
> [!NOTE] | ||
> The `SONAME` doesn't have to match and is not updated as the .so is copied into the host package. | ||
> This might cause trouble if you're trying to link with the library from other native code. | ||
> We're tracking [#14](https://github.com/callstackincubator/react-native-node-api-modules/issues/14) to fix this 🤞 | ||
|
||
The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package. | ||
|
||
## `*.apple.node` (for Apple) | ||
|
||
An XCFramework of dynamic libraries wrapped in `.framework` bundles, renamed from `.xcframework` to `.apple.node` to ease discoverability. | ||
|
||
The Apple Developer documentation on ["Creating a multiplatform binary framework bundle"](https://developer.apple.com/documentation/xcode/creating-a-multi-platform-binary-framework-bundle#Avoid-issues-when-using-alternate-build-systems) mentions: | ||
|
||
> An XCFramework can include dynamic library files, but only macOS supports these libraries for dynamic linking. Dynamic linking on iOS, watchOS, and tvOS requires the XCFramework to contain .framework bundles. | ||
|
||
<!-- TODO: Write this --> | ||
The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package. | ||
|
||
## Why did we choose this naming scheme? | ||
|
||
## Android: Directory of architecture specific directories of shared object library files. | ||
To align with prior art and established patterns around the distribution of Node-API modules for Node.js, we've chosen to use the ".node" filename extension for prebuilds of Node-API modules, targeting React Native. | ||
|
||
<!-- TODO: Write this --> | ||
To enable distribution of packages with multiple co-existing platform-specific prebuilts, we've chosen to lean into the pattern of platform-specific filename extensions, used by the Metro bundler. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
Cargo.lock | ||
|
||
/*.xcframework/ | ||
/*.apple.node/ | ||
/*.android.node/ | ||
|
||
# Generated files | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,6 +91,11 @@ const noWeakNodeApiLinkageOption = new Option( | |
"Don't pass the path of the weak-node-api library from react-native-node-api-modules" | ||
); | ||
|
||
const xcframeworkExtensionOption = new Option( | ||
"--xcframework-extension", | ||
"Don't rename the xcframework to .apple.node" | ||
).default(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm adding this option, since we're using this tool to build There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I'm thinking whether this renaming should be something that the users needs to opt-in, instead of asking not to do it. Moreover, I'm a little afraid that the |
||
|
||
export const program = new Command("react-native-node-api-cmake") | ||
.description("Build React Native Node API modules with CMake") | ||
.addOption(sourcePathOption) | ||
|
@@ -104,6 +109,7 @@ export const program = new Command("react-native-node-api-cmake") | |
.addOption(ndkVersionOption) | ||
.addOption(noAutoLinkOption) | ||
.addOption(noWeakNodeApiLinkageOption) | ||
.addOption(xcframeworkExtensionOption) | ||
.action(async ({ triplet: tripletValues, ...globalContext }) => { | ||
try { | ||
const buildPath = getBuildPath(globalContext); | ||
|
@@ -212,8 +218,10 @@ export const program = new Command("react-native-node-api-cmake") | |
}) | ||
); | ||
const frameworkPaths = libraryPaths.map(createAppleFramework); | ||
const xcframeworkFilename = | ||
determineXCFrameworkFilename(frameworkPaths); | ||
const xcframeworkFilename = determineXCFrameworkFilename( | ||
frameworkPaths, | ||
globalContext.xcframeworkExtension ? ".xcframework" : ".apple.node" | ||
); | ||
|
||
// Create the xcframework | ||
const xcframeworkOutputPath = path.resolve( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Shouldn't
.node
be reserved for binary files (shared libraries)? Aren't we breaking this assumption here, by making it a directory? In the Node-API docs I've found this quote (emphasis mine):There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are definitely walking a fine line here and I agree it could cause confusion.
The "problem" is that one of the use-cases we want to support is taking an existing library (with source-code packaged in) and building the Apple and Android binaries for it.
If we were true to the spirit of the docs you referenced, we would have to override the ".node" file which was already included in the package and we couldn't use that for iOS and Android simultaneously. We would have to "swap out" the
.node
file for the right (platform+arch) at build time, just before it's copied into the app bundle. As an example, I know this is also what a tool like electron-builder is doing when it's building the final app bundle.☝️ I feel that approach is not the right one, and I would like the prebuilds for different platforms and architectures to co-exist on the filesystem.
To my understanding, Node.js has no standardized way to publish multiple
.node
files (for multiple platforms / architectures) - we could spent some more time digging into the output formats of tools like:(slightly unrelated, I found this extension point in
node-gyp-build
where a platform can implementrequire.addon
https://github.com/prebuild/node-gyp-build/blob/6822ec52423a2b3ed48ef8960a9fe05902e9e1a3/index.js#L3 ... somewhat similar to ourrequireNodeAddon
🙂)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you help me formulate a problem statement?
@shirakaba also wrote about this in this thread on discord: https://discord.com/channels/426714625279524876/1343407685462130780