ProtocolBufferを使ってWebSocketでRPCをする

超暖冬だったりコロナウイルス騒ぎだったりと、なんと言うか全く落ち着かないこの頃、いかがお過ごしでしょうか。私は投資信託の金額が乱高下してなんとも言えない気持ちになっているのと、リーマンの再来とか言われてビクッとしている今日この頃です。 そういう世間の流れを一旦見ないことにして、最近やっと動作の確認が取れた、ProtocolBuffer + WebSocketによるRPCの方法を書こうかと思います。 <!–more–> さきにまとめ ここを見たら後の文は見なくていいんじゃないか疑惑が。 gRPCを利用しない場合、ブラウザと双方向でやり取りできるのはWebSocketくらい WebSocketではHTTP/2のようなpathは使えない JSON-RPCのような形式を使うとよい command的なもののEnumはデフォルト値をエラーとして使うといい 以降ではこれについての詳細を。 gRPCの利点と課題 ProtocolBufferを利用したRPCというと、gRPCがまっさきに出てきますし、ProtocolBufferを使う方の半分くらい(要出典)はこれが目的でしょう。ただし、それはgRPCを利用できる環境があるから、という前提が当然あります。 ではgRPCを利用できる条件とはどのようなものでしょうか。その大前提として、 HTTP/2 の存在があります。元々HTTP/2のベースであるSPDY自体、Googleが開発していたためでしょう。HTTP/2とHTTP/1.1の違いは次のようなものがあります。 HTTP/2はバイナリベース multiplexy Serverからクライアントに対してpushすることが出来る headerの圧縮が必須 gRPCは、これらのHTTP/2が持つ利点を活かして、高パフォーマンスかつ低遅延なRPCを実現しています。 間違いなく次世代のネットワークはHTTP/2ベースになると思いますが、OCamlではHTTP/2を利用する難易度がかなり高いです。また、ブラウザからJavaScriptを利用してHTTP/2接続を利用することも今は出来ません。 いま個人的に作っているツールでは、serverからのpushを必要としているので、HTTP/2が使えない場合、WebSocketを使うしかありません。しかし、WebSocketではgRPCを使うことは出来ません。 (gRPCは、HTTPのmethodやpathを利用して色々行っているため) WebSocketでRPCを実現しよう 改めて、WebSocketは以下のような特徴を持つprotocolです。 HTTPからのハンドシェイクをもって切り替える 一本のconnectionだけで通信を行う 双方向の通信が可能 完全に非同期 あるメッセージを待つ、というようなことは出来ない 参考:WebSocket Protocol仕様の日本語訳 しかし、JavaScriptでWebSocketを扱うプログラムを書いたことのある方はわかると思いますが、WebSocketは message という塊のやり取りしか出来ません。HTTP/1.1のようなpath/methodというようなものを使うことは出来ません。 RPCを実装する上では、そのメッセージがどのcommandに対するresponseなのか?を判別する必要があります。 ではどうするか?となりますが、ここで参考になるのが JSON-RPC です。 JSON-RPC自体、非常にLightな仕様ですが、大事なのが requestとresponseが完全に非同期である ということを前提としていることです。この特徴から、WebSocket上でも特に問題なく動作します。ということは、 JSON-RPCっぽいのをProtocolBufferで実装すればいいんではないか? という考えが浮かびます。 ProtocolBufferにJSON-RPCっぽいのを実装する では早速protoファイルを作ってみます。 syntax = "proto3"; enum Command { UNKNOWN_COMMAND= 0; FILER_INITIALIZE = 1; FILER_RELOAD_ALL= 2; FILER_MOVE_LOCATION= 3; FILER_UPDATED= 4; FILER_COPY_INTERACTION= 5; FILER_MOVE_INTERACTION= 6; FILER_DELETE_INTERACTION = 7; KEYMAP_ADD_KEY_BINDING= 8; KEYMAP_REMOVE_KEY_BINDING= 9; KEYMAP_GET = 10; KEYMAP_UPDATED = 11; } // common request message Request { string id = 1; Command command = 2; bytes payload = 3; } enum Status { UNKNOWN = 0; SUCCESS = 1; INVALID_REQUEST_PAYLOAD = 2; COMMAND_FAILED = 3; } message Error { int32 status = 1; string error_message = 2; } // common response. Field `id' must same value of the request. message Response { string id = 1; Status status = 2; bytes payload = 3; Error error = 4; } message SomeProcedureRequest { string fooBar = 0; } message SomeProcedureResponse { int32 count = 0; } service SampleService { rpc someProcedure(Request) returns (Response); } ポイントはいくつかありますが、特に大事だと感じたのは次の点です。 ...

March 15, 2020 · derui

hygen.ioでboilerplateを自動生成すると捗る話

閏年の閏日ということなので(?)、記事を書いておきます。特別な日にでも書いておかないとアウトプットがないので・・・。 今回は、最近使い始めて結構いい感じになってきた、hygen.ioについてです。 <!–more–> hygen.ioとは hygen.ioは、公式で以下のように紹介されています。 The scalable code generator that saves you time. 簡単に書くと、MavenとかGradleとかで初期構成を自動生成したり、create-react-appとかで生成したりといった、code generatorの一つです。 特徴としては 速度 と シンプルである ことで、複雑なDSLを覚える必要は特になく、簡単に使い始められます。また、後述する inject という機能のおかげで、自動生成しつつ、その情報を別ファイルに埋め込む、みたいなことが割と簡単です。 どんなprojectで使われてる? ここを見ると大体わかりそうです。JavaScript界隈での有名企業が入っていたりと、それなりに広く使われているようです。 なお、gulpとかnpm scriptとかMakefileでも出来るんちゃう?という気もしますし、実際出来ると思いますが、code generatorとして特化した機能を提供しているhygenを利用する方が、設定のごった煮になる可能性が低いかな・・・という気がします。 boilerplateを自動生成してみる 今個人で作業しているリポジトリでは、Reduxをmoduleという形で利用するとともに、多数のcommandというmoduleを生成する必要があります。ほとんどinterfaceだけは決まっているので、新しいcommandやmoduleを追加する度、同じようなファイルを生成したり、構造に気を使ったり・・・という作業が必要になります。 流石にこれはめんどくさい・・・となってきたので、hygenを利用していろいろ自動生成してみました。 hygen自体の使い方は公式サイトを見てもらったほうが良いと思いますので、リンクだけ貼っておきます。今回作ったgeneratorの構造はこんな感じです。 実際に使っているのはもうちょっと色々追加されています。 --+ _template |-+ module |-- help |-+ init | |-- actions-test.ejs.t | |-- actions.ejs.t | |-- index.ejs.t | |-- inject_reducer.ejs.t | |-- inject_import-module.ejs.t | |-- inject_action-type.ejs.t | |-- reducer-test.ejs.t | |-- reducer.ejs.t | |-- types.ejs.t |-- new-action これを使うと、こんな感じで新しいmoduleを追加したり、追加したmoduleに対して新しいactionを追加したり出来ます。 ...

February 29, 2020 · derui

polybarからi3blocksに乗り換えてみた

今年の冬は本当に暖冬で、今から今年の夏が心配です。水不足的な意味で。 時事ネタは置いといて、最近デスクトップのbarをpolybarからi3blockに移行したので、なんで移行したのかとかを書いておこうかと思います。 <!–more–> i3blocks i3blocksは、その説明にあるとおり、text-baseなstatus barです。polybarもだいたい同じような感じですね。これらの違いを個人的にあげてみます。 polybar feature rich 設定ファイルでだいたい何でもやる 組み込みで結構な量のmoduleがある i3blocks 本体機能は必要最小限 組み込みのmoduleはほぼ無い i3blocksは、組み込みmoduleとかがほぼない代わりに、moduleから返された結果を表示するだけ、というシンプルな形式に割り切っています。そのため、複雑化しやすい(個人の意見です)polybarよりも設定ファイルがシンプルになります。 polybarから乗り換えた理由 polybarは、その組み込みmoduleが多いことも相まって、コンパイル時の依存が多いです。出来るだけ依存を少なくしたいのと、利用しないmoduleが多かったというのもあり、他のstatus barを検討していました。 i3blocksは1.4まではデフォルトでmoduleを用意していましたが、1.5で大きく変更され、i3blocks本体は最小限で、moduleは全てi3blocks-contribに分割されています。(blockletと読んでいるようです)。このため、必要なものを自分でさっくり作るなり、必要なscriptだけを取得するとかが簡単です。 また、色とかもmodule内で出力することで個別に変更できるため、別ファイルにまとめておいて読み出す、とかもできます。configにまとめるのとどっちがいいか、というのはありますが・・・。 i3blocksの設定 color=#8fa1b3 [title] command=~/.config/i3blocks/scripts/title.sh interval=persist [uptime] label= command=uptime | sed 's/.*up \([^,]*\),.*/\1/' interval=60 [memory] label= command=~/.config/i3blocks/scripts/memory.sh interval=1 [load average] label= command=echo "$(uptime | sed 's/.*load average: \(.*\)/\1/' | cut -d, -f1)/$(grep 'processor' /proc/cpuinfo | wc -l)" interval=1 [date] label= command=echo " $(date '+%Y/%m/%d %H:%M(%a)')" interval=1 [power] label= command=~/.config/i3blocks/scripts/power.sh interval=persist [separator] 実際に使っているconfigです。polybarと違い、各moduleの設定は同一で、違うのはcommandとかintervalの中身だけ、という統一感がいい感じです。 ...

February 21, 2020 · derui

Domain Modeling Made Functionalを読んだ

個人的に作っているツールで、OCamlでどうやってDDDをやっていくか?ということを考える中で、 Domain Modeling Made Functionalというそのものズバリな本の存在を知りました。そこまで高くなかったので購入して読んでみたので、感想を書いてみます。 <iframe style=“width:120px;height:240px;” marginwidth=“0” marginheight=“0” scrolling=“no” frameborder=“0” src="//rcm-fe.amazon-adsystem.com/e/cm?lt1=_blank&bc1=000000&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=derui09-22&language=ja_JP&o=9&p=8&l=as4&m=amazon&f=ifr&ref=as_ss_li_til&asins=1680502549&linkId=05192cc54dff2d67c58d290cad5cdd28"></iframe> <!–more–> どんな内容? すごい簡単に書くと、 F#でDDDをやっていく時のノウハウが詰まっている 本です。たいていこういう本はScalaとかHaskellで書かれている印象(偏見)なので、F#というのが中々ニッチな印象でした。 ちなみにF#を知らない方のために紹介だけしておくと、F#は以下のような特徴を持つ言語です。 OCamlをベースにした関数型言語 ベースにしているので、命名規則とか文法とかは違いますが、ML族です なので、型クラスとかはありません .NET Platform上で動く 多分.NET Coreでも動くんではないでしょうか OCamlを使っている人間としては、F#の文法は若干の違和感を感じるくらいで、特に読みづらさとかは感じませんでした。 もうちょっと細かい内容 概ね、以下順で進んでいきます。 DDD自体の解説 仮想プロジェクトを使ったDomain導出の流れ この部分が、対話形式になっていてなかなか面白いです。また、ダイアグラムなどをあえて使わず、擬似言語を用いてユビキタス言語やビジネスの制約とかを書き下しているのが印象的でした。やってみたい ドメインをどうやって型に翻訳していくか ここからが関数型言語(特に代数的データ型を持つ言語)でどうやってドメインを型にしていくか、という話題です。この時点では実装を一切考えず、ビジネス要件を型の表現力でどう表現するか?に注力しています。 ワークフローをどう表現するか ビジネス上のワークフローを、小さいstepという関数で表現していくか、という内容です。ここでも実装そのものは行わず、step/work flowをひたすら型で表現していきます。 型に対する実装 ドメイン自体、そしてワークフローに対して行った大量の型をどのようにつなぎ合わせていくか、という内容です。ここから実装が登場します。バリデーションやエラーを扱う話題もあります。 関数でワークフローを表現した時、stepの依存などをどのように扱うか、という内容もあります。関数適用をDependency Injectionとして利用するなど、関数型言語で一般的なテクニックなども紹介しています。 エラー実装、永続化、シリアライズなど現実的な内容それぞれ独立した章に分かれていますが、全て実装に関する内容です。 エラーでは、主にResultをどう扱うか、Resultをどう繋げていくか、といった実践的な内容となっています。永続化、シリアライズでは、DBやJSONへのシリアライズなど、主にWebアプリケーションで扱いそうな内容を多く扱っています。 特に印象に残った点 DDDに当たる部分は、Evans本やIDDD本を読んでいれば、ある程度は読み飛ばしてしまっていいと思います。そこ以外で印象に残っていたり、参考になったものがいくつかあります。 とにかく型で表現する 文中には、必要に応じて減らしてもいい、という書き方をしています しかし、step/work flowすら型で表現する、というのか魅力的です IDとかは、実際にはfunctorで作ったり、ある程度自動的に導出することも出来るので、方はある程度多めになってもなんとかなる気はします Monadは必ずしも必要ではない 実際、文中ではMonadという言葉をほとんど使っていません 言及している部分では、 それほど恐れる必要はない という記述になっています Free Monadなどにも触れているので、実際のアプリケーションなどでは使うかもね・・・というニュアンスなのかもしれません 関数適用はDI 最近オブジェクト指向言語ばかりやっているのと、部分適用して使う、というのが普通すぎて、逆に目からウロコでした IOはEdgeに追いやる DomainはIOを知るべきではない、というのを何度も書いています Clean Architecture/Onion Architecture/Hexagonal Architectureといったアーキテクチャをより簡潔に言い表したものだなーって思います Edgeにどうやって追いやる?関数を使えよ、という当たり前の内容もちゃんと書いてくれています 最近OCamlで書いていると、なんとなくFunctorを使ってしまう部分でも、より基本的な関数をまず使おう、と思い直しました DTOをきっちり使う重要さ Domainを直接JSONなどに変換してはならない理由をちゃんと説明している点が非常に良かったです 個人的にもDomainをそのままAPIなどに露出しないようにしていますが、次からは何故そうするのか?と説得できそうな気がします 現実だと工数がかかりすぎる、とか言われそうですが・・・ 型パズルの解き方 大量の型が出てきた時に、どのように関数を繋げていくか、という方法論が書かれています 関数型言語でもDDDをやりたい人にはオススメです DDDをJavaとかC#、他の言語ではやっているけど、関数型言語ではどうやるんだろう、Monadとかよくわからない概念のオンパレードになるんじゃないか、とか思っている人にオススメです。 ...

February 7, 2020 · derui

Protocol BuffersとJSONの扱い in OCaml

以前の記事で、ProtocolBuffersでOCaml/TypeScript間の定義を作成して色々やっていたのですが、いくつか問題が出てきたので書いてみます。 <!–more–> ocaml-protoc-pluginに乗り換えた 前回は ocaml-protocを使いましたが、ocaml-protoc-pluginに乗り換えました。理由としては以下となります。 標準のprotocにおけるpluginという形での提供 自前で解析していてもいいとは思いますが、他のproductではprotocのplugin形式が多かったので 型定義にannotationを付けられる yojsonとかに変換するのが簡単です ts-protoc-gen + protoc標準から、pbjs(protobufjs)に乗り換えた 前はprotocに標準添付されているJavaScript実装と、ts-protoc-genを組み合わせていたのですが、以下のような問題があったので乗り換えました。 Jsonからの変換とかが出来ない protocの標準では、 protocolbuffers以外に対するserialize/deserializeがありません JSON-RPCを利用しているので、軽く絶望しながら切り替えました(先に調べろと 発生した問題 今作っているapplicationでは、Electronとbackend server間をWebsocketでつなぎ、RPCとしてJSON-RPCを利用しています。この構成、面倒ではありますが、割と使い勝手がよいのです。しかし、protoファイルから生成した型を使っていると、困る割にいい解決策が無い問題にあたりました。 その問題とは、 OCamlでの型定義とProtocolBufferでの型定義との互換性 です。ナンノコッチャですが、要はocaml-protoc-pluginが生成してくれる型と、protocol_conv_jsonのでのJSON変換が一筋縄では行かない、ということです。 私は大きく2つの問題にあたりましたが、そのうちの一つを例に上げます。 enum Types { Unknown = 0; String = 1; Number = 2; } message Foo { Types value = 0 } こんな感じのprotoファイルからOCamlの定義を作成すると、以下のような感じになります(moduleの定義は省略しています)。 open Ocaml_protoc_plugin.Runtime [@@warning "-33"] module rec Types : sig type t = Unknown | String | Number val to_int: t -> int val from_int: int -> (t, [> Runtime'.Result.error]) result end and Foo : sig val name': unit -> string type t = Types.t val to_proto: t -> Runtime'.Writer.t val from_proto: Runtime'.Reader.t -> (t, [> Runtime'.Result.error]) result end ここでポイントとなるのが、protoファイルにおいてenumと定義した部分と、OCaml版におけるTypes moduleです。OCamlにおいては、Enumを代数的データ型として表すのはごく自然だと思うのですが、問題はProtocol Buffersにおいては、enumは 単なる数値 でしかありません。 to_int とかがそれを物語っています。 ...

January 13, 2020 · derui

OCamlとTypeScriptをProtocal Buffersでつないでみる

気づいたら来週で今年の業務も終わりということに気づきました。今年もいろいろ・・・あったか? 今回は、最近色々と辛くなってきたので、初めてProtocol Buffers・・・というかProtocol Buffers languageを利用して、サーバー側=OCamlとクライアント側=TypeScriptで型定義を共有していきたいと思います。 ...

December 22, 2019 · derui

OCamlの開発環境2019末

この記事は株式会社オープンストリームアドベントカレンダーの3日目の記事です。 年の瀬ということで、いい感じに部屋が冷えてきてちょうどよいです。さて、 あまりネタがない 今回はOCamlの開発環境について書こうかと思います。 ...

December 3, 2019 · derui

Emacsの設定管理をuse-packageからleaf.elにしてみた

大分長い間use-packageを利用していましたが、一日掛けてleaf.elに移行してみました。leaf.elの利点や移行時の注意などをまとめたいと思います。 <!–more–> use-packageに感じていた問題点 ・・・というのは実はあまりないんですが、あえて言えば次のような点でした。 設定のgroupingがしづらい use-packageはネストすることを前提としていない?ので、packageの設定が分散しがち bindの設定方法が独特 aggressive-indentを使っていると、中々にindentが荒ぶります 標準パッケージをきちんと利用する方法がよくわからない あまり頻繁に.emacs.dを更新していない、というのもあるんですが、端的に言うと まーいいか という状態でした。 leaf.el leaf.elは、 leaf.el is yet another use-package. として作成されたpackageです。use-packageと比較してどうか?というのは、作者が書いている記事を見たほうが早いでしょう。 https://qiita.com/conao3/items/dc88bdadb0523ef95878 利用してみた感じでいうと、大体use-packageと同じ使用感ですが、色々と統一感が出るのがいい感じです。また、設定をグルーピングするという目的でも使えるので、use-packageで不自由だった部分が解消されて設定がスッキリしました。 移行後の内容は、以下のrepositoryを見てもらったほうが早いです。 https://github.com/derui/dot.emacs.d/blob/master/conf/package-config.el まだ修正中なので、いくつか不具合を抱えています。また、packageがあまりかかわらず、設定のフォルダとして利用した例は次のファイルに書いています。 https://github.com/derui/dot.emacs.d/blob/master/conf/emacs-base-setting.el leaf.elに移行してみて ただ、leaf.elもいいところばかりではなく、いくつか設定上の問題がありました。 bindingが上手く行かない問題 leaf.elでは、bindingに設定した関数は、基本的にそのpackage内の関数である、とみなそうとします。 (pp (macroexpand '(leaf evil :bind (:evil-normal-state-map ("f" . evil-forward-quote-char) ("F" . my:evil-forward)) :config (defun my:evil-forward () ())))) ;; => ;; (prog1 'evil ;; (leaf-handler-leaf-protect evil ;; (unless ;; (fboundp 'evil-forward-quote-char) ;; (autoload #'evil-forward-quote-char "evil" nil t)) ;; (unless ;; (fboundp 'my:evil-forward) ;; (autoload #'my:evil-forward "evil" nil t)) ;; (declare-function evil-forward-quote-char "evil") ;; (declare-function my:evil-forward "evil") ;; (defvar evil-normal-state-map) ;; (leaf-keys ;; ((:evil-normal-state-map :package evil ;; ("f" . evil-forward-quote-char) ;; ("F" . my:evil-forward)))) ;; (eval-after-load 'evil ;; '(progn ;; (defun my:evil-forward nil nil))))) こんな感じに。このとき、特に問題になるのが 自作関数 です。autoloadしようにも、そのpackage内に存在していないので、当然ながらload出来ません。また、こういう関数は、大抵このpackageの関数を使っているので、 :config 内に書いたりしています。そうなると、bindしようにも :config が実行されるのは、上の例でいくとevilがloadされた後になるんですが、その辺りが上手く動かない、というケースが多発しました。 ...

November 17, 2019 · derui

Windows上でOCamlアプリケーションを動かす デバッグ編

ようやく涼しくなってきたと思った瞬間に晩秋になってしまい、秋がなかったなぁ、としみじみと感じてしまいました。 第三回は、Cross Compileできたものの、上手く動かない、というときに役立つデバッグについて書きます。 <!–more–> Cross Compileしたバイナリの難しさ Linux上でクロスコンパイルしたバイナリですが、実際にこのバイナリを動かしてみると、問題が発生(Segfaultとか)することがあります。 特に最初はWineで動かすと思いますが、エラーの内容がメモリアドレスくらいしか無く、結構色々と辛いです。Windows上で実行してみるのも中々にしんどいです。普通にそのまま実行時エラーで落ちるので。 Visual Studioとかで動かしてみる、というのも手段だと思いますが、ここではあくまでLinux上で解決してみます。 gdbserverとgdb gdbには、remoteのgdbと繋げてローカルで実行できる gdbserver というツールが存在しています。 Debianであれば、まず以下でmingw向けのgdbserverと、mingwでコンパイルされたtargetをデバッグできるgdbをインストールします。 apt install mingw32-w64-gdbserver mingw32-w64-gdb-target これを使うと、以下のようにしてdebugを行えます。 wine /usr/share/win64/gdbserver :3000 sample.exe x86_64-w64-mingw32-gdb sample.exe # ここからGDB内 > remote target localhost:3000 # つながると普通の(若干コマンドが成約されていますが)gdbとして使えます。 > continue Program received signal SIGSEGV, Segmentation fault. 0x0000000000a19d1c in lwt_unix_not_available (feature=<optimized out>) at lwt_unix_stubs.c:107 107 lwt_unix_stubs.c: No such file or directory. (gdb) bt #0 0x0000000000a19d1c in lwt_unix_not_available (feature=<optimized out>) at lwt_unix_stubs.c:107 #1 0x0000000000a1b400 in lwt_unix_iov_max (a1=<optimized out>) at windows_not_available.c:16 #2 0x00000000008611ed in camlLwt_unix__entry () #3 0x0000000000000001 in ?? () 上記のように、Windows向けにビルドしたバイナリを、Linux上でデバッグできます。OCamlでビルドしたものであれば、上記のようにcaml系統のデバッグシンボルも見えるので、デバッグがはかどります。 ...

November 11, 2019 · derui

Windows上でOCamlアプリケーションを動かす ビルド環境編

歓迎会と送別会が連チャンになったらだいぶグロッキーになってしまい、睡眠を削って生活するのはもう無理だなぁと思いました。十分な睡眠によるパフォーマンス向上をなめてはいけない・・・。 さて、第二回は実際にCross Compileを行うための環境について書きたいと思います。 <!–more–> ビルド環境の選定 まずOCamlプログラムのクロスコンパイル・・・というかWindows向けのビルド環境をどうするか?です。Windows向けにビルドする場合、次のいずれかが主要な選択肢になります。 Linux上でMinGWをインストールし、Windows向けのバイナリをクロスコンパイルする Windows上でOCaml/OPAMをインストールし、Windows向けのバイナリをネイティブコンパイルする Windows上でコンパイルする場合、OCaml/OPAMそれぞれをビルドするか、もしくは配布されているバイナリを展開する方法があります。この内、自前でビルドする方法は後述するとおり結構厳しいです。一応チャレンジしてみましたが、敢え無く爆砕しました・・・。 と、いうことで、Linuxでのクロスコンパイルに一縷の望みを託してみます。 Windows上のOCamlについて OCaml本体は、実はWindows環境上でもちゃんとコンパイル・実行できるようになっています。(MSVC/Cygwinのいずれかが必要です)また、OPAMもWindows上でビルドできるようになっています・・・が。 OPAMをMinGWでビルドしてしまうと、 opam init が上手く動作しない、という問題が発生します・・・。これはOPAMでも認識されている問題です。このため、OPAMをWindows上で動かす場合、OCaml本体もCygwin向けにコンパイルする必要があります。 しかし、Cygwinでコンパイルしてしまうと、Cygwin1.dllというdllを同梱しないと動作しなくなります。Cygwin1.dllはライセンス的にも結構厳しいため、できれば付けたくありません・・・。 Linux上でのクロスコンパイル環境 OCamlプログラムを、Linux上でWindows向けにコンパイルする方法は、 MinGWでコンパイルされたOCamlコンパイラでコンパイル・リンクする という形になります。本来、Linux上でMinGWを利用してコンパイルされたバイナリは、Windows環境またはWineでしか動きません。 ここで前に紹介したocaml-cross-windowsが効いてきます。このリポジトリでは、Windowsバイナリを生成でき、Linux上で実行可能なOCamlコンパイラを提供してくれています。ただ、ocaml-cross-windowsでは、Debianとかを推奨?している雰囲気があります。しかし私の利用しているDistributionはGentoo・・・。 と、いうことで、こういうときはDockerに頼ります。Debianのベースイメージから、クロスコンパイル環境を整えます。 クロスコンパイル用のイメージを作成する FROM debian:bullseye RUN apt update \ && apt install -y --no-install-recommends opam gcc-mingw-w64-x86-64 gawk m4 git ca-certificates \ && rm -rf /var/cache/apt/archives \ && opam init -n --disable-sandboxing \ && opam switch install 4.08.0 \ && opam repository --all add windows git://github.com/derui/sxfiler-repo-windows \ && eval $(opam env) \ && opam install -y conf-flambda-windows \ && opam install -y ocaml-windows64 \ && opam install -y ocaml-windows COPY scripts/entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] entrypoint.sh の内容は、opamを利用できるようにしているだけです。ここでのポイントは、aptでopamを入れることで色々楽していることと、64bit向けのMinGW環境を導入していることです。gawkを入れているのは、地味にこれがないとOCaml自体のコンパイルに失敗するためです。 ...

November 2, 2019 · derui