Improve DX of linking prebuilds #262
Replies: 2 comments 1 reply
-
|
(Cross-post from Discord) Here's the require "json"
Pod::Spec.new do |s|
s.name = "NativeScript"
s.version = "0.2.0"
s.summary = ""
s.homepage = "https://github.com/NativeScript/napi-ios.git"
s.author = "DjDeveloperr", "Jamie Birch"
s.source = { git: '' }
# This leads to these two symptoms:
# - `-framework NativeScript.apple.node`
# - `ld: framework 'NativeScript.apple.node' not found`
# ... so I think we have to call it a .xcframework just to keep CocoaPods happy?
#
# s.vendored_frameworks = "build/RelWithDebInfo/NativeScript.apple.node"
s.vendored_frameworks = "build/RelWithDebInfo/NativeScript.xcframework"
s.platform = :macos, '11.0'
endAs instructed, I'm currently doing this in my entrypoint: module.exports = require("react-native-node-api").requireNodeAddon("-nativescript-macos-node-api--NativeScript");... however, I'm getting this at launch: Given that the actual library looks like this (no mention of ... I'm wondering whether my entrypoint should rather be: module.exports = require("react-native-node-api").requireNodeAddon("NativeScript");However, even if I save that change, the app keeps searching under: I dunno if this is a cache thing or what. If it is cache, how can I clear it? |
Beta Was this translation helpful? Give feedback.
-
|
With #281 we're now copying Xcframeworks and updating them in-place instead of recreating them from the framework directories. This is not exactly "Option 1) Copy library files as-is", since we are actually modifying the files, but I consider the new implementation much more robust and as such, I'll close this discussion for now. We might want to add an option to copy the frameworks as-is, in the future, but my current thinking is that this will be an opt-in option in some per-app configuration of |
Beta Was this translation helpful? Give feedback.
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.nodefile 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-modulefile)vendored_frameworksof a podspec andjniLibs.srcDirsin a Gradle project.requireNodeAddonwith 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 SONAMEto get the argument for an Android binary (strip theliband.sosuffix)(Note, the call signature of
requireNodeAddonis 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.txtfrom 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.srcDirsin our Gradle script.vendored_frameworksis 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
xcodeprojgem (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) withnamefrom the surroundingpackage.json(escaped to avoid special chars like@and/) and encourage (through examples andgyp-to-cmakeoutput?) setting this asOUTPUT_NAMEto this injected package name throughset_target_propertieson their library target.Beta Was this translation helpful? Give feedback.
All reactions