Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions src/kernel/mconfig_dot.ml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ end = struct
| Dot_merlin -> "dot-merlin-reader"
| Dune -> "dune"

let find_exe = function
| Dot_merlin ->
(* 1. If DOT_MERLIN_READER_EXE is defined, then use its value for the
dot-merlin-reader exe
2. If not, look in the same directory as the merlin executable for a
dot-merlin-reader.
3. If not, fallback to using whatever one is on the PATH. *)
let get_from_env_var = lazy (Sys.getenv_opt "DOT_MERLIN_READER_EXE") in
let get_from_same_dir_as_merlin_exe =
lazy
(let merlin_exe = Sys.executable_name in
let merlin_bin = Filename.dirname merlin_exe in
let dot_merlin_reader_exe =
Filename.concat merlin_bin "dot-merlin-reader"
in
match Sys.file_exists dot_merlin_reader_exe with
| true -> Some dot_merlin_reader_exe
| false -> None)
in
List.find_map_opt
[ get_from_env_var; get_from_same_dir_as_merlin_exe ]
~f:Lazy.force
|> Option.value ~default:"dot-merlin-reader"
| Dune ->
(* Always use the dune on the PATH *)
(* CR-someday: consider doing something better here *)
"dune"

exception Process_exited

module Process = struct
Expand All @@ -148,10 +176,10 @@ end = struct
let prog, args =
match cfg with
| Dot_merlin ->
let prog = "dot-merlin-reader" in
let prog = find_exe Dot_merlin in
(prog, [| prog |])
| Dune ->
let prog = "dune" in
let prog = find_exe Dune in
(prog, [| prog; "ocaml-merlin"; "--no-print-directory" |])
in
let cwd = Sys.getcwd () in
Expand Down Expand Up @@ -393,8 +421,11 @@ let get_config { workdir; process_dir; configurator } path_abs =
| Unix.Unix_error (ENOENT, "create_process", "dot-merlin-reader") ->
let error =
Printf.sprintf
"%s could not find `dot-merlin-reader` in the PATH. Please make sure \
that `dot-merlin-reader` is installed and in the PATH."
"%s 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."
(Lib_config.program_name ())
in
(empty_config, [ error ])
Expand Down
13 changes: 9 additions & 4 deletions src/utils/std.ml
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,17 @@ module List = struct
| None -> filter_map ~f xs
| Some x -> x :: filter_map ~f xs)

let rec find_map ~f = function
| [] -> raise Not_found
let rec find_map_opt ~f = function
| [] -> None
| x :: xs -> (
match f x with
| None -> find_map ~f xs
| Some x' -> x')
| None -> find_map_opt ~f xs
| Some x' -> Some x')

let find_map ~f l =
match find_map_opt ~f l with
| None -> raise Not_found
| Some x -> x

let rec map_end ~f l1 l2 =
match l1 with
Expand Down
60 changes: 60 additions & 0 deletions tests/test-dirs/config/dot-merlin-reader/find-dot-merlin-reader.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
$ cat > .merlin <<EOF
> UNIT_NAME Dot_merlin_was_successfully_read
> EOF


In real-world scenarios, ocamlmerlin and dot-merlin-reader are usually installed in the
same directory (for example, via an opam install). In this situation, if dot-merlin-reader
isn't on the PATH, we can still read the .merlin file.
$ mkdir merlin-bin
$ cp $(which dot-merlin-reader) merlin-bin
$ cp $(which ocamlmerlin) merlin-bin
$ cp $(which ocamlmerlin-server) merlin-bin
$ cp $(which ocaml-index) merlin-bin

$ PATH="" merlin-bin/ocamlmerlin single dump-configuration -filename test.ml \
> | jq .value.merlin.unit_name -r
Dot_merlin_was_successfully_read

The dot-merlin-reader in the same directory is prioritized over the one on the PATH.
$ mkdir bad-dot-merlin-reader-bin
$ cat > bad-dot-merlin-reader-bin/dot-merlin-reader <<EOF
> #!/bin/sh
> echo "this is a bad dot-merlin-reader" >&2
> exit 1
> EOF

$ PATH="bad-dot-merlin-reader-bin" \
> merlin-bin/ocamlmerlin single dump-configuration -filename test.ml \
> | jq .value.merlin.unit_name -r
Dot_merlin_was_successfully_read

But we can fall back to the one on the PATH if a dot-merlin-reader doesn't exist in the
same directory.
$ cp -r merlin-bin merlin-bin-no-reader
$ rm merlin-bin-no-reader/dot-merlin-reader

$ PATH="" \
> merlin-bin-no-reader/ocamlmerlin single dump-configuration -filename test.ml \
> | jq .value.merlin.unit_name -r
null

$ PATH="merlin-bin" \
> merlin-bin-no-reader/ocamlmerlin single dump-configuration -filename test.ml \
> | jq .value.merlin.unit_name -r
Dot_merlin_was_successfully_read

We can override using the DOT_MERLIN_READER_EXE environment variable.
$ cp -r merlin-bin merlin-bin-bad-reader
$ rm merlin-bin-bad-reader/dot-merlin-reader
$ cp bad-dot-merlin-reader-bin/dot-merlin-reader merlin-bin-bad-reader

$ PATH="merlin-bin-bad-reader" \
> merlin-bin-bad-reader/ocamlmerlin single dump-configuration -filename test.ml \
> | jq .value.merlin.unit_name -r
null

$ PATH="merlin-bin-bad-reader" DOT_MERLIN_READER_EXE="merlin-bin/dot-merlin-reader" \
> merlin-bin-bad-reader/ocamlmerlin single dump-configuration -filename test.ml \
> | jq .value.merlin.unit_name -r
Dot_merlin_was_successfully_read
2 changes: 1 addition & 1 deletion tests/test-dirs/config/no-dune.t
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@

$ cat output | jq '.value.merlin.failures'
[
"Merlin could not find `dot-merlin-reader` in the PATH. Please make sure that `dot-merlin-reader` is installed and in the PATH."
"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."
]