Improve DX of linking prebuilds #262
kraenhansen
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
Loading addons via relative paths on Node.js
Early on, we had a goal of supporting packages on NPM with existing Node-API addons.
Many existing packages use simple names for their library targets (often just "addon"), which result in an
addon.node
file somewhere in the package directory.These dynamic libraries are references by their relative path when loading (perhaps located via a helper like
bindings
), so it's easy for Node.js to disambiguate them.Library files share a global namespace in apps
A problem arise in a mobile context where library files are included in an app bundle, are no longer loaded by a relative path and therefore share a global namespace (the directory of bundled libraries).
For Apple triplets we rename library files and frameworks and rebuild XCFrameworks from the renamed files.
This is inflexible and brittle, since we're making assumptions on the internal structure of the original XCFramework, the absence of dSYM files and location of Info.plist files, etc. There are also differences between macOS frameworks (different dylib subdirectory) that we need to handle. The unknown-unknowns and adapting to format changes going forward is concerning to me.
This is not ideal.
We have similar renames of Android library files, but that seem less problematic so far.
Rethinking the solution
It seems inflexible that we don't just support linking any XCFramework with a dylib containing a Node-API addon.
Well ... we sort of already do
A library author could already today do the following:
react-native-node-api-module
file)vendored_frameworks
of a podspec andjniLibs.srcDirs
in a Gradle project.requireNodeAddon
with the original library name:otool -D <path_to_dylib>
to get the argument for an Apple binary (strip the @rpath/*.framework/` part)llvm-readelf -d <path_to_so> | grep SONAME
to get the argument for an Android binary (strip thelib
and.so
suffix)(Note, the call signature of
requireNodeAddon
is likely to change in the future).While a nice escape hatch, it does feel to me that ☝️ is a significantly degraded DX and I feel the host package's ability to discover and automatically include libraries into the app bundles is a significant DX improvement on this. For developers with a Node.js background, learning Gradle and CocoaPods is daunting - using linking via the host package skips this entirely.
Option 1) Copy library files as-is
Instead of renaming the library files as we copy them into the host package, we could copy them as-is (and hope for the best 🤞 )
In this case, we should check for duplicate names and fail early with clear instructions on how to rename at build time.
This would push the responsibility of resolving naming conflicts to the library authors.
Naming conflicts appear most frequently in Node.js examples (repeating names like "hello" and "addon") or in packages which copied their
CMakeList.txt
from other packages or (old) templates which declare an "addon" target. It may be less of an actual problem in practice.This behavior to avoid renaming, could potentially be a per-package or per-library opt-in option, i.e. renaming by default and advanced library authors, to opt-out of this behavior.
Option 2) Stop copying library files to the host package altogether
If we choose to not rename libraries, it makes less sense to copy them into the host package and instead reference them in their original location inside the dependency package:
jniLibs.srcDirs
in our Gradle script.vendored_frameworks
is an array of glob patterns resolved relative to the podspec's root).The only solution to not copying XCFrameworks to the host while still preserving the DX of "linking" them, would be by adjusting the app's Xcode project ourselves instead of relying on Cocoapods to do this. This seems a bit more involved and risky, but to would rely on the
xcodeproj
gem (which Cocoapod is using internally) or @bacons/xcode. The CocoaPods framework handling scripts are quite involved (extracting from XCFrameworks, code-resigning, etc.) - it's unclear if this complexity is needed for our use case. Taking on this responsibility would however also help us "future proof" against the inevitable chaos when React Native no longer use / support Cocoapods.Option 3) Build with better names
Regardless if we choose to "copy library files as-is" or "stop copying library files", we could help library authors get a better library name to avoid downstream conflicts:
cmake-rn
. The package names however, may not match target names (e.g.,@company/foo
→foo
), and being too strict about naming could be inflexible for experimentation or unpublished packages.PACKAGE_NAME
) withname
from the surroundingpackage.json
(escaped to avoid special chars like@
and/
) and encourage (through examples andgyp-to-cmake
output?) setting this asOUTPUT_NAME
to this injected package name throughset_target_properties
on their library target.Beta Was this translation helpful? Give feedback.
All reactions