diff --git a/src/kernel/mconfig_dot.ml b/src/kernel/mconfig_dot.ml index 65d94e76b..c146c8733 100644 --- a/src/kernel/mconfig_dot.ml +++ b/src/kernel/mconfig_dot.ml @@ -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 = Unix.realpath 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 @@ -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 @@ -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 ]) diff --git a/src/utils/std.ml b/src/utils/std.ml index af969edcb..26637f52e 100644 --- a/src/utils/std.ml +++ b/src/utils/std.ml @@ -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 diff --git a/tests/test-dirs/config/dot-merlin-reader/find-dot-merlin-reader.t b/tests/test-dirs/config/dot-merlin-reader/find-dot-merlin-reader.t new file mode 100644 index 000000000..91c48fbcc --- /dev/null +++ b/tests/test-dirs/config/dot-merlin-reader/find-dot-merlin-reader.t @@ -0,0 +1,69 @@ + $ cat > .merlin < 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 + +If ocamlmerlin is a symlink, it looks in the original install directory. + + $ mkdir merlin-bin-symlink + $ ln -s $(realpath merlin-bin/ocamlmerlin) merlin-bin-symlink/ocamlmerlin + + $ PATH="" merlin-bin-symlink/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 < #!/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 diff --git a/tests/test-dirs/config/no-dune.t b/tests/test-dirs/config/no-dune.t index 093914a4d..ecf944de9 100644 --- a/tests/test-dirs/config/no-dune.t +++ b/tests/test-dirs/config/no-dune.t @@ -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." ]