Electron项目中编译基于Sqlcipher的Sqlite3
为Electron项目中的原生模块进行重编译,已经做过几次了。这次升级Electron和Sqlcipher版本,自然需要重编译下Sqlite3。结果依然花了不少时间,决定再做一次整理。
编译环境
node - 10.15.x
electron - 5.0.8
sqlite3 - 5.0.0
sqlcipher - 4.3
要解决的问题
-
环境
全局安装需要的electron版本和node-gyp
一般原生模块的文档中都会提供所需环境配置手册。如编译sqlite3需要的sqlcipher
-
分发
官方提供的配置文件,一般不会考虑分发的问题,因此依赖库都是通过动态链接。这样做的好处很显然:配置简单、生成的模块文件小。但并不适用需要分发的Electron项目,手动修改配置文件,指定依赖的静态文件是主要难点。
下载 node-sqlite3
npm i sqlite3 --ignore-scripts
之所以加上--ignore-scripts,是因为大多原生模块的脚本中都写了下载时自动执行编译的脚本,例如node-sqlite3:
"scripts": {
"install": "node-pre-gyp install --fallback-to-build",
},
复制代码
往往这时候编译环境还没准备好,而且我们需要的是基于Electron和sqlcipher的版本,自动编译的是基于当前node环境的版本。如果编译失败了,那会导致整个包都无法下载下来。
Node原生模块通过binding.gyp
文件指定在不同平台下所需要的依赖:
{
"includes": [ "deps/common-sqlite.gypi" ],
"variables": {
"sqlite%":"internal",
"sqlite_libname%":"sqlite3"
},
"targets": [
{
"target_name": "<(module_name)",
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"xcode_settings": { "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"CLANG_CXX_LIBRARY": "libc++",
"MACOSX_DEPLOYMENT_TARGET": "10.7",
},
"msvs_settings": {
"VCCLCompilerTool": { "ExceptionHandling": 1 },
},
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"],
"conditions": [
["sqlite != 'internal'", {
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")", "<(sqlite)/include" ],
"libraries": [
"-l<(sqlite_libname)"
],
"conditions": [ [ "OS=='linux'", {"libraries+":["-Wl,-rpath=<@(sqlite)/lib"]} ] ],
"conditions": [ [ "OS!='win'", {"libraries+":["-L<@(sqlite)/lib"]} ] ],
'msvs_settings': {
'VCLinkerTool': {
'AdditionalLibraryDirectories': [
'<(sqlite)/lib'
],
},
}
},
{
"dependencies": [
"<!(node -p \"require('node-addon-api').gyp\")",
"deps/sqlite3.gyp:sqlite3"
]
}
]
],
"sources": [
"src/backup.cc",
"src/database.cc",
"src/node_sqlite3.cc",
"src/statement.cc"
],
"defines": [ "NAPI_VERSION=<(napi_build_version)", "NAPI_DISABLE_CPP_EXCEPTIONS=1" ]
},
{
"target_name": "action_after_build",
"type": "none",
"dependencies": [ "<(module_name)" ],
"copies": [
{
"files": [ "<(PRODUCT_DIR)/<(module_name).node" ],
"destination": "<(module_path)"
}
]
}
]
}
复制代码
从bingding.gyp中可以看出我们所需要的依赖,这点也可以通过官方ReadMe文档获知。
在node-sqlite3的binding.gyp中,有一些执行编译时需要指定的参数:
sqlite_libname
: 依赖库,如基于sqlcipher编译时,sqlite_libname=sqlcipher
napi_build_version
: 指定napi的版本,当前指定的3
module_name
: 生成原生模块的名称,默认node_sqlite3
module_path
: 指定原生模块生成位置,这里路径必须写对,后续node引入时,sqlite3的入口文件会根据调用环境解析原生模块的路径。如在mac上编译基于Electron 5.0.x的模块时,路径应为:../lib/binding/electron-v8.3-darwin-x64
于是,手动重编译的命令:
node-gyp rebuild --runtime=electron --target=5.0.8 --sqlite_libname=sqlcipher --dist-url=https://electronjs.org/headers --module_path=../lib/binding/electron-v5.0-darwin-x64 --module_name=node_sqlit3 --napi_build_version=3
MacOS
-
HomeBrew 下载 Sqlcipher
brew install sqlcipher
笔者今年更换了MacBook Pro,homebrew等环境也都下载安装的新。发现HomeBrew已经不支持大多数option,也无法通过
brew install sqlcipher@4.x
这样的命令来安装指定版本。好在,我们这次也决定升级sqlcipher。 -
指定静态文件路径
在 binding.gyp 中,
include_dirs
和libraries
用于指定依赖库的路径。已知sqlcipher的存放路径:
/usr/local/Cella/sqlcipher
,然后将对应mac平台配置中的值进行修改:"include_dirs": [ "<!@(node -p \"require('node-addon-api').include\")", "/usr/local/Cellar/sqlcipher/4.3.0_1/include"], "conditions": [ ["sqlite != 'internal'", { "include_dirs": [ "<!@(node -p \"require('node-addon-api').include\")", "<(sqlite)/include" ], "libraries": [ "/usr/local/Cellar/sqlcipher/4.3.0_1/lib/libsqlcipher.a" ], "conditions": [ [ "OS=='linux'", {"libraries+":["-Wl,-rpath=<@(sqlite)/lib"]} ] ], "conditions": [ [ "OS!='win'", {"libraries+":["/usr/local/Cellar/sqlcipher/4.3.0_1/lib/libsqlcipher.a"]} ] ], 'msvs_settings': { 'VCLinkerTool': { 'AdditionalLibraryDirectories': [ '<(sqlite)/lib' ], }, } }, 复制代码
-
重编译
因已经将 binding.gyp 中变量 sqlite_libname的值写死,可以去掉上文编译命令中的这个参数:
node-gyp rebuild --runtime=electron --target=5.0.8 --dist-url=https://electronjs.org/headers --module_path=../lib/binding/electron-v5.0-darwin-x64 --module_name=node_sqlit3 --napi_build_version=3
-
本地测试
在Electron项目中引入编译得到的 node_sqlite3.node文件,对旧版的数据库文件进行连接测试。这里需要注意Sqlicpher不同版本带来的变化:
旧版本下的数据库文件基于Sqlcipher 3.x,在使用新编译的模块去连接操作时,若按照原语法操作,会报错。
这里有两种解决方案,具体见 Upgrading to SQLCipher 4
1)数据库文件升级
PRAGMA key = '<key material>'; PRAGMA cipher_migrate; 复制代码
成功连上旧数据库文件后,通过 sqlcipher 4.x 提供的新PRAGMA
cipher_migrate
,可以将旧数据库文件的格式和设置升级,从而获得更好的性能。虽然数据库迁移语句只需要执行一次,但迁移过程的耗时不可控,建议增加提示的交互,迁移完成后再打开Electron窗口。(在windows上测试时,主窗口直接无响应了很长时间,mac倒没有..)
p.s 官方虽然提供了迁移语句,但迁移过程也有可能失败,所以也提供了备选方案:解开原数据库并导出非加密版数据库文件 plaintext.db,使用新sqlcipher 对 plaintext.db加密。该方案较为繁琐。
2)使用降级语句
PRAGMA key = '<key material>'; PRAGMA cipher_compatibility = 3; 复制代码
好处是直接使用,没有耗时的迁移过程,但也无法得到新版本的性能提升。
所以,我们选择方案1
-
分发测试
Electron打包分发,自信满满的在其他mac设备上进行测试,结果被报错打脸。
原因仍然出在动态链接上,但我们明明已经将依赖库sqlcipher指定成静态文件 libsqlcipher.a了...一番查issue和分析后,发现问题出在brew下载的 sqlcipher上。
sqlcipher 的加解密依赖于OpenSSL库,而通过brew 安装的sqlcipher是基于openssl动态链接编译的,本地测试能通过的原因是我已经
brew install openssl
过。解决方案 - 手动编译sqlcipher:
参考:www.shuzhiduo.com/A/lk5aaDao5…
discuss.zetetic.net/t/updating-…
-
clone sqlicpher , github.com/sqlcipher/s…
-
编译源码
make clean ./configure --enable-tempstore=yes --enable-load-extension --disable-tcl --with-crypto-lib=commoncrypto CFLAGS="-DSQLITE_HAS_CODEC -DSQLITE_ENABLE_JSON1 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_ENABLE_FTS5" LDFLAGS="-framework Security -framework Foundation" make 复制代码
- 替换brew下载的sqlcipher
成功后出现 .libs 隐藏文件夹,将生成的
libsqlcipher.a
,libsqlcipher.0.dylib
复制到/usr/local/Cella/sqlciher/xxx/lib
下去替换。然后,按上述binding.gyp修改方式,引入静态文件,进行重编译。
-
Windows
在windows上,除了基本的electron和node-gyp,还需要全局安装windows-build-tools
和 python 2.x,以及 visual studio相关工具集。
旧方案
在node-sqlite3 和 sqlcipher 的相关文档中,并没有详细的关于windows平台编译方案。之前项目中使用的是win-sqlcipher,该项目作者fork了一份带有OpenSSL源码的node-sqlite3版本,并通过node-pre-gyp
实现了下载后自动编译。
然后,在win-sqlcipher
下再通过node-gyp指定electron版本和架构(ia32或x64),重编译得到需要的原生模块文件。
但win-sqlcipher
和相关repository已经很久没有更新,不能满足使用Sqlcipher 4.x的需求。
新方案
1)手动编译 sqlcipher
windows平台基于sqlcipher源码的编译方案,翻了很久,没有找到完整有效的流程,暂时放弃。
2)基于 node-sqlcipher
node-sqlcipher与 win-sqlcipher类似,也是fork了node-sqlite3,然后在package.json中,将node-pre-gyp 指向自己的二进制文件目录。
作者当前提供的正好是基于sqlcipher 4.3版本,符号我们的需求。同时也提供了自定义sqlcipher版本的操作方法。SQLCipher.md
使用node-sqlcipher的方法很简单,作者已经提供了基于静态OpenSSL编译的Sqlcipher。笔者遇到的坑是缺少 Visual Studio 2015 环境。好在预装了 Visual Studio 2019,可以在IDE中选择 2015 工具集进行安装。
node-sqlcipher也提供了mac环境基于静态编译的sqlcipher,这里没有继续尝试,后续升级可能会再试下。
总结
Electron项目中,原生模块的编译虽然麻烦,但掌握其流程和原理很有必要。无论是引入开源的 Nodejs原生模块,还是集成其他厂商提供的原生SDK。
原生模块基于C/C++,体积和性能上优于 nodejs 模块,而且二进制的文件格式,代码安全性更高。飞书的桌面端也是基于Electron,其应用中就大量使用了原生模块,而且基本都是自用的模块。
相关文章