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

jules ( + gemini ) など経由で触ってみるのにマイナーな言語での記述はどうだろう?ということが気になったりしていた。そんなわけで手元でも環境を作ってみることにした。
こんなことを呟いている
これはこの投稿へのリアクション
裏側の話として関数型まつり ( #fp_matsuri ) に触発されたみたいな話はある。

実際の環境設定とhello world
テキトーにgrokに聴いて調べてみることにする(あとでドキュメントなんかも見ないとだめかも知れない)
ocamlの現代的なインストール方法は何ですか?何かありますか?私はwsl上のubuntuでの利用を考えています。このときaptでインストールの他に環境のセットアップの方法はありますか?
- apt? opam?
- opamで行くことにする
- emacs? vscode?
- vscodeで行くことにする。

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.

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

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)

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

(これは個人的なキーバインディングの設定のコピー)
$ 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)
とかで見られる。

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

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

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

テキトーなコマンドを作成して試してみる。
gist uploadとかさせてみる。
テキトーにgeminiと相談して決める。

依存ライブラリのインストールとビルドは基本的には以下で済むみたい。
$ opam install . --deps-only
$ dune build
./_build/default/ 以下に現れる

ファイル構造
.
├── 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

色々修正が必要だった。
<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>

↑の変更はあんまり精査できていない。
(dependsとかが消えるのは本当に良いのか?雑にcopilot agentとかにお願いして雰囲気でやってる(後で理解しないとだめですね))
とりあえず、以下で動くことは確認した。
# README.md をupload
$ ./_build/default/bin/main.exe README.md --config config.json

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

これをjulesで変更できるかはわからない。