Skip to content

Commit 8636427

Browse files
authored
Make Merlin look for dot-merlin-reader in the install directory (#179)
* Make Merlin look for dot-merlin-reader in the install directory * Use realpath
1 parent 1a4722e commit 8636427

File tree

4 files changed

+114
-9
lines changed

4 files changed

+114
-9
lines changed

src/kernel/mconfig_dot.ml

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,34 @@ end = struct
129129
| Dot_merlin -> "dot-merlin-reader"
130130
| Dune -> "dune"
131131

132+
let find_exe = function
133+
| Dot_merlin ->
134+
(* 1. If DOT_MERLIN_READER_EXE is defined, then use its value for the
135+
dot-merlin-reader exe
136+
2. If not, look in the same directory as the merlin executable for a
137+
dot-merlin-reader.
138+
3. If not, fallback to using whatever one is on the PATH. *)
139+
let get_from_env_var = lazy (Sys.getenv_opt "DOT_MERLIN_READER_EXE") in
140+
let get_from_same_dir_as_merlin_exe =
141+
lazy
142+
(let merlin_exe = Unix.realpath Sys.executable_name in
143+
let merlin_bin = Filename.dirname merlin_exe in
144+
let dot_merlin_reader_exe =
145+
Filename.concat merlin_bin "dot-merlin-reader"
146+
in
147+
match Sys.file_exists dot_merlin_reader_exe with
148+
| true -> Some dot_merlin_reader_exe
149+
| false -> None)
150+
in
151+
List.find_map_opt
152+
[ get_from_env_var; get_from_same_dir_as_merlin_exe ]
153+
~f:Lazy.force
154+
|> Option.value ~default:"dot-merlin-reader"
155+
| Dune ->
156+
(* Always use the dune on the PATH *)
157+
(* CR-someday: consider doing something better here *)
158+
"dune"
159+
132160
exception Process_exited
133161

134162
module Process = struct
@@ -148,10 +176,10 @@ end = struct
148176
let prog, args =
149177
match cfg with
150178
| Dot_merlin ->
151-
let prog = "dot-merlin-reader" in
179+
let prog = find_exe Dot_merlin in
152180
(prog, [| prog |])
153181
| Dune ->
154-
let prog = "dune" in
182+
let prog = find_exe Dune in
155183
(prog, [| prog; "ocaml-merlin"; "--no-print-directory" |])
156184
in
157185
let cwd = Sys.getcwd () in
@@ -393,8 +421,11 @@ let get_config { workdir; process_dir; configurator } path_abs =
393421
| Unix.Unix_error (ENOENT, "create_process", "dot-merlin-reader") ->
394422
let error =
395423
Printf.sprintf
396-
"%s could not find `dot-merlin-reader` in the PATH. Please make sure \
397-
that `dot-merlin-reader` is installed and in the PATH."
424+
"%s could not find `dot-merlin-reader`. Please make sure that \
425+
`dot-merlin-reader` is installed. `dot-merlin-reader` is expected to \
426+
be in the same directory as the merlin executable or on the PATH. You \
427+
may also specify the path to `dot-merlin-reader` via the \
428+
`DOT_MERLIN_READER_EXE` environment variable."
398429
(Lib_config.program_name ())
399430
in
400431
(empty_config, [ error ])

src/utils/std.ml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,17 @@ module List = struct
109109
| None -> filter_map ~f xs
110110
| Some x -> x :: filter_map ~f xs)
111111

112-
let rec find_map ~f = function
113-
| [] -> raise Not_found
112+
let rec find_map_opt ~f = function
113+
| [] -> None
114114
| x :: xs -> (
115115
match f x with
116-
| None -> find_map ~f xs
117-
| Some x' -> x')
116+
| None -> find_map_opt ~f xs
117+
| Some x' -> Some x')
118+
119+
let find_map ~f l =
120+
match find_map_opt ~f l with
121+
| None -> raise Not_found
122+
| Some x -> x
118123

119124
let rec map_end ~f l1 l2 =
120125
match l1 with
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
$ cat > .merlin <<EOF
2+
> UNIT_NAME Dot_merlin_was_successfully_read
3+
> EOF
4+
5+
6+
In real-world scenarios, ocamlmerlin and dot-merlin-reader are usually installed in the
7+
same directory (for example, via an opam install). In this situation, if dot-merlin-reader
8+
isn't on the PATH, we can still read the .merlin file.
9+
$ mkdir merlin-bin
10+
$ cp $(which dot-merlin-reader) merlin-bin
11+
$ cp $(which ocamlmerlin) merlin-bin
12+
$ cp $(which ocamlmerlin-server) merlin-bin
13+
$ cp $(which ocaml-index) merlin-bin
14+
15+
$ PATH="" merlin-bin/ocamlmerlin single dump-configuration -filename test.ml \
16+
> | jq .value.merlin.unit_name -r
17+
Dot_merlin_was_successfully_read
18+
19+
If ocamlmerlin is a symlink, it looks in the original install directory.
20+
21+
$ mkdir merlin-bin-symlink
22+
$ ln -s $(realpath merlin-bin/ocamlmerlin) merlin-bin-symlink/ocamlmerlin
23+
24+
$ PATH="" merlin-bin-symlink/ocamlmerlin single dump-configuration -filename test.ml \
25+
> | jq .value.merlin.unit_name -r
26+
Dot_merlin_was_successfully_read
27+
28+
The dot-merlin-reader in the same directory is prioritized over the one on the PATH.
29+
$ mkdir bad-dot-merlin-reader-bin
30+
$ cat > bad-dot-merlin-reader-bin/dot-merlin-reader <<EOF
31+
> #!/bin/sh
32+
> echo "this is a bad dot-merlin-reader" >&2
33+
> exit 1
34+
> EOF
35+
36+
$ PATH="bad-dot-merlin-reader-bin" \
37+
> merlin-bin/ocamlmerlin single dump-configuration -filename test.ml \
38+
> | jq .value.merlin.unit_name -r
39+
Dot_merlin_was_successfully_read
40+
41+
But we can fall back to the one on the PATH if a dot-merlin-reader doesn't exist in the
42+
same directory.
43+
$ cp -r merlin-bin merlin-bin-no-reader
44+
$ rm merlin-bin-no-reader/dot-merlin-reader
45+
46+
$ PATH="" \
47+
> merlin-bin-no-reader/ocamlmerlin single dump-configuration -filename test.ml \
48+
> | jq .value.merlin.unit_name -r
49+
null
50+
51+
$ PATH="merlin-bin" \
52+
> merlin-bin-no-reader/ocamlmerlin single dump-configuration -filename test.ml \
53+
> | jq .value.merlin.unit_name -r
54+
Dot_merlin_was_successfully_read
55+
56+
We can override using the DOT_MERLIN_READER_EXE environment variable.
57+
$ cp -r merlin-bin merlin-bin-bad-reader
58+
$ rm merlin-bin-bad-reader/dot-merlin-reader
59+
$ cp bad-dot-merlin-reader-bin/dot-merlin-reader merlin-bin-bad-reader
60+
61+
$ PATH="merlin-bin-bad-reader" \
62+
> merlin-bin-bad-reader/ocamlmerlin single dump-configuration -filename test.ml \
63+
> | jq .value.merlin.unit_name -r
64+
null
65+
66+
$ PATH="merlin-bin-bad-reader" DOT_MERLIN_READER_EXE="merlin-bin/dot-merlin-reader" \
67+
> merlin-bin-bad-reader/ocamlmerlin single dump-configuration -filename test.ml \
68+
> | jq .value.merlin.unit_name -r
69+
Dot_merlin_was_successfully_read

tests/test-dirs/config/no-dune.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@
2727

2828
$ cat output | jq '.value.merlin.failures'
2929
[
30-
"Merlin could not find `dot-merlin-reader` in the PATH. Please make sure that `dot-merlin-reader` is installed and in the PATH."
30+
"Merlin could not find `dot-merlin-reader`. Please make sure that `dot-merlin-reader` is installed. `dot-merlin-reader` is expected to be in the same directory as the merlin executable or on the PATH. You may also specify the path to `dot-merlin-reader` via the `DOT_MERLIN_READER_EXE` environment variable."
3131
]

0 commit comments

Comments
 (0)