JaneaSystems / nodejs-mobile

Full-fledged Node.js on Android and iOS
https://code.janeasystems.com/nodejs-mobile
Other
2.57k stars 182 forks source link

Neon Rust bindings support #193

Closed stoically closed 4 years ago

stoically commented 5 years ago

neon compiles Rust code to native nodejs modules and provides bindings to work with those native modules. Would be awesome if nodejs-mobile could support neon compiled native modules.

Notes

"Related" issues over at neon:

Also, maybe it's already possible by manually building https://github.com/janeasystems/nodejs-mobile/issues/173 the neon module and providing it to nodejs-mobile? I've just naively tried to copy over my neon-build module into the nodejs-mobile node_modules folder, but that makes react-native run-android fail with ':app:packageDebug'. > org.gradle.tooling.BuildException (no error message).

stoically commented 5 years ago

Did some digging, tl;dr is it builds but crashes without error when trying to run.

Prerequisites

Findings

neon projects don't have a bindings.gyp, so it's needed to put a BUILD_NATIVE_MODULES.txt with content 1 into the nodejs-assets directory. That will trigger the neon build if the neon project has additionally an "install": "neon build" scripts entry in the package.json.

react-native run-android will successfully build, put the compiled neon native module into build/nodejs-native-assets/nodejs-native-assets-{abi}/node_modules/neon-test/native/index.node and then packages the native/index.node into the apk and deploys it to the emulator.

Errors

Requiring the neon-test project inside the nodejs-assets/nodejs-project/main.js and nodejs.starting from react-native will crash the app without an error message. Not sure how to debug further. Any pointers would be appreciated.

Trying to build the apk with ./gradlew assembleRelease will fail if there's already a native/index.node and native/target in the neon-test node_modules directory, which is the case if npm install is executed in the nodejs-assets/nodejs-project with the "install": "neon build" script in the package.json. It's needed to remove native/index.node and native/target first to have a successful build.

stoically commented 5 years ago

So I realized that I can check the Android Studio Logcat and there's actually an error:

11909-11956/com.neontest E/NODEJS-MOBILE: internal/modules/cjs/loader.js:717
      return process.dlopen(module, path.toNamespacedPath(filename));
                     ^

  Error: dlopen failed: cannot locate symbol "node_module_register" referenced by "/data/data/com.neontest/files/nodejs-project/node_modules/neon-test/native/index.node"...
stacktrace at Object.Module._extensions..node (internal/modules/cjs/loader.js:717:18) at Module.load (internal/modules/cjs/loader.js:598:32) at tryModuleLoad (internal/modules/cjs/loader.js:537:12) at Function.Module._load (internal/modules/cjs/loader.js:529:3) at Module.require (internal/modules/cjs/loader.js:636:17) at require (internal/modules/cjs/helpers.js:20:18) at Object. (/data/data/com.neontest/files/nodejs-project/node_modules/neon-test/lib/index.js:1:75) at Module._compile (internal/modules/cjs/loader.js:688:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10) at Module.load (internal/modules/cjs/loader.js:598:32) 11909-11914/com.neontest A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x5d8 in tid 11914 (Jit thread pool), pid 11909 (com.neontest)

To actually get neon to build successfully, it's needed to set some rust cargo build environment variables and have the neon-cli respect the CARGO_BUILD_TARGET. I've opened PRs regarding that here:

(I've pushed the neon change to a fork, so it's possible to add npx neon-cli@stoically/neon#neon-cli-dist build as install script)

Also, rust needs the targets installed, on linux these are:

rustup target add i686-linux-android
rustup target add arm-linux-androideabi
rustup target add armv7-linux-androideabi
rustup target add aarch64-linux-android
rustup target add x86_64-linux-android
jaimecbernardo commented 5 years ago

There's also the matter of having to link the final binary with nodejs-mobile for it to load successfully at runtime (this might be why the dlopen failed error appeared) : https://github.com/janeasystems/nodejs-mobile-gyp/issues/4

It's working now, then?

stoically commented 5 years ago

Thanks for the hint. I've tried to let neon use the nodejs-mobile fork of node-gyp by putting /path/to/nodejs-mobile-gyp/bin/nody-gyp.js build --nodedir=/path/to/nodejs-mobile-react-native/android/libnode into the neon-runtime package.json where currently node-gyp is called. It again builds successfully, but unfortunately still seeing the dlopen failed error. Maybe you got an idea on how to debug this further?

stoically commented 5 years ago

Here's the output from nodejs-mobile-gyp configure and build (run by neon-runtime):

gyp configure ``` Output { status: ExitStatus(ExitStatus(0)), stdout: " > @ configure-debug /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0 > /path/to/nodejs-mobile-react-native/android/../../nodejs-mobile-gyp/bin/node-gyp.js configure --verbose --debug ", stderr: "npm info it worked if it ends with ok npm verb cli [ \'/path/to/.nvm/versions/node/v10.15.3/bin/node\', npm verb cli \'/path/to/.nvm/versions/node/v10.15.3/bin/npm\', npm verb cli \'run\', npm verb cli \'configure-debug\' ] npm info using npm@6.9.0 npm info using node@v10.15.3 npm verb run-script [ \'preconfigure-debug\', npm verb run-script \'configure-debug\', npm verb run-script \'postconfigure-debug\' ] npm info lifecycle @~preconfigure-debug: @ npm info lifecycle @~configure-debug: @ gyp info it worked if it ends with ok gyp verb cli [ \'/path/to/.nvm/versions/node/v10.15.3/bin/node\', gyp verb cli \'/path/to/nodejs-mobile-gyp/bin/node-gyp.js\', gyp verb cli \'configure\', gyp verb cli \'--verbose\', gyp verb cli \'--debug\' ] gyp info using node-gyp@0.3.0 gyp info using node@10.15.3 | linux | x64 gyp verb command configure [] gyp verb check python checking for Python executable \"python2\" in the PATH gyp verb `which` succeeded python2 /usr/bin/python2 gyp verb check python version `/usr/bin/python2 -c \"import sys; print \"2.7.15 gyp verb check python version .%s.%s\" % sys.version_info[:3];\"` returned: %j gyp verb get node dir compiling against specified --nodedir dev files: /path/to/nodejs-mobile-react-native/android/libnode/ gyp verb build dir attempting to create \"build\" dir: /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/build gyp verb build dir \"build\" dir needed to be created? null gyp verb build/config.gypi creating config file gyp verb build/config.gypi writing out config file: /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/build/config.gypi gyp verb config.gypi checking for gypi file: /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/config.gypi gyp verb common.gypi checking for gypi file: /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/common.gypi gyp verb gyp gyp format was not specified; forcing \"make\" gyp info spawn /usr/bin/python2 gyp info spawn args [ \'/path/to/nodejs-mobile-gyp/gyp/gyp_main.py\', gyp info spawn args \'binding.gyp\', gyp info spawn args \'-f\', gyp info spawn args \'make-android\', gyp info spawn args \'-I\', gyp info spawn args \'/path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/build/config.gypi\', gyp info spawn args \'-I\', gyp info spawn args \'/path/to/nodejs-mobile-gyp/addon.gypi\', gyp info spawn args \'-I\', gyp info spawn args \'/path/to/nodejs-mobile-react-native/android/libnode/include/node/common.gypi\', gyp info spawn args \'-Dlibrary=shared_library\', gyp info spawn args \'-Dvisibility=default\', gyp info spawn args \'-Dnode_root_dir=/path/to/nodejs-mobile-react-native/android/libnode/\', gyp info spawn args \'-Dnode_gyp_dir=/path/to/nodejs-mobile-gyp\', gyp info spawn args \'-Dnode_lib_file=/path/to/nodejs-mobile-react-native/android/libnode/$(Configuration)/node.lib\', gyp info spawn args \'-Dmodule_root_dir=/path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0\', gyp info spawn args \'-Dnode_engine=v8\', gyp info spawn args \'--depth=.\', gyp info spawn args \'--no-parallel\', gyp info spawn args \'--generator-output\', gyp info spawn args \'build\', gyp info spawn args \'-Goutput_dir=.\' ] gyp info ok npm verb lifecycle @~configure-debug: unsafe-perm in lifecycle true npm verb lifecycle @~configure-debug: CWD: /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0 npm info lifecycle @~postconfigure-debug: @ npm verb exit [ 0, true ] npm timing npm Completed in 510ms npm info ok " } ```
gyp build ``` gyp build output Output { status: ExitStatus(ExitStatus(0)), stdout: " > @ build-debug /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0 > /path/to/nodejs-mobile-react-native/android/../../nodejs-mobile-gyp/bin/node-gyp.js build --debug make: Entering directory '/path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/build ' make: Nothing to be done for 'all '. make: Leaving directory '/path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/build ' ", stderr: "npm info it worked if it ends with ok npm verb cli [ '/path/to/.nvm/versions/node/v10.15.3/bin/node ', npm verb cli '/path/to/.nvm/versions/node/v10.15.3/bin/npm ', npm verb cli 'run ', npm verb cli 'build-debug ' ] npm info using npm@6.9.0 npm info using node@v10.15.3 npm verb run-script [ 'prebuild-debug ', 'build-debug ', 'postbuild-debug ' ] npm info lifecycle @~prebuild-debug: @ npm info lifecycle @~build-debug: @ gyp info it worked if it ends with ok gyp verb cli [ '/path/to/.nvm/versions/node/v10.15.3/bin/node ', gyp verb cli '/path/to/nodejs-mobile-gyp/bin/node-gyp.js ', gyp verb cli 'build ', gyp verb cli '--debug ' ] gyp info using node-gyp@0.3.0 gyp info using node@10.15.3 | linux | x64 gyp verb command build [] gyp verb build type Debug gyp verb architecture x86_64 gyp verb node dev dir /path/to/nodejs-mobile-react-native/android/libnode/ gyp verb `which` succeeded for `make` /usr/bin/make gyp info spawn make gyp info spawn args [ 'V=1 ', 'BUILDTYPE=Debug ', '-C ', 'build ' ] gyp info ok npm verb lifecycle @~build-debug: unsafe-perm in lifecycle true npm verb lifecycle @~build-debug: CWD: /path/to/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0 npm info lifecycle @~postbuild-debug: @ npm verb exit [ 0, true ] npm timing npm Completed in 254ms npm info ok " } ```

It says gyp verb get node dir compiling against specified --nodedir dev files: /path/to/nodejs-mobile-react-native/android/libnode/, so it seems to use the correct npm_config_ env from the gradle task. (I've removed the command-line --nodedir= mentioned in the last comment)

jaimecbernardo commented 5 years ago

Hi @stoically ,

The resulting binary files need to be linked to the nodejs-mobile shared library : https://github.com/janeasystems/nodejs-mobile/blob/2101f2096d59d2a081f64da2f10fd7385222ac8a/common.gypi#L517-L538

One way of debugging this would be to verify if the resulting ELF binary (the .node file) has a reference to the library. Here's an example, where I use the NDK's readelf tool for arm64 binaries to check it in a macOS machine:

$ANDROID_NDK_HOME/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf -d android/build/nodejs-native-assets/nodejs-native-assets-arm64-v8a/node_modules/sha3/build/Release/sha3.node | grep "NEEDED"

This gives the following output:

 0x0000000000000001 (NEEDED)             Shared library: [liblog.so]
 0x0000000000000001 (NEEDED)             Shared library: [libnode.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc++_shared.so]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so]
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]

The line for [libnode.so] is needed in order for the runtime dynamic loader in Android to find the symbols at runtime.

stoically commented 5 years ago

The neon.node binary which is generated by the neon-runtime from the node-gyp call internally indeed links to the libnode.so

$ANDROID_NDK_HOME/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-readelf -d /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/neon-runtime-0.2.0/build/Debug/obj.target/neon.node | grep "NEEDED"
 0x0000000000000001 (NEEDED)             Shared library: [liblog.so]
 0x0000000000000001 (NEEDED)             Shared library: [libnode.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc++_shared.so]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so]
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]

But that binary is not used. The neon-runtime proceeds to compile a libneon.a based on the neon.o (build from node-gyp). That libneon.a is linked into the neon-runtime crate. Finally the resulting dylib (lib<neon-project-name>.so) is copied to native/index.node.

And the resulting index.node binary, obviously, ends up not being linked against libnode.so.

$ANDROID_NDK_HOME/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-readelf -d android/build/nodejs-native-assets-temp-build/nodejs-native-assets-x86_64/nodejs-project/node_modules/neon-test/native/index.node | grep "NEEDED"
 0x0000000000000001 (NEEDED)             Shared library: [libdl.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so]

So my best guess at the moment is that it's needed to let the neon-runtime Cargo.toml point to the nodejs-mobile lib files in its link section.

stoically commented 5 years ago

Linking the libnode.so worked by putting links = "node" in the neon project native/Cargo.toml and

[target.x86_64-linux-android.node]
rustc-link-search = ["/path/to/nodejs-mobile-react-native/android/libnode/bin/x86_64"]
rustc-link-lib = ["node"]

in the native/.cargo/config

Now adb logcat tells me the following when starting the app:

05-16 20:06:45.983 16152 16200 E NODEJS-MOBILE: internal/modules/cjs/loader.js:717
05-16 20:06:45.983 16152 16200 E NODEJS-MOBILE:   return process.dlopen(module, path.toNamespacedPath(filename));
05-16 20:06:45.983 16152 16200 E NODEJS-MOBILE:                  ^
05-16 20:06:45.983 16152 16200 E NODEJS-MOBILE: 
05-16 20:06:45.983 16152 16200 E NODEJS-MOBILE: Error: Module did not self-register.

So my next best guess was that libc++_shared.so and liblog.so are needed too. I've tried linking the /path/to/android/build/standalone-toolchains/x86_64/sysroot/usr/lib/x86_64-linux-android/libc++_shared.so, but that results in

error: linking with `/path/to/android/build/standalone-toolchains/x86_64/bin/x86_64-linux-android-clang++` failed: exit code: 1
  = note: /path/to/android/build/standalone-toolchains/x86_64/bin/../lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin/ld: error: /path/to/android/build/standalone-toolchains/x86_64/sysroot/usr/lib/x86_64-linux-android/libc.a(sse2-memset-slm.o):
requires dynamic R_X86_64_PC32 reloc against '__memset_chk_fail' which may overflow at runtime;
recompile with -fPIC

I'm not sure what exactly needs to compiled with -fPIC and if using the libc++_shared.so from the toolchain is even the right approach. Maybe you got another helpful hint?

jaimecbernardo commented 5 years ago

Hi @stoically ,

The error here is Error: Module did not self-register.

Node Modules register themselves by declaring themselves through NODE_MODULE, which is a macro that defines a function that's supposed to run when you dlopen the module. The function is marked as a __attribute__((constructor)) for this behavior, so that the function is registered in the .ctor section of the ELF binary.

Here's an example of such a function being declared: https://stackoverflow.com/a/41283828/4594761

Some build systems might not include these symbols when building something from a static library, since these symbols were not referenced anywhere in the code that's using the static library. You mentioned the module is first built as a static library and then into a shared library, so it's possible there's some issues there.

There's also some changes to nodejs-mobile headers that might be related to this, in order to properly build the nodejs-mobile library for iOS (builds into a static library that is used by a Framework project. For Rust, the function might need to be static, so this change might need to be reverted in the node headers: https://github.com/janeasystems/nodejs-mobile/commit/7ee73e96c2a89914d025f4d217faef32b370fbf6 The node.h file is inside the --nodedir.

There might be some way to use the NDK's readelf (or similar) tool to see if the registered initialization function for the module you are building is properly exported in the final shared library, in order to debug this.

I hope this is helpful :)

stoically commented 5 years ago

Hey @jaimecbernardo - thanks again for another, indeed helpful, hint!

Checked the registered constructors in the final shared library with objdump -Dr -j .init_array. The index.node compiled from neon itself contains

00000000024d8468 <_ZN4node18__LOAD_NEON_MODULE17h3645e986fe67a8c5E>:

which is the responsible code for calling node_register_module.

The index.node compiled from nodejs-mobile doesn't contain an .init_array section as you suspected. I've tried changing the node.h as you suggested and did a full rebuild, but that unfortunately didn't help either. So I guess I have to figure out why the ctor is not included in the final library. I have no experience regarding compilation of static/shared libraries, so yet another suggestion on where I could start looking would be much appreciated!

jaimecbernardo commented 5 years ago

Hi @stoically ,

I'm not familiar enough with Rust's toolset to understand what could be going on, but my first step would be comparing the commands being run when compiling to run in the Desktop, if the symbols are being correctly exported to the final .so there. Whatever flag is being passed to include those symbols might not be supported in the NDK toolchain, perhaps?

cgdusek commented 5 years ago

image

Added the missing npm_config environment variables to build-settings in neon/src/build-settings.ts and neon/lib/build-settings.js and now the module registers

image

image image

cgdusek commented 5 years ago

Forgot to add that you need to also add in the android/iOS target here like so:

#[cfg_attr(target_os = "android", link_section = ".ctors")]

https://github.com/neon-bindings/neon/blob/37191e316e12bdbd8f84d3cd42739263d3312455/src/lib.rs#L87

stoically commented 4 years ago

@cgdusek Cool stuff, thanks! I guess we can close this issue then. Would you be interested in opening an PR against Neon with those changes?

cgdusek commented 4 years ago

Yeah, I will do it when I get a little bit of time. The neon runtime also needs to have the nodejs-mobile-nodegyp dependency like this. The Neon team is currently working on a different methodology for the Node bindings so I have been hesitant to PR waiting for the outcome of that work.

Screenshot_20191030_095746

staltz commented 4 years ago

I've been following this thread closely, and the tips you've put @stoically @cgdusek @jaimecbernardo have been very helpful.

I managed to get my Neon-built npm package working with nodejs-mobile, and for reference I'll list here the right configurations and changes that made it possible:

These changes to the neon repo are necessary (I will submit a PR):

──────────────────────────────────────────────────────────────────────────────────────────────────────────
modified: cli/src/build-settings.ts
──────────────────────────────────────────────────────────────────────────────────────────────────────────
@ cli/src/build-settings.ts:60 @ export default class BuildSettings {
      npm_config_disturl:           process.env.npm_config_disturl || null,
      npm_config_runtime:           process.env.npm_config_runtime || null,
      npm_config_build_from_source: process.env.npm_config_build_from_source || null,
-     npm_config_devdir:            process.env.npm_config_devdir || null
+     npm_config_devdir:            process.env.npm_config_devdir || null,
+     npm_config_node_engine:       process.env.npm_config_node_engine || null,
+     npm_config_nodedir:           process.env.npm_config_nodedir || null,
+     npm_config_node_gyp:          process.env.npm_config_node_gyp || null,
+     npm_config_platform:          process.env.npm_config_platform || null
    });
  }

──────────────────────────────────────────────────────────────────────────────────────────────────────────
modified: crates/neon-sys/build.rs
──────────────────────────────────────────────────────────────────────────────────────────────────────────
@ crates/neon-sys/build.rs:97 @ mod build {
    //
    //     gyp verb architecture ia32
    fn parse_node_arch(node_gyp_output: &str) -> String {
-       let version_regex = Regex::new(r"gyp verb architecture (?P<arch>ia32|x64)").unwrap();
+       let version_regex = Regex::new(r"gyp verb architecture (?P<arch>ia32|x64|arm|arm64)").unwrap();
        let captures = version_regex.captures(&node_gyp_output).unwrap();
        String::from(&captures["arch"])
    }
──────────────────────────────────────────────────────────────────────────────────────────────────────────
modified: src/lib.rs
──────────────────────────────────────────────────────────────────────────────────────────────────────────
@ src/lib.rs:108 @ macro_rules! register_module {
        // Mark this function as a global constructor (like C++).
        #[allow(improper_ctypes)]
        #[cfg_attr(target_os = "linux", link_section = ".ctors")]
+       #[cfg_attr(target_os = "android", link_section = ".ctors")]
        #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
        #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
        #[used]

These rustup targets need to be installed:

rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add arm-linux-androideabi

The Cargo.toml of the Neon-built npm package needs this:

diff --git a/node_modules/ssb-neon-keys/native/Cargo.toml b/node_modules/ssb-neon-keys/native/Cargo.toml
index 4e72059..6fc3dbb 100644
--- a/node_modules/ssb-neon-keys/native/Cargo.toml
+++ b/node_modules/ssb-neon-keys/native/Cargo.toml
@@ -4,6 +4,7 @@ version = "8.0.0"
 authors = ["Andre Staltz <andre@staltz.com>"]
 license = "AGPL-3.0"
 build = "build.rs"
+links = "node"
 exclude = ["artifacts.json", "index.node"]
 edition = "2018"

The Neon-built npm package needs this additional config file (it uses absolute paths, so this file should be created locally via a script):

node_modules/ssb-neon-keys/native/.cargo/config.toml

[target.aarch64-linux-android.node]
rustc-link-search = ["/home/me/absolute/path/to/project/node_modules/nodejs-mobile-react-native/android/libnode/bin/arm64-v8a"]
rustc-link-lib = ["node"]

[target.armv7-linux-androideabi.node]
rustc-link-search = ["/home/me/absolute/path/to/project/node_modules/nodejs-mobile-react-native/android/libnode/bin/armeabi-v7a"]
rustc-link-lib = ["node"]

[target.arm-linux-androideabi.node]
rustc-link-search = ["/home/me/absolute/path/to/project/node_modules/nodejs-mobile-react-native/android/libnode/bin/armeabi-v7a"]
rustc-link-lib = ["node"]

The project compiled correctly and ran successfully in runtime. Only on Android. I'm still trying to figure out iOS and would I appreciate a lot if anyone has made progress on iOS.

staltz commented 4 years ago

I just figured out iOS support! Compiled and tested on an iPad. You should assume that the following changes build on top of my previous comment, I can't guarantee that this will work if you don't apply the changes from the previous comment.

These changes to the neon repo are necessary (I will submit a PR):

──────────────────────────────────────────────────────────────────────────────────────────────────────────
modified: src/lib.rs
──────────────────────────────────────────────────────────────────────────────────────────────────────────
@ src/lib.rs:108 @ macro_rules! register_module {
        // Mark this function as a global constructor (like C++).
        #[allow(improper_ctypes)]
        #[cfg_attr(target_os = "linux", link_section = ".ctors")]
        #[cfg_attr(target_os = "android", link_section = ".ctors")]
        #[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
+       #[cfg_attr(target_os = "ios", link_section = "__DATA,__mod_init_func")]
        #[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
        #[used]

These rustup targets need to be installed:

rustup target add aarch64-apple-ios
rustup target add x86_64-apple-ios

nodejs-mobile-react-native/scripts/ios-build-native-modules.sh needs these changes to add CARGO_BUILD_TARGET (I will submit a PR):

 pushd $CODESIGNING_FOLDER_PATH/nodejs-project/
 if [ "$PLATFORM_NAME" == "iphoneos" ]
 then
-  GYP_DEFINES="OS=ios" npm_config_nodedir="$NODEJS_HEADERS_DIR" npm_config_node_gyp="$NODEJS_MOBILE_GYP_BIN_FILE" npm_config_platform="ios" npm_config_format="make-ios" npm_config_node_engine="chakracore" npm_config_arch="arm64" npm --verbose rebuild --build-from-source
+  GYP_DEFINES="OS=ios" CARGO_BUILD_TARGET="aarch64-apple-ios" npm_config_nodedir="$NODEJS_HEADERS_DIR" npm_config_node_gyp="$NODEJS_MOBILE_GYP_BIN_FILE" npm_config_platform="ios" npm_config_format="make-ios" npm_config_node_engine="chakracore" npm_config_arch="arm64" npm --verbose rebuild --build-from-source
else
-  GYP_DEFINES="OS=ios" npm_config_nodedir="$NODEJS_HEADERS_DIR" npm_config_node_gyp="$NODEJS_MOBILE_GYP_BIN_FILE" npm_config_platform="ios" npm_config_format="make-ios" npm_config_node_engine="chakracore" npm_config_arch="x64" npm --verbose rebuild --build-from-source
+  GYP_DEFINES="OS=ios" CARGO_BUILD_TARGET="x86_64-apple-ios" npm_config_nodedir="$NODEJS_HEADERS_DIR" npm_config_node_gyp="$NODEJS_MOBILE_GYP_BIN_FILE" npm_config_platform="ios" npm_config_format="make-ios" npm_config_node_engine="chakracore" npm_config_arch="x64" npm --verbose rebuild --build-from-source
 fi
 popd

Finally, the index.node files need to be converted to folders, this was important so that nodejs-mobile scripts create .framework files for these Cargo-built (not gyp-built) .node files. I did this by patching the package.json of the my neon npm packages, to add a postinstall script which runs after Cargo has built the index.node. I wrote a script in my project to automatically apply this patch after npm install. Maybe this should be part of Neon? I'm not sure. Anyway, here it is, a simple hack:

 {
   "name": "my-neon-package",
   ...
   "scripts": {
+    "postinstall": "mv native/index.node native/index && mkdir native/index.node && mv native/index native/index.node/index"
   }
 }