Electronでsqliteを使いたくなった場合,
github.com
とか
github.com
とかを使うと思う.これを組み込んだ状態でアプリをビルドするときに,かなり詰まったので記録しておく.
成果物と同じプラットフォームでビルドする場合は問題なし
たとえばLinuxでLinux用のビルドをするとか,WindowsでWindows用のビルドをするような場合,特になんの問題もない.上記のライブラリはどちらもnative dependencyになるのだが,同じプラットフォーム向けなのでビルドされたバイナリもそのまま動く.
問題はそうではない場合が,かなりきついという話.
MacOS x64でarm64向けのビルド
ここがかなり詰まった部分になる.今まで,
- DMG: electron-builder + electron-notarize
- AppStore: electron-packager + electron-universal + codesignコマンド
でビルドしていた.electron-builderは,archを指定した場合にはarchごとにrebuildをしてくれている.しかしelectron-packagerは本当にパッケージングするだけなので,x64マシンでビルドした成果物はarm64では動かない.arm64向けにnative dependencyをrebuildしなければならず,electron-rebuildを使って
github.com
こいういうことをする必要がある.そのため,最初に失敗したのはAppStore版であった(後にDMGも結局動かないことが発覚したので,いずれにしろ今までの設定ではダメなことに変わりはなかった).
同時に複数の問題が発生しており,時系列順に書くとかなり複雑なため,時系列をすっ飛ばして発生した問題を整理する.問題点が4つある.
問題1: electron-osx-signでAppStore版をcode signingするとローカルで起動できない
electron-builderでもelectron-packagerでも良いのだが,ビルドしたあとにcode signingする必要がある.DMGとして配布するのであれば Developer ID Application: YOUR_NAME(TEAM_ID)
という証明書でcode signingできれば良い.
AppStore版の場合は 3rd Party Mac Developer Application: YOUR_NAME(TEAM_ID)
という証明書を使う.
DMG版はelectron-osx-signでcode signingしたあと,普通に起動できるのだが(当然だ),AppStore版については EXC_CRASH (SIGKILL (Code Signature Invalid))
というエラーによりエラーがでて起動できない.これを避けるためにもともと electron-packager + codesignコマンドを使っていた.
参考: https://github.com/h3poteto/whalebird-desktop/blob/4.7.4/appStore.sh
が,どうやら,electron-osx-signにおいてこの挙動は想定内であり,通常の挙動らしい.つまりAppStore用のビルドはAppStore経由出ない場合には起動できない.ではどうするかというと,ProvisioningProfileとともにcode signingした上で,AppStoreConnectにアップロードし,TestFlight経由でインストールすると起動する(その他の設定が正しければ).
というわけで,この時点でelectron-packager + electron-universal + codesignコマンドを捨てて,electron-builderでAppStore版もビルドすることにした.
問題2: native depencencyの成果物もcode signingしないとエラーになる
どういうことかというと,node-sqlite3もbetter-sqlite3もrebuild時に .node
ファイルを生成し,electronはこいつを呼び出してsqliteを実行する.このときに呼び出す .node
は外部ライブラリ扱いであり,最近のMacOSでは依存している外部ライブラリもcode signingされている必要がある.
これをやらないと,
Uncaught Exception:
Error: dlopen(/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU, 0x0001): tried: '/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' (code signature in <5439AE43-90A3-3A47-A95F-32AEC2235239> '/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' not valid for use in process: mapped file has no Team ID and is not a platform binary (signed with custom identity or adhoc?)), '/System/Volumes/Preboot/Cryptexes/OS/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' (no such file), '/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' (code signature in <5439AE43-90A3-3A47-A95F-32AEC2235239> '/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' not valid for use in process: mapped file has no Team ID and is not a platform binary (signed with custom identity or adhoc?)), '/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' (code signature in <5439AE43-90A3-3A47-A95F-32AEC2235239> '/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' not valid for use in process: mapped file has no Team ID and is not a platform binary (signed with custom identity or adhoc?)), '/System/Volumes/Preboot/Cryptexes/OS/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' (no such file), '/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' (code signature in <5439AE43-90A3-3A47-A95F-32AEC2235239> '/private/var/folders/w9/_481zfb94wx2yq562f5h68vw0000gn/T/.social.whalebird.app.mH2BlU' not valid for use in process: mapped file has no Team ID and is not a platform binary (signed with custom identity or adhoc?))
at process.func [as dlopen] (node:electron/js2c/asar_bundle:5:1812)
at Module._extensions..node (node:internal/modules/cjs/loader:1205:18)
at Object.func [as .node] (node:electron/js2c/asar_bundle:5:2039)
at Module.load (node:internal/modules/cjs/loader:988:32)
at Module._load (node:internal/modules/cjs/loader:829:12)
at c._load (node:electron/js2c/asar_bundle:5:13343)
at Module.require (node:internal/modules/cjs/loader:1012:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/Applications/Whalebird.app/Contents/Resources/app.asar/node_modules/sqlite3/lib/sqlite3-binding.js:4:17)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
というようなエラーになる.
github.com
ここでは com.apple.security.cs.disable-library-validation
が紹介されているが,BigSur以上のMacOSの場合は,この設定に問題がある件が指摘されている.
https://github.com/electron-userland/electron-builder/issues/3940#issuecomment-900527250
で,そもそもなぜアプリに含まれているはずの .node
ファイルがelectron-osx-signでcode signingされないのかというと,これらがすべて asar
に圧縮されてしまっているためだ.electronはプログラムを asar
に圧縮して配布している.割と簡単に解凍できるのだが,code signするときにいちいち解凍はしてくれない.
というわけで,
"build": {
"mac": {
"asarUnpack": "node_modules/**/*.node"
}
}
という設定を入れて,.node
ファイルを asar
圧縮から除外してやる必要がある.これをやると asar.unpacked
というディレクトリ内に生ファイルのまま格納されるので, electron-osx-signが --deep
オプション付きでcodesignするときにすべてcode signingされる.
ちなみに元のcodesignコマンドを使ったスクリプトは --deep
オプションを使っていなかったので,unpackedにしたところで結局同じエラーに陥った.
問題3: native dependencyの成果物をuniversalでマージできない
electron-universalは,一度x64,arm64両方のビルドを作り,それらをマージすることでuniversalバイナリを作っている.このときに,問題2でasarから除外したunpackedなnative dependencyが問題になる.
Detected unique file "node_modules/sqlite3/lib/binding/napi-v6-darwin-unknown-arm64" in "***/whalebird-desktop/build/mac-universal--arm64/Whalebird.app/Contents/Resources/app.asar.unpacked" not covered by allowList rule: "undefined"
github.com
これと同じことが発生し,同じ名前の別archファイルをマージできなくなる.これについては,singleArchFiles
オプションを使えという話ではあるが,最終的に俺は mergeASARs: false
にして,universalのときにasarのマージをやめた.これは次の問題4に大きく関係する.
が,いずれにしろ,どちらかのオプションを使い,両方のアーキテクチャ用のバイナリを残す必要がある.
問題4: node-sqlite3はelectron-rebuildした成果物が正しくない
ここまでで,だいたい動くようにはなったのだが,一つ大きな問題が残った.どうもx64では問題なくarm64でのみ問題が発生することがわかった.
エラーがこれだ.
Uncaught Exception:
Error: Cannot find module '/Applications/Whalebird.app/Contents/Resources/app.asar/node_modules/sqlite3/lib/binding/napi-v6-darwin-unknown-arm64/node_sqlite3.node'
おかしい.そもそも問題3の解決のために node_sqlite3.node
は asarから除外してunpackedに入っているはずだ.しかし,ここではasarの中を探している.
さらにもう一点おかしいことがある. unpackedディレクトリの中身はx64もarm64も同じで,binding
の下には
napi-v{napi_build_version}-darwin-unknown-arm64
napi-v{napi_build_version}-darwin-unknown-x64
napi-v6-darwin-unknown-x64
の3つしかディレクトリがない.確かにこれなら,x64は napi-v6-darwin-unknown-x64
を使って起動できるが,arm64になると napi-v6-darwin-unknown-arm64
が存在せずにエラーになることは理解できる.asarを探しているのは,おそらく探索順序が unpacked
-> asar
の順で探索されているだけだろう(これは勘).
というわけで
github.com
これだ.これはelectron-rebuildとnode-sqlite3の掛け合わせで発生する問題のようだ.
これ自体が解決していないので,そもそもnode-sqlite3を使うのをここでやめて,better-sqlite3を使うことにした.
better-sqlite3は開発時もrebuildしないと起動しない
github.com
これはproduction buildの話ではないのだが,そもそも開発環境でも
NODE_MODULE_VERSION 108. This version of Node.js requires
NODE_MODULE_VERSION 107. Please try re-compiling or re-installing
というエラーがでて起動できなかった.これはelectronのバージョン(の中で決まっているNODE_MODULE_VERSION)が合致したものを使えばいいとは思うのだが,毎回それを気にしてライブラリの更新をするのも手間なので,install直後にrebuildすることにした.
package.jsonに
"scripts": {
"postinstall": "electron-builder install-app-deps"
}
とかを追記しておけば,起動するようになる.
解決
そして解決し,無事にAppStoreのレビューも通った.
今回の問題のうち
はAppStoreのみに関係する問題だったが,
に関しては,DMGでも同様のエラーがでて使えないという報告が上がっており,
Whalebird 5.0.0 fails to start · Issue #4195 · h3poteto/whalebird-desktop · GitHub
Launching Whalebird 5.0.0 universal on macos 13.2.1 arm64 m2 fires uncaught exception · Issue #4183 · h3poteto/whalebird-desktop · GitHub
v5.0.0 macOS x64 JavaScript error on launch · Issue #4173 · h3poteto/whalebird-desktop · GitHub
同時にすべて解決することができた.
まとめ:結局どうするのが良いのか
node-sqlite3ではなくbetter-sqlite3を使う.
electron-builderでもelectron-packagerでも問題ない.そもそもビルド環境と実行環境が同じプラットフォームであれば,気にすることはない.
AppStore
electron-builderを使う.
こういう設定でビルドする.
{
"productName": "Whalebird",
"appId": "social.whalebird.app",
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
"directories": {
"output": "build"
},
"extraResources": [
"build/icons/*"
],
"files": [
"dist/electron/**/*",
"build/icons/*"
],
"mas": {
"type": "distribution",
"entitlements": "plist/parent.plist",
"entitlementsInherit": "plist/child.plist",
"entitlementsLoginHelper": "plist/loginhelper.plist",
"hardenedRuntime": false,
"gatekeeperAssess": false,
"extendInfo": {
"ITSAppUsesNonExemptEncryption": "false"
},
"provisioningProfile": "./packages/socialwhalebirdapp_MAS.provisionprofile"
},
"mac": {
"icon": "build/icons/icon.icns",
"target": [
{
"target": "mas",
"arch": [
"universal"
]
}
],
"category": "public.app-category.social-networking",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"darkModeSupport": true,
"extendInfo": {
"ITSAppUsesNonExemptEncryption": "false"
},
"mergeASARs": false,
"asarUnpack": "node_modules/**/*.node"
}
}
なお,electron-builderは内部でelectron-osx-signやelectron-rebuild, electron-universalを使っている.そのため,
com.apple.security.application-groups
や ElectronTeamID
は自動挿入されるので,entitlementsに追加する必要はない.
whalebird-desktop/plist at master · h3poteto/whalebird-desktop · GitHub
この辺を参考にしてもらうと良い.
electron-builderを使う.
こちらもあまり変わらないが
{
"productName": "Whalebird",
"appId": "social.whalebird.app",
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
"directories": {
"output": "build"
},
"extraResources": [
"build/icons/*"
],
"files": [
"dist/electron/**/*",
"build/icons/*"
],
"afterSign": "build/notarize.js",
"dmg": {
"sign": false,
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns",
"target": [
{
"target": "dmg",
"arch": [
"x64",
"arm64",
"universal"
]
}
],
"category": "public.app-category.social-networking",
"entitlements": "plist/entitlements.mac.plist",
"entitlementsInherit": "plist/entitlements.mac.plist",
"entitlementsLoginHelper": "plist/loginhelper.plist",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"darkModeSupport": true,
"extendInfo": {
"ITSAppUsesNonExemptEncryption": "false"
},
"mergeASARs": false,
"asarUnpack": "node_modules/**/*.node"
}
}
こんな感じ.