node的native addon本质上就是动态链接库,按下细节不表。
但是常见的动态库会依赖其他第三方的动态链接库,这个过程具有传染性。如果对外发布的时候,不希望对系统环境产生依赖,那么我们需要把依赖的第三方库也打包到合适的位置,并且还要让node能找到正确的依赖。
先说简单的。
Windows
长话短说,windows会从当前目录,C:\Windows\System32等位置寻找需要使用的dll文件。由于本文的宗旨是避免对系统环境产生依赖,所以,最合适的位置就是.node文件所在的位置!
唯一难受的地方,就是三方的三方在windows上不是那么好分析,这里有几个工具可以使用
你唯一需要做的就是把工具列出来的DLL全部都拷贝到.node文件所在的位置。如果有很多DLL(几十上百个),那就参考微软的文档,可以把它们都列在应用程序安装目录的位置。
https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order
macOS
过程中会用到以下两个工具。
- otool
- install_name_tool
otool
otool的作用主要有两个
- 列出.node文件依赖的其他第三方dylib
- 查看编译产物的@rpath设置
install_name_tool的作用如下
- 修改依赖的dylib路径
- 修改rpath
- 修改dylib的id
下面以homebrew为包管理器为例进行举例:
$ otool -L node_modules/sqlite3/build/Release/node_sqlite3.node
node_modules/sqlite3/build/Release/node_sqlite3.node:
/usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib (compatibility version 9.0.0, current version 9.6.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1700.255.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)
$ otool -L /usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib
/usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib:
/usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib (compatibility version 9.0.0, current version 9.6.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.12)
/usr/local/opt/openssl@3/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
$ otool -L /usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib
/usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib:
/usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib (compatibility version 9.0.0, current version 9.6.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.12)
/usr/local/opt/openssl@3/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
$ otool -L /usr/local/opt/openssl@3/lib/libcrypto.3.dylib
/usr/local/opt/openssl@3/lib/libcrypto.3.dylib:
/usr/local/opt/openssl@3/lib/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)
- Intel的CPU下,homebrew安装的第三方依赖默认位于
/usr/local/
。 - M系列的芯片,homebrew安装的第三方依赖默认位于
/opt/homebrew/
。
当otool列出上述两个目录下的dylib时,就需要借助install_name_tool来进行修改了。
大多数情况下,系统自带的第三方依赖不需要处理,如果有兼容性问题,也不会升级系统自带的dylib,而是使用homebrew安装的可选第三方依赖。
#没有设置rpath的时候 -- 没有任何显示
$ otool -l build/Release/node_sqlite3.node | grep RPATH -B 1 -A 10
#设置rpath之后
$ otool -l build/Release/node_sqlite3.node | grep RPATH -B 1 -A 10
Load command 13
cmd LC_RPATH
cmdsize 32
path @loader_path (offset 12)
#修改依赖的第三方dylib从绝对路径到以@rpath为基准的相对路径
$ install_name_tool \
-change /usr/local/opt/sqlcipher/lib/libsqlcipher.0.dylib @rpath/libsqlcipher.0.dylib \
-add_rpath @loader_path \
build/Release/node_sqlite3.node
# install_name_tool -id 修改id和系统文件以作区分
$ install_name_tool \
-change /usr/local/opt/openssl@3/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib \
-add_rpath @loader_path \
-id libcrypto.3.dylib \
build/Release/libsqlcipher.0.dylib
...
# 完成后的成果
$ otool -L build/Release/*
build/Release/libcrypto.3.dylib:
libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.100.3)
build/Release/libsqlcipher.0.dylib:
libsqlcipher.0.dylib (compatibility version 9.0.0, current version 9.6.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.12)
@rpath/libcrypto.3.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
build/Release/node_sqlite3.node:
@rpath/libsqlcipher.0.dylib (compatibility version 9.0.0, current version 9.6.0)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1700.255.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)
这样对外发布的时候,就不依赖系统环境里的第三方dylib了。
后记:
macOS默认会校验dylib的签名,而install_name_tool会导致签名失效。
所以在向外分发的dylib【包含.node】的时候,会有概率提示SIGKILL (Code Signature Invalid)
。
有些用户系统会默认禁用签名校验com.apple.security.cs.disable-library-validation
,就不会出现这种问题,常见于黑苹果或者其他原因关闭SIP的用户。
出现这种问题,需要先清除签名,然后在install_name_tool完成修改后重新签名。
codesign --remove-signature build/Release/${NAME_SQLCIPHER}
codesign --remove-signature build/Release/${NAME_CRYPTO}
# ...
install_name_tool build/Release/node_sqlite3.node
# 默认情况下生成的.node没有LINK相关的信息,所以直接codesign --remove-ginatrue会抛错
codesign --remove-signature build/Release/node_sqlite3.node
# ...
# 重新签名,因为默认不允许加载无签名的动态链接库
codesign -s - -v build/Release/node_sqlite3.node
codesign -s - -v build/Release/${NAME_SQLCIPHER}
codesign -s - -v build/Release/${NAME_CRYPTO}