Open19

OCamlの環境設定をしてみる (wsl, ubuntu, 2025)

podhmopodhmo

jules ( + gemini ) など経由で触ってみるのにマイナーな言語での記述はどうだろう?ということが気になったりしていた。そんなわけで手元でも環境を作ってみることにした。

こんなことを呟いている

https://x.com/podhmo/status/1934201591596523928

これはこの投稿へのリアクション

https://x.com/jinjor/status/1933900101434618339


裏側の話として関数型まつり ( #fp_matsuri ) に触発されたみたいな話はある。

podhmopodhmo

手元で環境を作るの比べてjulesのようなリモート側で動作するコーディングエージェントだと毎回リポジトリをcloneして実行されるのだけれどその環境のセットアップが面倒な可能性がある。できても都度の初期化が遅すぎる可能性がある。

podhmopodhmo

実際の環境設定とhello world

テキトーにgrokに聴いて調べてみることにする(あとでドキュメントなんかも見ないとだめかも知れない)

ocamlの現代的なインストール方法は何ですか?何かありますか?私はwsl上のubuntuでの利用を考えています。このときaptでインストールの他に環境のセットアップの方法はありますか?

https://x.com/i/grok/share/aLoffcjozw2OYLcwKw4iugudV

  • apt? opam?
    • opamで行くことにする
  • emacs? vscode?
    • vscodeで行くことにする。
podhmopodhmo

OCaml自体のインストール

opamをインストールして、そのあとopam経由でOCaml5.2.0をインストールする。

$ sudo apt install opam
$ opam --version
2.1.5
$ opam init
No configuration file found, using built-in defaults.
Checking for available remotes: rsync and local, git, mercurial.
  - you won't be able to use darcs repositories unless you install the darcs command on your system.


<><> Fetching repository information ><><><><><><><><><><><><><><><><><><><><><>
Processing  1/1: [default: http]
Processing  1/1: [default: http]
[default] Initialised
default (at https://opam.ocaml.org):
    [WARNING] opam is out-of-date. Please consider updating it (https://opam.ocaml.org/doc/Install.html)


<><> Required setup - please read <><><><><><><><><><><><><><><><><><><><><><><>

  In normal operation, opam only alters files within ~/.opam.

  However, to best integrate with your system, some environment variables
  should be set. If you allow it to, this initialisation step will update
  your bash configuration by adding the following line to ~/.profile:

    test -r /home/po/.opam/opam-init/init.sh && . /home/po/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true

  Otherwise, every time you want to access your opam installation, you will
  need to run:

    eval $(opam env)

  You can always re-run this setup with 'opam init' later.

Do you want opam to modify ~/.profile? [N/y/f]
(default is 'no', use 'f' to choose a different file)
<><> Creating initial switch 'default' (invariant ["ocaml" {>= "4.05.0"}] - initially with ocaml-system)

<><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><><><>
Switch invariant: ["ocaml" {>= "4.05.0"}]

<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
∗ installed base-bigarray.base
∗ installed base-threads.base
∗ installed base-unix.base
⬇ retrieved ocaml-system.4.14.1  (https://opam.ocaml.org/cache)
∗ installed ocaml-system.4.14.1
⬇ retrieved ocaml-config.2  (2 extra sources)
∗ installed ocaml-config.2
∗ installed ocaml.4.14.1
Done.
podhmopodhmo

OCaml自体をインストールする。それなりに時間がかかった。

$ opam switch create 5.2.0

<><> Installing new switch packages <><><><><><><><><><><><><><><><><><><><><><>
Switch invariant: ["ocaml-base-compiler" {= "5.2.0"} | "ocaml-system" {= "5.2.0"}]

<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
∗ installed base-bigarray.base
∗ installed base-threads.base
∗ installed base-unix.base
∗ installed ocaml-options-vanilla.1
⬇ retrieved ocaml-config.3  (2 extra sources)
⬇ retrieved ocaml-base-compiler.5.2.0  (https://opam.ocaml.org/cache)
Processing 18/27: [ocaml-base-compiler: make]
∗ installed ocaml-base-compiler.5.2.0
∗ installed ocaml-config.3
∗ installed ocaml.5.2.0
∗ installed base-domains.base
∗ installed base-nnp.base
Done.
# Run eval $(opam env --switch=5.2.0) to update the current shell environment

OCamlのversionの切り替え。

$ ocaml -version
The OCaml toplevel, version 4.14.1

# 環境の一覧を表示
$ opam switch list
#  switch   compiler                   description
→  5.2.0    ocaml-base-compiler.5.2.0  5.2.0
   default  ocaml.4.14.1               default

[WARNING] The environment is not in sync with the current switch.
          You should run: eval $(opam env)

# 更新
$ eval $(opam env)
$ ocaml --version
The OCaml toplevel, version 5.2.0
podhmopodhmo

pathの追加

pathの追加をする必要があるようだ。自分は.bashrcと.bashrc_wslを分けている。そしてWSL専用のsetupは.bashrc_wslに書いている。

# ocamlの設定
if [ -d "$HOME/.opam" ] && command -v opam > /dev/null 2>&1; then
   eval "$(opam env)"
fi

実行できることを確認する

$ ocaml --version
The OCaml toplevel, version 5.2.0

$ ocaml -e 'print_endline "hello world!"'
hello world!

$ ocaml -e 'Printf.printf "Hello, world! (OCaml version: %s)\n" Sys.ocaml_version;;'
Hello, world! (OCaml version: 5.2.0)
podhmopodhmo

vscodeの設定

個人的な理由によりprofileを設定する。

$ code -n . --profile ocaml

インストールしたのは以下のような拡張だった。

# vscode上でCtrl + @でたちあげたターミナル (which codeが異なるみたい)

$ code --list-extensions 
Extensions installed on WSL: Ubuntu-24.04:
github.copilot
github.copilot-chat
ocamllabs.ocaml-platform
podhmopodhmo

(これは個人的なキーバインディングの設定のコピー)

$ cp /mnt/c/Users/<user>/AppData/Roaming/Code/User/keybindings.json  /mnt/c/Users/<user>/AppData/Roaming/Code/User/profiles/<profile>/

場所自体はコマンドパレットで Preferences: Open Keyboard Shortcuts (JSON) とかで見られる。

podhmopodhmo

こんな感じでインストールしたOCamlのバージョンが表示されていれば良さそう。

podhmopodhmo

コマンドパレットで OCaml: Install OCaml-LSP Server をやった。動いていそう。

podhmopodhmo

ちなみに現在の最新が5.3.0なのに5.2.0を使っているのはgrokの回答がそれだったから。

podhmopodhmo

テキトーなコマンドを作成して試してみる。

gist uploadとかさせてみる。

https://gist.github.com/podhmo/a05aac20773fe1806a953348e949ae31

テキトーにgeminiと相談して決める。

podhmopodhmo

依存ライブラリのインストールとビルドは基本的には以下で済むみたい。

$ opam install . --deps-only
$ dune build

./_build/default/ 以下に現れる

podhmopodhmo

ファイル構造

.
├── README.md
├── bin
│   ├── dune
│   └── main.ml
├── dune-project
├── lib
│   ├── config.ml
│   ├── config.mli
│   ├── dune
│   ├── gist_uploader.ml
│   └── gist_uploader.mli
└── ocaml-gist-uploader.opam

3 directories, 10 files

podhmopodhmo

色々修正が必要だった。

<details>

diff --git a/.gitignore b/.gitignore
index 14a8d2a..6d4f97c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,7 @@ _opam/
 *.o
 
 # Editors
-.merlin
\ No newline at end of file
+.merlin
+
+# credentials
+config.json
\ No newline at end of file
diff --git a/README.md b/README.md
index 8e8bbb1..02125a4 100644
--- a/README.md
+++ b/README.md
@@ -23,12 +23,17 @@ OCamlで書かれた、ファイルをGitHub Gistにアップロードするた
     cd ocaml-gist-uploader
     ```
 
-2.  依存関係をインストールします。
+2.  opamパッケージリストを更新します。(初回や依存関係エラーが出る場合に実行)
+    ```bash
+    opam update
+    ```
+
+3.  依存関係をインストールします。
     ```bash
     opam install . --deps-only
     ```
 
-3.  プロジェクトをビルドします。
+4.  プロジェクトをビルドします。
     ```bash
     dune build
     ```
diff --git a/bin/main.ml b/bin/main.ml
index 77a7619..b9f9312 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -45,6 +45,14 @@ let run_upload public title config_path auto_mode filepath =
               let error_msg = Printf.sprintf "Failed to upload Gist: %s" msg in
               Lwt.return (`Error (false, error_msg))
 
+let run_upload_sync public title config_path auto_mode filepath =
+  match Lwt_main.run (run_upload public title config_path auto_mode filepath) with
+  | `Ok () -> ()
+  | `Error (show_help, msg) ->
+      if show_help then Fmt.epr "%s\n%!" "@help@"
+      else Fmt.epr "Error: %s\n%!" msg;
+      exit 1
+
 (* Cmdliner setup *)
 open Cmdliner
 
@@ -71,7 +79,7 @@ let filepath =
   Arg.(required & pos 0 (some file) None & info [] ~docv:"FILE" ~doc)
 
 let main_term =
-  Term.(const run_upload $ public $ title $ config_path $ auto_mode $ filepath)
+  Term.(const run_upload_sync $ public $ title $ config_path $ auto_mode $ filepath)
 
 let cmd =
   let doc = "Upload a file to GitHub Gist" in
@@ -83,12 +91,4 @@ let cmd =
   in
   Cmd.v (Cmd.info "gist-uploader" ~version:"0.1.0" ~doc ~man) main_term
 
-let () =
-  let promise = main_term in
-  match Lwt_main.run promise with
-  | `Ok () -> exit 0
-  | `Error (show_help, msg) ->
-      if show_help then Fmt.epr "%s\n%!" (Cmd.help cmd)
-      else Fmt.epr "Error: %s\n%!" msg;
-      exit 1
-  | _ -> exit 1
\ No newline at end of file
+let () = ignore (Cmd.eval cmd)
\ No newline at end of file
diff --git a/dune-project b/dune-project
index 6ec3c25..b0c0627 100644
--- a/dune-project
+++ b/dune-project
@@ -1,6 +1,14 @@
 (lang dune 3.12)
 (name ocaml-gist-uploader)
 (generate_opam_files true)
-(source (github ocaml-gist-uploader/ocaml-gist-uploader))
-(authors "Your Name")
-(maintainers "Your Name <your-email@example.com>")
\ No newline at end of file
+
+(package
+ (name ocaml-gist-uploader)
+ (synopsis "A command-line tool to upload files to GitHub Gist")
+ (description "A command-line tool to upload files to GitHub Gist.")
+ (authors "Your Name")
+ (maintainers "Your Name <your-email@example.com>")
+ (license MIT)
+ (source (github ocaml-gist-uploader/ocaml-gist-uploader))
+ (homepage "https://github.com/ocaml-gist-uploader/ocaml-gist-uploader")
+ )
\ No newline at end of file
diff --git a/lib/config.ml b/lib/config.ml
index 627c96a..11a66de 100644
--- a/lib/config.ml
+++ b/lib/config.ml
@@ -7,7 +7,7 @@ let get_api_key { api_key } = api_key
 let from_file path =
   Lwt.catch
     (fun () ->
-      let* json = Yojson.Safe.Lwt.from_file path in
+      let* json = Lwt_preemptive.detach Yojson.Safe.from_file path in
       match Yojson.Safe.Util.member "apikey" json with
       | `String api_key -> Lwt.return_ok { api_key }
       | _ -> Lwt.return_error "JSON file must contain an 'apikey' field with a string value.")
diff --git a/lib/dune b/lib/dune
index d97283d..42b1b14 100644
--- a/lib/dune
+++ b/lib/dune
@@ -1,4 +1,4 @@
 (library
  (name ocaml_gist_uploader)
  (public_name ocaml-gist-uploader)
- (libraries lwt lwt.unix lwt_process cohttp-lwt-unix yojson))
\ No newline at end of file
+ (libraries lwt lwt.unix cohttp-lwt-unix yojson))
\ No newline at end of file
diff --git a/ocaml-gist-uploader.opam b/ocaml-gist-uploader.opam
index 11faccf..2c9988c 100644
--- a/ocaml-gist-uploader.opam
+++ b/ocaml-gist-uploader.opam
@@ -1,18 +1,15 @@
 # This file is generated by dune, edit dune-project instead
 opam-version: "2.0"
 synopsis: "A command-line tool to upload files to GitHub Gist"
+description: "A command-line tool to upload files to GitHub Gist."
 maintainer: ["Your Name <your-email@example.com>"]
 authors: ["Your Name"]
-source: "https://github.com/ocaml-gist-uploader/ocaml-gist-uploader"
-bug-reports: "https://github.com/ocaml-gist-uploader/ocaml-gist-uploader/issues"
+license: "MIT"
+homepage: "https://github.com/ocaml-gist-uploader/ocaml-gist-uploader"
+bug-reports:
+  "https://github.com/ocaml-gist-uploader/ocaml-gist-uploader/issues"
 depends: [
-  "ocaml" {>= "5.0.0"}
   "dune" {>= "3.12"}
-  "cmdliner" {>= "1.1.0"}
-  "cohttp-lwt-unix" {>= "5.0.0"}
-  "lwt_ppx" {>= "2.1.0"}
-  "lwt_process" {>= "1.1.0"}
-  "yojson" {>= "2.1.0"}
   "odoc" {with-doc}
 ]
 build: [
@@ -28,4 +25,6 @@ build: [
     "@runtest" {with-test}
     "@doc" {with-doc}
   ]
-]
\ No newline at end of file
+]
+dev-repo:
+  "git+https://github.com/ocaml-gist-uploader/ocaml-gist-uploader.git"

</details>

podhmopodhmo

↑の変更はあんまり精査できていない。
(dependsとかが消えるのは本当に良いのか?雑にcopilot agentとかにお願いして雰囲気でやってる(後で理解しないとだめですね))

とりあえず、以下で動くことは確認した。

# README.md をupload
$  ./_build/default/bin/main.exe README.md  --config config.json 

https://gist.github.com/podhmo/d478d1d06e0f8a0e80c920d59bdf20e3

podhmopodhmo

tls

途中で以下のようなエラーが出た。言われた通りにopam installした。

$ ./_build/default/bin/main.exe README.md  --config config.json Error: Failed to upload Gist: HTTP request failed: Failure("No SSL or TLS support compiled into Conduit. You must install it through opam with one of these commands: `opam install lwt_ssl` or `opam install tls-lwt`")
$ opam install lwt_ssl