C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

vs. COVID-19: Folding@home の FAHControl でご家庭内で参加中の複数のクライアントをまとめて監視・制御する方法のメモ

ご家庭内から複数のPC端末等をクライアントとして Folding@home に参加する場合に、1箇所の FAHControl (管理画面ツール) でそれ自体はもちろん、他のクライアントも全て管理したい場合もあります。

f:id:USAGI-WRP:20200327170347p:plain

例えば↑な具合で管理できるようにしたい、と。こうしておくと、動作を監視するだけでなく、制御もできるため、アイドル時のみの設定ではなく手動で今は計算資源を提供しよう、あるいはちょっと止まって欲しい、そうした扱い方がしやすくなります。

設定方法は簡単なのですが、 FAHControl アプリは妙なところで癖の強さを残していて、たぶんこのメモを見ているであろう人には何か困っている事もある予感がするので、つまりメモを残すことにしました。

必要な予備知識

  • Folding@home が何か概要だけでも知っている (知らない場合は→ Wikipedia とか PC Watch - 自宅のPCで新型コロナウイルス治療に向けた解析が可能に とか )
  • FAHControl に関する特殊な操作方法
    • FAHControl には、たぶん Latin-1 的なキーボードじゃないとまともに文字を入力できません (何故か謎の半角カタカナが入力されても驚かない😅)
    • FAHControl には、キーボードで CTRL+V しても貼り付けが動作しないキーボードがあります
    • FAHControl には、ポインティングデバイスで右クリックメニューを出して Paste すれば任意の文字列を貼り付けで入力できます
    • まともに入力できて Copy できるテキストエディターか端末エミュレーターっぽい何かなどを起動してから始めましょう
  • ご家庭のネットワークに関する一般的な技術情報
    • ipconfig とか ip address とかして端末の IP を知れる程度の能力
    • ご家庭のネットワークの "ネットワークアドレス" の意味がわかっている程度の知識
    • ご家庭で使用中の OS やルーターFirewall 機能と設定を理解している程度の能力

方法

  1. 別のPCから接続を「受ける」側のPCで、
    1. FAHControl を起動し、
    2. 画面左側の Clients のリストにある local をダブルクリック

f:id:USAGI-WRP:20200327165245p:plain

3. Configure 画面の上部のタブを Remote Access へ切り替え
4. 実はスクロールするすごい縦長画面なので画面右側のスクロールバーを使い IP Address Restriction の囲いまで進み
5. Allow に 127.0.0.1 に加えて接続を許可する"ネットワークアドレス"(例: 192.168.0.0/24 )を「書き足し」ます ( 空白文字区切りなので `127.0.0.1 192.168.0.0/24` とか入力された状態にします )
6. Configure 画面を Save で閉じ
7. OSのタスクトレイ的なところにある Folding のアイコンを右クリックでコンテキストメニューを出し、
8. Quit で一端終了させ

f:id:USAGI-WRP:20200327165631p:plain

9. スタートメニュー的なところ、あるいはデスクトップにあるショートカットなどで [Folding@home][] を起動しなおします
  1. 接続「しに行く」側のPCで、
    1. FAHControl を起動し、
    2. 画面左側の Clients のリスト部分の下部にある Add をクリック
    3. Configure 画面の Connection タブの Address に先に「受ける」側の設定を施したクライアントの IP を入力
    4. Save

もし、この手順でやっても Clients の Status が Connecting のまま繋がらない場合は、

  • 設定を間違えていた (ネットワークアドレスの値、書き方)
  • 「受ける」側の Folding@home を再起動(手動でQuitして起動し直し)していなかった
  • Firewall でブロックしていた
  • そもそも別のネットワークに分離されていた

とかそういう状況かもしれません。よくわからないときは諦めるか、専門家に依頼しましょう。この程度ならスシやヤキニクで十分やってくれる技師もいるかもしれません。

ちなみに

"こういうの" の事は専門的には「グリッド・コンピューティング」と言います。似たようなので有名な例は BOINC とか。広義的には "スーパー・コンピューター" の一種です。実際に Folding@home が COVID-19 の根絶や特効薬の開発に寄与できるかはさておき、こうしたプロジェクトが稼働できる事、人類の危機に際して計算資源の提供が増加する様には、地球の人類にもまだ少しは浪漫を感じられますね😭

私も少なくとも今後しばらくは、 Folding@home に計算資源を提供する事にしましょう。

…そうそう、一般のご家庭では揉める事はないと思いますが、学校、会社、その他何らかの組織などで計算資源の管理者の承諾無く Folding@home のようなボランティアを設定すると人生が終了する可能性もありえなくはないのでご注意下さい。現在は COVID-19 の影響でオフィスや教室に未使用の計算機資源がたくさん眠っていて使い所のように感じる熱いハートをお持ちの方もおられるかと思いますが、先ずは管理者か、あるいは鶴の一声が可能なクラスの役員や大株主などの偉い人に相談しましょう。よほど高性能な計算機資源をたくさん抱えているわけでなければ電気代よりも売名効果の方が大きいかもしれませんが、少なくとも、他人の資産を勝手に使うと、ブラウザーのマイニングアプリでも警察に逮捕される地方があるみたいですし、みなさまどうかご安全に。

Cargo.toml の [dependencies] の特殊化のメモ

例 1. crates.io に公開されている crate を使っている状態:

[dependencies]
neon = "0.4.0"

例 2. ローカルファイルシステムで改変中の独自版へ依存先を特殊化したい場合:

[dependencies]
neon = [ path = "../path-to-my-customized-neon" ]

例 3. features フラグを独自設定に特殊化したいだけの場合:

[dependencies]
#neon = "0.4.0"
[dependencies.neon]
version = "0.4.0"
default-features = false
features = [ "default-panic-hook", "legacy-runtime" ]

例 4. features フラグを独自設定に特殊化しつつ、ローカルファイルシステムで改変中の独自版へ依存先も特殊化したい場合:

[dependencies]
#neon = "0.4.0"
[dependencies.neon]
#version = "0.4.0"
path = "../path-to-my-customized-neon"
default-features = false
features = [ "default-panic-hook", "legacy-runtime" ]

参考

WSL の内側の世界から、外側の Windows の世界の PATH が "何もしていないのに" 通っている件、をどうにかしたい場合のメモ

WSLの内側の世界では、外側の世界の Windows で実行可能なバイナリー「も」実行できます。

実際この機能はしばしば便利な事もあります。例えば、

  • 諸事情によりWSLの内側の世界で作業中に
  • PE32+ (†1) なバイナリーファイルを
  • nm (†2) したいけど ELF (†3) じゃないのでもちろんできないので
  • 仕方がないので dumpbin (†4) しなければならないが
  • わざわざWSLの外側の世界で cmdpowershell で実行するのは面倒 (†5)

みたいな事が起こるユーザーにはとても便利です。 PATH 通すか aliasfunction で呼べるようにしておけばね💪

そういうわけで PATH の話に無理やり繋げました。 WSL の内側の世界では Windows (のWSLの仕組み) が "気の利かせ" て Windows の世界の PATH を WSL の内側の Linux の世界へ全自動デフォルトで注入してくれます。野暮なお節介ババア的な感じで。すごくいい人だけどいい人すぎて余計なお世話のトラブルメーカー的な。

つまりこういう事が起こる:

  1. WSL の内側の世界でプラットフォームのパッケージマネージャーを介して yarn を入れてあるとしましょう:
  << which yarn
/sbin/yarn
  1. その yarnglobal bin 置き場は default な具合でこうなっていて、なにか適当なアプリが入っていたとしましょう (↓の例では neon; 例なのでなんでもいいです ):
  << yarn global bin --offline
/home/usagi/.yarn/bin

  << ll $(yarn global bin --offline)
total 8.0K
drwxr-xr-x 2 usagi usagi 4.0K 2020-03-22T13:20:24 ./
drwxr-xr-x 3 usagi usagi 4.0K 2020-03-20T22:04:21 ../
lrwxrwxrwx 1 usagi usagi   48 2020-03-22T13:20:24 neon -> ../../.config/yarn/global/node_modules/.bin/neon
  1. ところがこの yarn を介して導入したはずのアプリを実行すると何か奇妙な現象に見舞われて、原因を探るためにまず which とか readlink とか headhexdump
  << which neon
/mnt/c/Users/usagi/AppData/Local/Yarn/bin/neon

/(^o^)\

みたいな事が起こります。起こりました。単独で他のファイルに絡まずに実行可能なスクリプトでは実害のある問題は起こらない事もありますが、そうではない場合はわりと危険が危ないです。これについて、ぱわーのあるユーザーなら PATH について .zshrc 的ないつものところへ PATH=$( 💪 MY AWESOME BLACK MAGIC BE DESTROY AUNT MEDDLESOME WINDOWS GIVEN HOLY PATH 💪 ) みたいなみなぎる筋肉をぶつけていくスタイルでマッチョに解決していた時代も実際あったようです。 perl でも php でも awk でも python であれ、何か得意な黒魔術で対処療法の時代。

現在は既に対処療法ではなく遺伝子操作やワクチンによる解決が実用可能になっていて:

WSL 内へ摂取する"ワクチン的"でお手軽な方法として:

  1. /etc/wsl.conf を書く:
[Interop]
appendWindowsPath = False
  1. 管理者権限の powershellRestart-Service LxssManager しておく ( 全ての WSL 環境は安全に閉じてから↑の設定後に1回する )

f:id:USAGI-WRP:20200322150610p:plain

これだけでよくなったようです。🎉

f:id:USAGI-WRP:20200322150804p:plain

外の世界の Windows のお節介による PATH が消えたので先の例の WSL 内 yarn を介して導入した neonwhich でも確認できるようになりました。 yarn だけなら💪的に export PATH="$(yarn global bin --offline):$PATH" だけでも事実上は解決できるのだけど、それだけ、という事ではなく解消しておきたい場合には一般的に WSL のお節介機能を無効化するのが安全です。

"遺伝子操作"的なやつがお好みの場合は regedit して HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss のなかをもぞもぞする方法もあるようです。私は楽にすませたいので /etc/wsl.conf したのでこちらは試しませんでしたけれど。

reddit で話題になった Tauri と NEON についてのメモ、ついでに template-rust-backend-with-electron-frontend の開発理由についてのメモ

reddit の Rust コミュニティーtemplate-rust-backend-with-electron-frontend のリリースについてポストしたところ、主に2つ、 Tauri と NEON について話題になりました。( up-vote たくさんとちょうど使いたかったんだありがとう的なポストも頂きました。幸いです。 )

1. Tauri vs. Electron ( 間接的には template-rust-backend-with-electron-frontend にも関連します )

こちらは template-rust-backend-with-electron-frontend にとっては via via 的な間接的でややオフトピックよりな感じの内容ではありますが、特徴的な部分についての対決姿勢的な議論が生じていました。主な主張は次の2通り(意訳と補足を含みます):

  1. Tauri 上げ上げ系
    • Tauri なら高速でフットプリントも高々6MBに収まるよ、 Electron はすごい重いっす😭
  2. Tauri じゃない理由を推論してくれる系
    • Tauri のフットプリントが小さいのはブラウザーがバンドルされておらず、OS が提供するブラウザーを WebView として使うから。つまり…
      • 動作や見た目が実行環境によって変わる可能性(ブラウザーの種類やバージョンによる互換性問題)をアプリ側で考慮する必要が生じる
        • Electron だと WebView をバンドルする特定版の Chromium に決め打ちできる (少なくともユーザー環境に依存したブラウザーの互換性問題は起こらない)
        • 実際問題: OSX -> Safari が起動、 WindowsMSHTML が起動するとつらい😭 (まともに更新されている一般的なWindows 10しか考慮しなくていい世界ならEdgeだけどね😜)
    • Tauri はまだ若く TODO な部分がまだたくさんあるんだ

vs. な姿勢については、 Rust バックエンドに Web 系テクノロジーのフロントエンドをマーシャリングする選択肢について「でも、現時点の選択肢としては両方からどちらもかんたんに選べるのが嬉しいじゃろ」と思います。 Tauri が順調に TODO を消化し、その上でさらにビルド・プロセスで特定のブラウザーをバンドルする能力を獲得する日が来たら、 Tauri は Electron に対してほとんど上位互換的な技術要素になり得ますが、 TODO 消化はともかくバンドル能力の獲得は恐らく Tauri の開発思想的に無さそうな気はします。

Tauri は Rust のフロントエンド向け技術要素として現状に対しては良い選択肢の1つだと思います。名前が STARGATE の地球の人の意味の Tau'ri っぽいのも面白い点です。しかし、ブラウザー互換性問題への対処可能性がアプリ開発のプロジェクトチームの負担となる事は、特に「日本企業」とか「役所」とか「瑕疵担保」などを考える場合には2020年現在もまだネガティブな要因として考慮から外せないかなって思います。そうした懸念の低い分野のプロジェクトでは積極的に採用しても面白いのかな、と思います。

また、 Tauri の公式 README.md を見る分には TODO もそれほど困るコア機能的な部分は既に無さそうには見えます(どう見ても OSX 好きな開発者が中心になっているので WindowsGNU/Linux ではうまく動かない機能とかある"かも")。 Tauri vs. Electron については reddit で生じていた議論も核心の1つではありますが、 Tauri 公式の README.md の Comparison between Tauri and Electron が網羅的でわかりやすい1つ比較例として有用そうです:

参考: https://github.com/tauri-apps/tauri

  1. フットプリント: 例えるなら、
    • Electron はスイス・アーミー・ナイフ、
    • Tauri は刃先は現地で調達した何かを挿して使えるナイフの柄みたいなものかなーと思うので、「Tauri、せやろな」と素直に思います
  2. Interface Service Provider:
    • 多様性とユーザーへ選択肢を与える事は思想的にも、もしもの「こんなこともあろうかと」への備えとしては理想性が高いけれど、
      • 現実は reddit でも議論になったように可能性があるだけでもしんどい上に実際にもブラウザー互換性問題はしんどい部分がまだ捨てきれません
    • 開発チームの現実の負荷と生産性へのデメリットの視点からは Tauri は不利に思います
  3. Backend Binding:
    • Tauri vs. template-rust-backend-with-electron-frontend ではどちらも Rust になるし、
      • どちらもネイティブなバイトコードが実行されるので差はないですね
  4. FLOSS:
    • Electron も現実的に問題になる可能性は既に極めて低いので、
    • あとは宗教的/政治的な意図で Free/Libre 互換性を気にしたいかどうか次第じゃないかな
  5. Multithreading:
    • Tauri が Yes と堂々と書いていてつよいなーとは思います。技術変態的な用途で遊びたいなら Tauri が楽しいかもしれません。
    • ただ、実用性としてはそもそもデスクトップアプリのフロントエンド部分としての Electron について、 ES の Worker、そもそもの ES の実行効率、また Tauri が堂々と Yes と言うからにはそこにはブラウザー互換性とそれをカバーするであろうサブシステムもあるのでしょうし、遊びかなーって思います。
    • バックエンド部分を分離したアーキテクチャーでフロントエンド部分で MT が活きる用途というのは実際には非常に限られます。もしレンダリングをMT化できれば嬉しいのでは、と思う人もいるかもしれませんが、OSの描画コンテキストを生で扱える処理系でもレンダリングのMT化はまだまだ極めて困難な技術です。仮にGLコンテキストを使うならWindowsではGLコンテキストのMT対応は不十分ですし、そもそもプラットフォーム依存という仕様で困難が大きいです。加えて、ブラウザーで標準化された Web Worker (厳密にはこれはJavaScriptECMAScriptの仕様ではなくブラウザー側で追加されるWeb APIに含まれる仕様です)は MT としてはメッセージングベースで…閑話休題
  6. Auto Updater:
    • Electron にはあります。製品の要求にこの機能が欲しい事はしばしばあるので便利です
    • Tauri も Soon らしいので期待したいですね。
  7. Android, iOS:
    • Electron の弱点の1つかもしれません
    • この点は「 Tauri なら」と言える強みかもしれません
  8. No localhost option:
    • Electron の実行環境がマルチユーザーなシステムの場合にファイアーウォールを比較的高度に設定できないと脆弱性になり得えます
    • この点も「 Tauri なら」と言えそうです
      • もし開発するアプリのリリースターゲットがマルチユーザー同時ログオンを考慮する必要があって、その上でリッチなGUIデスクトップアプリを実行させたいなら、というニッチな状況も想定しなければならないなら、だけど
  9. Desktop Tray:
    • Tauri では Soon
    • Electron では No
      • native バックエンドを持つアプリなら必要に応じてトレイ常駐的な機能の後付けはどうとでもならない事もない
      • プロセス内から実現しにくい場合はトレイ用の別プロセスを立ててIPCすればそれほど難しい機能ではありません (クロスプラットフォーム対応したいとなると少々しんどくなります)

以上の比較表の外で Tauri の TODO から気になるのは Multiwindow Mode です。「にゃんにゃんしますか?(でんっ)」みたいなダイアログも原理的にはマルチウィンドウなのだけど、そういう部分なのか、また少し違ったニュアンスなのかは調査してみないとわかりませんが、チョット気になります。

(1と2のハザマ): template-rust-backend-with-electron-frontend の開発理由

私が template-rust-backend-with-electron-frontend を準備したモチベーションはそもそも…

  • WebView 系フロントエンド技術要素の開発
  • Electron や関連プロジェクトへのコミット
  • Tauri や関連プロジェクトへのコミット
  • FLOSS の普及/布教または単にボランティア・ハートによる奉仕したみ
  • Rust バックエンドに対応する GUI Toolkit の開発
  • 何であれとにかく GUI Toolkit 的なフロントエンド技術の開発

ではなく、寧ろそのようなあたりへ触れている人生の資源の余裕は残念ながら無いので、

  • Rust でアプリ作りたいけど
  • GUI Toolkit 的なフロントエンド技術の成熟がまだ弱い
    • Qt や Gtkバインディングはあるけど高価 or Free/Libre なライセンス汚染が生じてしまう ( OSS 作りたいだけならいいんだけど… )
    • conrod だとビジネス的なGUI部品やデフォルトでの見栄え的に弱い (日本語の入出力ちゃんとできてすごいし、ゲーム開発ならGUI部品も絵作りから独自にやりたいからその用途で使うなら問題にならないんじゃが…)
    • OrbTkdruid にも期待したいけど、"今"使うには若すぎる…せめて日本語の入出力…ウィジェット(≃コントロール)もモウチョット欲しい… (作ったりミコットしたりしつつ、それを使った製品本体も作る余力はない用途にも使いたいんじゃ…スマヌ…)
  • どうせ "今を凌ぐ仮のフロントエンド技術" を採用するなら…
    • できるだけバックエンドとは疎結合ですげ替えやすさを維持できる要素技術だと嬉しい
    • あれがない、これはできない、それは作らないといけない…そうした事ができるだけ少ない成熟した技術をぽんっと使っておきたい
      • (=プロジェクトチームの生産性を "仮のフロントエンド" にできるだけ注ぐ必要が少ないと嬉しい)
    • ルック&フィール、意匠設計的な部分に手を付けるのにフレームワークの深い理解が必要ない方が嬉しい
      • ("顧客が本当に必要だったGUI" への要求に手早く簡潔に対応できるものだとなお嬉しい)
    • 表示とUIとしての入出力部分だけきちんとうまいことやって貰えたら、あとはバックエンドの開発力でどうとでもするぞい💪💪💪

と、いうわけで、そういう事なら…

  • .NET Core/WPF/Prism
  • Node.js/(...brabrabra...)/React/Electron

どっちかが私や関連するプロジェクトチームのスキルセット的にも有利な選択肢ネ、でも Rust 純度高い技術の可能性も捨てたくは…やっぱり少なくとも今はまだダメでした…となった流れと、 .NET Core の WPF だと Windows 以外へのクロスプラットフォーム・ポータビリティー的に不安しかない、 Node.js/React/Electron 系を採用しておけばバックエンド Rust と融合させて Windows, OSX, GNU/Linux での心配は少ないし、チョットどうにかすれば Web サービス、さらにモウチョットどうにかすれば Android / iOS 向けにも fork しやすい…ハズ…。

と、いうことでにゃんにゃんしてできたのが template-rust-backend-with-electron-frontend です。要素技術のシンプルなマーシャリングを実現しただけの状態のプロジェクトテンプレートはバックエンドプロジェクトに余計な束縛を与えない事も大事な要素です。もちろん、ある程度はアレ向け、ソレ向け、と用途にフォーカスした派生プロジェクトテンプレートも用意したいとは考えていますし、できれば Rust cdylib と Electron の間のマーシャリング・コードの自動生成機能を付けたい気持ちもあります。但し、しつこいようですが、バックエンドの Rust コードが主体で、その開発に制約を設けたり、特定のツールやビルド方法への強めの束縛は取り込みたくありません。

2. NEON; Rust と Node.js を繋ぐマーシャリング技術

NEON 自体は Rust と Node.js のマーシャリング技術要素部分に注力した「補助的なビルドツール」のプロジェクトです。

実用していなかったので単純に存在を忘れていました、という正直なところはあるのですが、 reddit で「どうして NEON 使わないんだい?」的な議論から試みました。いい感じに template-rust-backend-with-electron-frontend へ組み込めたら素直に嬉しい技術です。

Windows で試したところ、開発環境への NEON ツール群の導入は簡単にできたものの、プロジェクトテンプレートを作成するコマンドライン・インターフェースが挙動不審で制御が帰ってこなくなりました😭

誰も助けてくれない気配を感じたので NEON の repos を git clone して NEONcli 部分についてコードを確認、 TypeScript と判明したのでデバッガー(今回は VSCode を使いました)に必要な設定の変更と追加をして適当にブレークポイントを挿しつつステップ実行してみました: -> https://github.com/neon-bindings/neon/issues/406#issuecomment-601832883

結果: console.log() で制御が行方不明になる -> https://github.com/neon-bindings/neon/issues/406#issuecomment-601832883

原因がわかればもちろん修正して PR したいところでしたが、わたしの Node.js / TypeScript のワカリでは状況に対して修正すべき原因に適切な直感が綺麗には働きませんでした。残念。

ただ、不幸中の幸い、この問題はどうやら neon new の主だった処理を終えた後、残すところフレーバー的な表示の出力だけになった状態で発生する、実質的にはそれがわかっていれば実害は無い部分という事もわかりました。WSL2 を使えば Windows と共有するファイルシステム上にも neon new 期待動作できる事は先の Issue #406 へのレポートのようにわかっていましたし、さいあく neon new できなくても neon build --release できるプロジェクトをどうにかできれば NEON 自体は WSL2 も使えない Windows 開発環境のメンバーがプロジェクトチームに居たとしても大丈夫…かも…などなどありましたが、その前提の "さいあく" はとりあえず回避できました。

  • neon build --release

これで "Hello, world" 的な .js は出力できました。build では new のような問題が起こらなくて一安心です。続いて、NEON 公式の docs の確認を進めると、

を見つけました。「ぉ、これはオレオレテンプレートからもりもりしたり、 neon new から Electron/React/その他いっぱいのプロジェクト初期状態の構築や依存関係のうにゃーとかせずに使えて便利になるのかも?」とちょっと期待しました。

ダメでした…。よく見ると neon-hello は4年も保守されていません。 昨年あったらしい PR #1 にも Author の応答が無さそうです。お手軽にはじめるテンプレート・プロジェクト的な部分だと思うので、手動でどうにかすればいいのかも、とも思いますが…

  1. 少なくとも "いま" フロントエンド部分の要素技術に手間暇を割きたくはありません ( 趣味のOSS開発&コミット活動としてなら何であれ参加できるのはたのしーのだけど… )
  2. neon build --release しかできない束縛ちょっと嫌かなぁ…
    • たぶん NEON ではマーシャリング部分のみ neon して、結果的には3層構造の Node.js 処理系 <--> neon した .rs から .js と cdylib のマーシャリング部分 <--> .rs pure な rlib なアーキテクチャーを前提にしているのかなーと思うので、そのために --release しか受け取らないのかなーとは感じるものの

加えて、NEON本体のソースについても要素技術の更新がかなーーり古いまま保守されていない部分もあり、「うーむ、これなら… template-rust-backend-with-electron-frontend にマーシャリング・コードの自動生成機能を cargo か npm / yarn のカスタムビルドでオレオレ機能追加した方がいいかなぁ…というお気持ちも。 NEON と良し悪しで戦う気はありませんが、アプローチの思想的にも、アーキテクチャー的にも差異を感じつつ、もう少し NEON については試しておこうかな…といった具合です。いまのところ。

RustのcdylibをバックエンドにElectronをフロントエンドにするcargo-generateプロジェクトテンプレートを公開したメモ

「RustのcdylibをバックエンドにElectronをフロントエンドにする」プロジェクトを3秒くらいでお手軽作れるcargo-generateできるプロジェクトテンプレートを公開しました。

f:id:USAGI-WRP:20200320172806p:plain

使い方:

cargo generate --git https://github.com/usagi/template-rust-backend-with-electron-frontend.git

使い方としての細かいことは README.md#Usage を読んでください。

このメモの主体は↑の使い方ではなく、作る最中に遭遇したトラブルシュートのメモです。

作るまでに遭遇したトラブルたち

  1. Rust の GUI Toolkit を採用したプロジェクトを作ろうと思ったけれど:
  2. Electron を GUI フロントエンド部に採用しようと思い:

こんな事がありました。ありましたが💪で解決できたのでとりあえずこの Rust をバックエンドに cdylib を書いて Electron をフロントエンドにイケイケできるプロジェクトのテンプレートを cargo-generate できるように公開しました。👍

ちなみに、この template-rust-backend-with-electron-frontend で GUI 部分をイケイケに作り込む場合はさらに TypeScript やクライアントサイド向けのフレームワークなどの要素技術スタックがアーキテクチャーに仲間入りしたり、 React を Angular / Vue へ変えたくなる事もあるかもしれません。🤔 バックエンド部の Rust も何をするアプリかにより crate を追加し、ビジネスロジックをにゃんにゃん書きます。もちろん、最小限の試作程度ならこの CSS なにそれ美味しいの的な HTML で GUI をごりっと書いて Rust でビジネスロジックの核心的な部分を書いて十分、それもアリです。

全ての技術要素を完全に理解して使う事は難しいと思いますが、このテンプレートを使えば Rust と Electron で可能性は無限大かつ現実的なやつがすぐに作れちゃうぞ的な美味しい感じでわくわくプロジェクトを始められると思います。たぶん。Rust 純度高めでも QML みたいなお手軽GUIホイホイできるようになるその日まで。(ΦωΦ)フフフ…

react-electron-ffi-(native dll/so/dylib) プロジェクトを作るメモ (2020-03-19版)

(1/2) react-electron

ここは今回のメモの本編ではないものの、"やり方"がころころ変わるようなので一応現時点での方法をついで程度に整理します。

(1) プロジェクトのディレクトリー(=リポジトリー)を create-react-app で生成:

npx create-react-app myapp
cd myapp

(2) electron 系のパッケージを追加:

yarn add electron electron-builder wait-on concurrently --dev
yarn add electron-is-dev

(3) public/electron.js を作る:

const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

const path = require('path');
const isDev = require('electron-is-dev');

let mainWindow;

function createWindow() {
  app.allowRendererProcessReuse = true;
  mainWindow = new BrowserWindow({width: 1920/4, height: 1080/4, webPreferences:{ nodeIntegration: true } });
  mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`);
  mainWindow.on('closed', () => mainWindow = null);
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow();
  }
});

(4) package.json に electron 用のお約束を追記:

(以下のコードはmergeする追記分のみ)

{ "main": "public/electron.js"
, "scripts":
  { "dev": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\""
  }
}

ここまでの時点で yarn dev で package.json に追記した scripts の dev を呼ぶと一応 react-scripts を介して react-electron が期待動作してくれるはず。しなかったらこのメモの作成時点とは何か変化が必要にどこかが変わっている可能性があります。 Node.js, electron, Chromium など要素技術の変化はかなり激しいので、そういう事があっても慌てず問題を探りましょう、未来のわたしへ。

(5) rescripts を導入し eject せず create-react-app を扱える様に調整:

(5-1) rescripts を install

yarn add @rescripts/cli @rescripts/rescript-env --dev

(5-2) package.json / scripts の start, build, testreact-scripts から resccripts へ変更:

(以下のコードは該当部分の変更後のみ)

{ "scripts":
  { "start": "rescripts start"
  , "build": "rescripts build"
  , "test": "rescripts test"
  }
}

(5-3) package.json / scripts に postinstall, preelectron-pack, electron-pack を追加:

(以下のコードはmergeする追記分のみ)

{ "scripts":
  { "postinstall": "electron-builder install-app-deps"
  , "preelectron-pack": "yarn build"
  , "electron-pack": "electron-builder build -w"
  }
}

ここで electron-packelectron-builder build -w-wWindows 向けのパッケージングを有効にする引数。OSX向けを有効にしたければ -mGNU/Linux 向けを有効にしたければ -lWindowsOSXGNU/Linux 向けを同時に有効にしたければ -wml のように定義でき、プロジェクトに応じて変更します。必要ならプラットフォームのアーキテクチャーも --ia32, --x64 で設定できます。

(5-4) package.json / scripts から eject を削除

rescripts を使う場合 eject を使う必要は無いし、残しておいても事故の元になるだけなので scripts から完全に消し去ります。

(5-5) package.json に rescripts を追記:

(以下のコードはmergeする追記分のみ)

{ "rescripts": [ "env" ]
}

これを書かないと electron のウィンドウ(=client-side)は動いても react な中身(=server-side)が期待動作しなくなります。

(5-6) .webpack.config.js を追加:

module.exports = config =>
{
  config.target = 'electron-renderer';
  return config;
}

(5-7) .rescripts.js を追加:

module.exports = [require.resolve('./.webpack.config.js')]

(6) package.json にパッケージング用のメタデータを追加:

{ "author":
  { "name": "Author Name"
  , "email": "aurhot.email@example.com"
  , "url": "https://author-website.example.com"
  }
, "build":
  { "appId": "com.my-website.my-app"
  , "productName": "MyApp"
  , "copyright": "Copyright © 2020 ${author}",
  , "mac":
    { "category": "public.app-category.utilities"
    }
  , "files":
    [ "build/**/*"
    , "node_modules/**/*"
  ]
, "directories":
  { "buildResources": "assets"
  }
}

(7) assets / icon.png を作成

  1. assets ディレクトリーを作成
  2. 256x256 の icon.png を放り込む(アプリのアイコンになります)

以上で react-electron の 2020-03-19 頃の準備手順は完了です。 yarn dev で開発版を実行、 yarn electron-pack でリリース版パッケージを dist ディレクトリーへ生成できるようになっています。なっていなければ、このメモの作成時点とは何か変化が必要にどこかが変わっている可能性があります。 Node.js, electron, Chromium など要素技術の変化はかなり激しいので、そういう事があっても慌てず問題を探りましょう、未来のわたしへ。

動作を確認したら次の react-electron を react-electron-ffi-(native dll/so/dylib) へ進みましょう。

(2/2) react-electron から react-electron-ffi-(native dll/so/dylib) へ

react-electron を使わない Node.js で FFI して native dll/so/dylib を呼ぶ方法は1つ前回書いた Node.js と FFI の 2020-03-18 時点でのメモ; node-ffi 系 → node-ffi-napi 系 - C++ ときどき ごはん、わりとてぃーぶれいく☆ のように node-ffi 系ではなく node-ffi-napi 系を使う事だけ知っていればわりと簡単に yarn add ffi-napi ref-napi して、

var ffi = require('ffi-napi')
var ref = require('ref-napi')
var s = ref.types.CString
var nyanko = ffi.Library( 'nyanko.dll',{ 'f': [ s, [] ] } )
console.log( nyanko.f() )

のように使えばよいだけです。これを踏まえて、 (1/2) で作成した react-electron のプロジェクトで、

  1. yarn add ffi-napi ref-napi して
  2. src/App.js で前述のような FFI コードを記述して実行

すると:

f:id:USAGI-WRP:20200318225023p:plain

こんな具合で死にます。☠ Node.js, react, electron に慣れていない初心者にはわけがわからない上にググらビリティーに問題を抱えたエラーの様に感じられますが、わかってしまえばどうという事はなくなります:

(1) public/electron.js の new BrowserWindowwebPreferences.nodeIntegration を明示的に true に変更する:

  mainWindow = new BrowserWindow({width: 1920/4, height: 1080/4, webPreferences:{ nodeIntegration: false } });

これをやらないと次の(2)だけやっても "window.require is not a function" とか electron のウィンドウにエラーが表示される事になります。nodeIntegrationtrue にしないと Node.js 部分の機能が有効にならないため、 fs も使えないとかそういう事が起こります。そうなると ffi も使えません。(2/2) の最初の死亡状態で無いと言われていた exists はたぶん fsexists でしょう。そこに気がつければこの問題と原因と解決方法へ繋がります。

(2) src/App.js では window.require('electron').remoterequire して FFI する

const remote = window.require('electron').remote;
const ffi = remote.require( 'ffi-napi' );
const ref = remote.require( 'ref-napi' );

こんな具合で期待動作します。これで例えば:

let s = ref.types.CString;
let nyanko = ffi.Library( '../fuga/nyanko.dll',{ 'f': [ s, [] ] } ); // 例として前回適当に作った文字列値を返すだけのライブラリーを束縛
let mewing = nyanko.f();

としておいて、 function App()return{mewing} を:

          Learn React {mewing} !

など仕込んで実行すると:

f:id:USAGI-WRP:20200319013223p:plain

こうなります。成功👍

もし、 yarn dev する場合とパッケージングして実行する場合では native dll/so/dylib のパスが変わる場合は:

const is_dev = remote.require( 'electron-is-dev' );
const nyanko_path = is_dev ? '../fuga/nyanko.dll' : 'nyanko.dll';
let nyanko = ffi.Library( nyanko_path,{ 'f': [ s, [] ] } )

と electron-is-dev を使うと簡単です。呼び出す native dll/so/dylib もオリジナルに作ったものを使用したいプロジェクトではこのように対応します。あるいは、たぶん実行時の引数かシェル変数で渡す事にしておいて dev では package.json の scripts/start で特殊化しても対応できそうですが、私は今回 electron-is-dev で満足したのでその方法については思いついただけに留まります。

ついで、蛇足となりますが自分用メモとして、 electron-is-dev の値は Boolean なので、これを文字列で表示したい場合は {is_dev} ではなく {is_dev.toString()} しないと文字列では見れません。このメモを書いた時に一度「あれ、文字列には…」と期待動作しない状態を見てしまったので自分用備忘録。

参考

Node.js と FFI の 2020-03-18 時点でのメモ; node-ffi 系 → node-ffi-napi 系

今回のメモの Node.js は:

node -v
v13.5.0

です。先ずは Node.js 初心者らしく npm i ffi して死にました:

npm i ffi

f:id:USAGI-WRP:20200318171359p:plain

ほか多数のエラーを観測し、死んでしまいました。☠

f:id:USAGI-WRP:20200318170856p:plain

どんどん使われなくなっているので何か代替に置き換わったのだろうと思い調べてみると、 ffi は Node.js < 11 の時代で死んでしまったらしい事、 Node >= 11 用には fork した @saleae/ffi が対応しているらしい事がわかりました。

既にこちらも Weekly download が怪しいですが、とりあえず npm i @saleae/ffi してみました:

npm i @saleae/ffi

f:id:USAGI-WRP:20200318171506p:plain

死亡。☠

ここで元々の ffi の Issues を眺めてみると、

が見つかり、

  1. ffi-napi が Node >= 12 用に使えるらしい
  2. ffi-napi はコールバックの実装にまだ問題があるかもしれないらしい(このメモより14日前の情報)

事がわかりました。コールバックの問題点は、

  1. Fix crasher in callback calls by atishay · Pull Request #56 · node-ffi-napi/node-ffi-napi · GitHub (このメモより6日前に merged )
  2. Port fix for Multithreaded callback crash by atishay · Pull Request #50 · node-ffi-napi/node-ffi-napi · GitHub (このメモより2ヶ月くらい前に merged )

あたりについて、特に日付的に #56 の方の注意だと思います。現在は解決しているようです。とりあえず ffi-napi もインストールチャレンジしてみました:

npm i ffi-napi

しばらくのち:

+ ffi-napi@2.4.7
added 10 packages from 57 contributors, removed 4 packages and audited 17 packages in 18.686s
found 0 vulnerabilities

エラーなく導入できました。👍

実際に使う場合は型のバインディングref も欲しいのですが、こちらも node-ffi-napi シリーズの repos が一通りあるので:

npm i ref-napi します:

npm i ffi-napi

しばらくのち:

+ ref-napi@1.4.3
updated 1 package, moved 1 package and audited 23 packages in 5.993s
found 0 vulnerabilities

ref-napi も導入できました。👍

これでFFIの最低限の準備が整いました。適当に動作テストしてみます。

(1) テスト用に FFI で呼ばれる native な .dll (.so, .dylib) を作ります:

今回はなんとなく Rust で呼ぶと "にゃおーん" 文字列を返してくれる nyanko.dll を作ります。

# Windows でやっている場合は PowerShell で echo すると utf16 になるので 
echo '#[no_mangle] pub extern fn f() -> * const u8 { "にゃおーん".as_ptr() }' > nyanko.utf16.rs

# UTF8 に一手間増えます(Windowsの謎仕様でBOMが付きますが rustc はスルーしてくれます)
Get-Content nyanko.utf16.rs | Out-File -Encoding utf8 nyanko.rs

# このくらいなら Cargo さんに頑張ってもらわなくても rustc 直呼びで手早く済ませられます
rustc --crate-type cdylib nyanko.rs

これで Node.js から ffi-napi により FFI で呼ばれる側の nyanko.dll ができました。一応 nyanko.rs のソースだけ書き残しておくと

#[no_mangle] pub extern fn f() -> * const u8
{
  "にゃおーん".as_ptr()
}

(2) テスト用に FFI で呼ぶ側の Node.js な tester.js を作ります:

echo "console.log( require('ffi-napi').Library( 'nyanko.dll',{ 'f': [ require('ref-napi').types.CString, [] ] } ).f() )" > tester.utf16.js
Get-Content tester.utf16.js | Out-File -Encoding utf8 tester.js

ワンライナーにしちゃうとこちらはしんどいですね…。素直に書いたソースは:

var ffi = require('ffi-napi')
var ref = require('ref-napi')
var s = ref.types.CString
var nyanko = ffi.Library( 'nyanko.dll',{ 'f': [ s, [] ] } )
console.log( nyanko.f() )

です😏

Node.js で tester.js を実行して node-ffi-napi / node-ref-napi が期待動作しネイティブライブラリーの nyanko.dll から "にゃおーん" が無事に返り表示されるはずです:

node tester.js
にゃおーん

成功しました👍

コンパクト化したコマンドライン

ついでに npm -> yarn 化。

# テスト用のディレクトリーと npm init していない場合はここから
mkdir myapp
cd myapp
yarn init -y
# ffi-napi, ref-napi を追加
yarn add ffi-napi ref-napi
# テスト用の native dll を作成
echo '#[no_mangle] pub extern fn f() -> * const u8 { "にゃおーん".as_ptr() }' > nyanko.utf16.rs
Get-Content nyanko.utf16.rs | Out-File -Encoding utf8 nyanko.rs
rustc --crate-type cdylib nyanko.rs
# テスト用の .js を作成
echo "console.log( require('ffi-napi').Library( 'nyanko.dll',{ 'f': [ require('ref-napi').types.CString, [] ] } ).f() )" > tester.utf16.js
Get-Content tester.utf16.js | Out-File -Encoding utf8 tester.js
# 動作確認
node tester.js
にゃおーん

参考