diff --git a/bin/describe/describe.ml b/bin/describe/describe.ml index a844b802d4a..e37f3b49e7a 100644 --- a/bin/describe/describe.ml +++ b/bin/describe/describe.ml @@ -19,6 +19,7 @@ let subcommands = ; Aliases_targets.Aliases_cmd.command ; Package_entries.command ; Describe_pkg.command + ; Describe_contexts.command ] ;; diff --git a/bin/describe/describe_contexts.ml b/bin/describe/describe_contexts.ml new file mode 100644 index 00000000000..b0119d902cf --- /dev/null +++ b/bin/describe/describe_contexts.ml @@ -0,0 +1,23 @@ +open Import + +let term = + let+ builder = Common.Builder.term in + let common, config = Common.init builder in + Scheduler.go ~common ~config + @@ fun () -> + let open Fiber.O in + let* setup = Import.Main.setup () in + let+ setup = Memo.run setup in + let ctxts = + List.map + ~f:(fun (name, _) -> Context_name.to_string name) + (Context_name.Map.to_list setup.scontexts) + in + List.iter ctxts ~f:print_endline +;; + +let command = + let doc = "List the build contexts available in the workspace." in + let info = Cmd.info ~doc "contexts" in + Cmd.v info term +;; diff --git a/bin/describe/describe_contexts.mli b/bin/describe/describe_contexts.mli new file mode 100644 index 00000000000..5422c3c119b --- /dev/null +++ b/bin/describe/describe_contexts.mli @@ -0,0 +1,4 @@ +open Import + +(** Dune command to print out the available build contexts.*) +val command : unit Cmd.t diff --git a/bin/ocaml/ocaml_merlin.ml b/bin/ocaml/ocaml_merlin.ml index 7ee94602065..e68a5cd3617 100644 --- a/bin/ocaml/ocaml_merlin.ml +++ b/bin/ocaml/ocaml_merlin.ml @@ -1,13 +1,34 @@ open Import +module Selected_context = struct + let arg = + let ctx_name_conv = + let parse ctx_name = + match Context_name.of_string_opt ctx_name with + | None -> Error (`Msg (Printf.sprintf "Invalid context name %S" ctx_name)) + | Some ctx_name -> Ok ctx_name + in + let print ppf t = Stdlib.Format.fprintf ppf "%s" (Context_name.to_string t) in + Arg.conv ~docv:"context" (parse, print) + in + Arg.( + value + & opt ctx_name_conv Context_name.default + & info + [ "context" ] + ~docv:"CONTEXT" + ~doc:"Select the Dune build context that will be used to return information") + ;; +end + module Server : sig - val dump : string -> unit Fiber.t - val dump_dot_merlin : string -> unit Fiber.t + val dump : selected_context:Context_name.t -> string -> unit Fiber.t + val dump_dot_merlin : selected_context:Context_name.t -> string -> unit Fiber.t (** Once started the server will wait for commands on stdin, read the requested merlin dot file and return its content on stdout. The server will halt when receiving EOF of a bad csexp. *) - val start : unit -> unit Fiber.t + val start : selected_context:Context_name.t -> unit -> unit Fiber.t end = struct open Fiber.O @@ -129,35 +150,39 @@ end = struct |> error ;; - let to_local file = + let to_local ~selected_context file = match to_local file with | Error s -> Fiber.return (Error s) | Ok file -> - let+ workspace = Memo.run (Workspace.workspace ()) in - let module Context_name = Dune_engine.Context_name in - (match workspace.merlin_context with - | None -> Error "no merlin context configured" - | Some context -> - Ok (Path.Build.append_local (Context_name.build_dir context) file)) + (match Dune_engine.Context_name.is_default selected_context with + | false -> + Fiber.return + (Ok (Path.Build.append_local (Context_name.build_dir selected_context) file)) + | true -> + let+ workspace = Memo.run (Workspace.workspace ()) in + (match workspace.merlin_context with + | None -> Error "no merlin context configured" + | Some context -> + Ok (Path.Build.append_local (Context_name.build_dir context) file))) ;; - let print_merlin_conf file = - to_local file + let print_merlin_conf ~selected_context file = + to_local ~selected_context file >>| (function | Error s -> Merlin_conf.make_error s | Ok file -> load_merlin_file file) >>| Merlin_conf.to_stdout ;; - let dump s = - to_local s + let dump ~selected_context s = + to_local ~selected_context s >>| function | Error mess -> Printf.eprintf "%s\n%!" mess | Ok path -> get_merlin_files_paths path |> List.iter ~f:Merlin.Processed.print_file ;; - let dump_dot_merlin s = - to_local s + let dump_dot_merlin ~selected_context s = + to_local ~selected_context s >>| function | Error mess -> Printf.eprintf "%s\n%!" mess | Ok path -> @@ -165,12 +190,13 @@ end = struct Merlin.Processed.print_generic_dot_merlin files ;; - let start () = + let start ~selected_context () = + let open Fiber.O in let rec main () = match Commands.read_input stdin with | Halt -> Fiber.return () | File path -> - let* () = print_merlin_conf path in + let* () = print_merlin_conf ~selected_context path in main () | Unknown msg -> Merlin_conf.to_stdout (Merlin_conf.make_error msg); @@ -192,7 +218,8 @@ module Dump_config = struct let term = let+ builder = Common.Builder.term - and+ dir = Arg.(value & pos 0 dir "" & info [] ~docv:"PATH") in + and+ dir = Arg.(value & pos 0 dir "" & info [] ~docv:"PATH") + and+ selected_context = Selected_context.arg in let common, config = let builder = let builder = Common.Builder.forbid_builds builder in @@ -200,7 +227,7 @@ module Dump_config = struct in Common.init builder in - Scheduler.go ~common ~config (fun () -> Server.dump dir) + Scheduler.go ~common ~config (fun () -> Server.dump ~selected_context dir) ;; let command = Cmd.v info term @@ -222,7 +249,8 @@ let man = let start_session_info name = Cmd.info name ~doc ~man let start_session_term = - let+ builder = Common.Builder.term in + let+ builder = Common.Builder.term + and+ selected_context = Selected_context.arg in let common, config = let builder = let builder = Common.Builder.forbid_builds builder in @@ -230,7 +258,7 @@ let start_session_term = in Common.init builder in - Scheduler.go ~common ~config Server.start + Scheduler.go ~common ~config (Server.start ~selected_context) ;; let command = Cmd.v (start_session_info "ocaml-merlin") start_session_term @@ -264,7 +292,7 @@ module Dump_dot_merlin = struct ~doc: "The path to the folder of which the configuration should be printed. \ Defaults to the current directory.") - in + and+ selected_context = Selected_context.arg in let common, config = let builder = let builder = Common.Builder.forbid_builds builder in @@ -274,8 +302,8 @@ module Dump_dot_merlin = struct in Scheduler.go ~common ~config (fun () -> match path with - | Some s -> Server.dump_dot_merlin s - | None -> Server.dump_dot_merlin ".") + | Some s -> Server.dump_dot_merlin ~selected_context s + | None -> Server.dump_dot_merlin ~selected_context ".") ;; let command = Cmd.v info term diff --git a/doc/changes/10324.md b/doc/changes/10324.md new file mode 100644 index 00000000000..9e6f2577c5f --- /dev/null +++ b/doc/changes/10324.md @@ -0,0 +1,5 @@ +- Add new flag `--context` to `dune ocaml-merlin`, which allows to select a Dune + context when requesting Merlin config. Add `dune describe contexts` + subcommand. Introduce a field `generate_merlin_rules` for contexts declared in + the workspace, that allows to optionally produce Merlin rules for other + contexts besides the one selected for Merlin (#10324, @jchavarri) diff --git a/doc/reference/dune-workspace/context.rst b/doc/reference/dune-workspace/context.rst index 8f5dbdf1410..af42d8ecc90 100644 --- a/doc/reference/dune-workspace/context.rst +++ b/doc/reference/dune-workspace/context.rst @@ -21,6 +21,9 @@ the description of an opam switch, as follows: - ``(merlin)`` instructs Dune to use this build context for Merlin. +- ``(generate_merlin_rules)`` instructs Dune to generate Merlin rules for this + context, even if it is not the one selected via ``(merlin)``. + - ``(profile )`` sets a different profile for a :term:`build context`. This has precedence over the command-line option ``--profile``. diff --git a/src/dune_rules/context.ml b/src/dune_rules/context.ml index 9dcbc39d18f..fa95e916a0d 100644 --- a/src/dune_rules/context.ml +++ b/src/dune_rules/context.ml @@ -150,7 +150,10 @@ module Builder = struct extend_paths ~env paths in { t with - merlin + merlin = + (match merlin with + | Selected -> true + | Rules_only | Not_selected -> false) ; profile ; dynamically_linked_foreign_archives ; instrument_with @@ -610,7 +613,13 @@ module Group = struct | Opam opam -> Builder.set_workspace_base builder opam.base | Default default -> let builder = Builder.set_workspace_base builder default.base in - let merlin = workspace.merlin_context = Some (Workspace.Context.name context) in + let merlin = + workspace.merlin_context = Some (Workspace.Context.name context) + || + match default.base.merlin with + | Rules_only -> true + | Not_selected | Selected -> false + in { builder with merlin } in match context with diff --git a/src/dune_rules/workspace.ml b/src/dune_rules/workspace.ml index ce98579da2b..99ba2963cf4 100644 --- a/src/dune_rules/workspace.ml +++ b/src/dune_rules/workspace.ml @@ -266,6 +266,28 @@ module Context = struct ;; end + module Merlin = struct + type t = + | Selected + | Rules_only + | Not_selected + + let equal x y = + match x, y with + | Selected, Selected | Rules_only, Rules_only | Not_selected, Not_selected -> true + | Selected, (Rules_only | Not_selected) + | (Rules_only | Not_selected), Selected + | Rules_only, Not_selected + | Not_selected, Rules_only -> false + ;; + + let to_dyn : t -> Dyn.t = function + | Selected -> String "selected" + | Rules_only -> String "rules_only" + | Not_selected -> String "not_selected" + ;; + end + module Common = struct type t = { loc : Loc.t @@ -279,7 +301,7 @@ module Context = struct ; fdo_target_exe : Path.t option ; dynamically_linked_foreign_archives : bool ; instrument_with : Lib_name.t list - ; merlin : bool + ; merlin : Merlin.t } let to_dyn { name; targets; host_context; _ } = @@ -318,7 +340,7 @@ module Context = struct dynamically_linked_foreign_archives t.dynamically_linked_foreign_archives && List.equal Lib_name.equal instrument_with t.instrument_with - && Bool.equal merlin t.merlin + && Merlin.equal merlin t.merlin ;; let fdo_suffix t = @@ -384,7 +406,12 @@ module Context = struct "instrument_with" (Dune_lang.Syntax.since syntax (2, 7) >>> repeat Lib_name.decode) and+ loc = loc - and+ merlin = field_b "merlin" in + and+ merlin = field_b "merlin" + and+ generate_merlin_rules = + field_b + ~check:(Dune_lang.Syntax.since Stanza.syntax (3, 16)) + "generate_merlin_rules" + in fun ~profile_default ~instrument_with_default -> let profile = Option.value profile ~default:profile_default in let instrument_with = @@ -409,7 +436,13 @@ module Context = struct ; fdo_target_exe ; dynamically_linked_foreign_archives ; instrument_with - ; merlin + ; merlin = + (match merlin with + | true -> Selected + | false -> + (match generate_merlin_rules with + | true -> Rules_only + | false -> Not_selected)) } ;; end @@ -571,7 +604,7 @@ module Context = struct ; fdo_target_exe = None ; dynamically_linked_foreign_archives = true ; instrument_with = Option.value instrument_with ~default:[] - ; merlin = false + ; merlin = Not_selected } } ;; @@ -840,11 +873,11 @@ let step1 clflags = !defined_names (Context_name.Set.of_list (Context.all_names ctx)); match Context.base ctx, acc with - | { merlin = true; _ }, Some _ -> + | { merlin = Selected; _ }, Some _ -> User_error.raise ~loc:(Context.loc ctx) [ Pp.text "you can only have one context for merlin" ] - | { merlin = true; _ }, None -> Some name + | { merlin = Selected; _ }, None -> Some name | _ -> acc) in let contexts = diff --git a/src/dune_rules/workspace.mli b/src/dune_rules/workspace.mli index e6093120b18..ff29b1bceb0 100644 --- a/src/dune_rules/workspace.mli +++ b/src/dune_rules/workspace.mli @@ -38,6 +38,16 @@ module Context : sig val equal : t -> t -> bool end + module Merlin : sig + type t = + | Selected + | Rules_only + | Not_selected + + val equal : t -> t -> bool + val to_dyn : t -> Dyn.t + end + module Common : sig type t = { loc : Loc.t @@ -57,7 +67,7 @@ module Context : sig the runtime system. *) ; dynamically_linked_foreign_archives : bool ; instrument_with : Lib_name.t list - ; merlin : bool + ; merlin : Merlin.t } end diff --git a/test/blackbox-tests/test-cases/describe/describe-contexts.t b/test/blackbox-tests/test-cases/describe/describe-contexts.t new file mode 100644 index 00000000000..bae930a4820 --- /dev/null +++ b/test/blackbox-tests/test-cases/describe/describe-contexts.t @@ -0,0 +1,19 @@ +Showcase behavior of the `dune describe contexts` subcommand + + $ cat >dune-project < (lang dune 3.14) + > EOF + + $ cat > dune-workspace << EOF + > (lang dune 3.14) + > + > (context default) + > + > (context + > (default + > (name alt))) + > EOF + + $ dune describe contexts + alt + default diff --git a/test/blackbox-tests/test-cases/merlin/alt-context.t b/test/blackbox-tests/test-cases/merlin/alt-context.t new file mode 100644 index 00000000000..4808c567d61 --- /dev/null +++ b/test/blackbox-tests/test-cases/merlin/alt-context.t @@ -0,0 +1,79 @@ +Showcase behavior when passing the `--context` flag to ocaml-merlin + + $ cat >dune-project < (lang dune 3.14) + > EOF + + $ cat > dune-workspace << EOF + > (lang dune 3.14) + > + > (context default) + > + > (context + > (default + > (name alt))) + > EOF + + $ lib1=foo + $ lib2=bar + $ cat >dune < (library + > (name $lib1) + > (modules $lib1) + > (enabled_if (= %{context_name} "default"))) + > (library + > (name $lib2) + > (modules $lib2) + > (enabled_if (= %{context_name} "alt"))) + > EOF + + $ touch $lib1.ml $lib2.ml + + $ dune build + + $ FILE1=$PWD/$lib1.ml + $ FILE2=$PWD/$lib2.ml + +If `generate_merlin_rules` is not used, we can't query anything in alt context +because by default Merlin rules are only created for the default context + + $ printf '(4:File%d:%s)' ${#FILE2} $FILE2 | dune ocaml-merlin | grep -i "$lib2" + ((5:ERROR58:No config found for file bar.ml. Try calling 'dune build'.)) + + $ printf '(4:File%d:%s)' ${#FILE2} $FILE2 | dune ocaml-merlin --context alt | grep -i "$lib2" + ((5:ERROR58:No config found for file bar.ml. Try calling 'dune build'.)) + +Let's use `generate_merlin_rules` to test these commands + + $ cat > dune-workspace << EOF + > (lang dune 3.16) + > + > (context default) + > + > (context + > (default + > (name alt) + > (generate_merlin_rules))) + > EOF + + $ dune build + +Request config for file in alt context without using --context + + $ printf '(4:File%d:%s)' ${#FILE2} $FILE2 | dune ocaml-merlin | grep -i "$lib2" | sed 's/^[^:]*:[^:]*://' + No config found for file bar.ml. Try calling 'dune build'.)) + +Request config for file in alt context using --context + + $ printf '(4:File%d:%s)' ${#FILE2} $FILE2 | dune ocaml-merlin --context alt | dune format-dune-file | grep -i "$lib2" | sed 's/^[^:]*:[^:]*://' + $TESTCASE_ROOT/_build/alt/.bar.objs/byte) + +Request config for default context without using --context + + $ printf '(4:File%d:%s)' ${#FILE1} $FILE1 | dune ocaml-merlin | dune format-dune-file | grep -i "$lib1" | sed 's/^[^:]*:[^:]*://' + $TESTCASE_ROOT/_build/default/.foo.objs/byte) + +Request config for default context using --context + + $ printf '(4:File%d:%s)' ${#FILE1} $FILE1 | dune ocaml-merlin --context alt | dune format-dune-file | grep -i "$lib1" | sed 's/^[^:]*:[^:]*://' + No config found for file foo.ml. Try calling 'dune build'.)) diff --git a/test/blackbox-tests/test-cases/merlin/default-based-context.t/run.t b/test/blackbox-tests/test-cases/merlin/default-based-context.t/run.t index bab44db75f2..c0f5f55d8f1 100644 --- a/test/blackbox-tests/test-cases/merlin/default-based-context.t/run.t +++ b/test/blackbox-tests/test-cases/merlin/default-based-context.t/run.t @@ -105,3 +105,59 @@ If Merlin field is present, this context is chosen -short-paths -keep-locs -g))) + +If `generate_merlin_rules` field is present, rules are generated even if merlin +is disabled in that context + + $ cat >dune-workspace < (lang dune 3.16) + > (context (default)) + > (context + > (default + > (name cross) + > (generate_merlin_rules))) + > EOF + + $ dune build + + $ ls -a _build/cross/.merlin-conf + . + .. + lib-foo + + $ ls -a _build/default/.merlin-conf + . + .. + lib-foo + + $ dune ocaml merlin dump-config "$PWD" + Foo: _build/default/foo + ((STDLIB OPAM_PREFIX) + (EXCLUDE_QUERY_DIR) + (B + $TESTCASE_ROOT/_build/default/.foo.objs/byte) + (S + $TESTCASE_ROOT) + (FLG + (-w + @1..3@5..28@30..39@43@46..47@49..57@61..62-40 + -strict-sequence + -strict-formats + -short-paths + -keep-locs + -g))) + Foo: _build/default/foo.ml + ((STDLIB OPAM_PREFIX) + (EXCLUDE_QUERY_DIR) + (B + $TESTCASE_ROOT/_build/default/.foo.objs/byte) + (S + $TESTCASE_ROOT) + (FLG + (-w + @1..3@5..28@30..39@43@46..47@49..57@61..62-40 + -strict-sequence + -strict-formats + -short-paths + -keep-locs + -g)))