diff --git a/bin/tools/ocamllsp.ml b/bin/tools/ocamllsp.ml index 41634df7ee6..535f58c2664 100644 --- a/bin/tools/ocamllsp.ml +++ b/bin/tools/ocamllsp.ml @@ -13,7 +13,8 @@ let run_ocamllsp common ~args = ~object_: (User_message.command (String.concat ~sep:" " (ocamllsp_exe_name :: args)))); Console.finish (); - restore_cwd_and_execve common exe_path_string (exe_path_string :: args) Env.initial + let env = Shell_env.add_path Env.initial in + restore_cwd_and_execve common exe_path_string (exe_path_string :: args) env ;; let build_ocamllsp common = diff --git a/bin/tools/shell_env.ml b/bin/tools/shell_env.ml new file mode 100644 index 00000000000..44e4127873d --- /dev/null +++ b/bin/tools/shell_env.ml @@ -0,0 +1,41 @@ +open! Import +module Pkg_dev_tool = Dune_rules.Pkg_dev_tool + +let all_tools = List.map ~f:Pkg_dev_tool.exe_name [ Ocamlformat; Ocamllsp ] +let bin_path () = Path.build (Pkg_dev_tool.bin_path ()) + +let shell_script = {|#!/bin/sh +dune tools exec $(basename $0) -- "$@" +|} + +let setup path = + if not (Path.exists path) then Path.mkdir_p path; + List.iter all_tools ~f:(fun tool -> + let tool_path = Path.relative path tool in + if not (Path.exists tool_path) then Io.write_file ~perm:0o777 tool_path shell_script) +;; + +let add_path env = + let dir = bin_path () in + setup dir; + Env_path.cons env ~dir +;; + +let term = + let+ builder = Common.Builder.term in + let common, config = Common.init builder in + Scheduler.go ~common ~config (fun () -> + let env = add_path Env.initial in + Format.printf + "%s=%s@." + Env_path.var + (Option.value ~default:"" (Env.get env Env_path.var)); + Fiber.return ()) +;; + +let info = + let doc = "Configure shell environment" in + Cmd.info "env" ~doc +;; + +let command = Cmd.v info term diff --git a/bin/tools/shell_env.mli b/bin/tools/shell_env.mli new file mode 100644 index 00000000000..92e1a2dae4e --- /dev/null +++ b/bin/tools/shell_env.mli @@ -0,0 +1,4 @@ +open! Import + +val add_path : Env.t -> Env.t +val command : unit Cmd.t diff --git a/bin/tools/tools.ml b/bin/tools/tools.ml index bd078dd0086..b2bdfcbd283 100644 --- a/bin/tools/tools.ml +++ b/bin/tools/tools.ml @@ -14,4 +14,4 @@ end let doc = "Command group for wrapped tools." let info = Cmd.info ~doc "tools" -let group = Cmd.group info [ Exec.group; Which.group ] +let group = Cmd.group info [ Exec.group; Which.group; Shell_env.command ] diff --git a/otherlibs/stdune/src/bin.ml b/otherlibs/stdune/src/bin.ml index f44078ba527..d424a16c1ee 100644 --- a/otherlibs/stdune/src/bin.ml +++ b/otherlibs/stdune/src/bin.ml @@ -12,9 +12,11 @@ let encode_strings paths = String.concat ~sep:(String.make 1 path_sep) paths let cons_path ?(path_sep = path_sep) p ~_PATH = let p = Path.to_absolute_filename p in + let p_sep = p ^ String.make 1 path_sep in match _PATH with | None -> p - | Some s -> Printf.sprintf "%s%c%s" p path_sep s + | Some s when String.is_prefix s ~prefix:p_sep -> s + | Some s -> p_sep ^ s ;; let exe = if Sys.win32 then ".exe" else "" diff --git a/src/dune_rules/pkg_dev_tool.ml b/src/dune_rules/pkg_dev_tool.ml index 26b1ae8b9a3..20726e9eb88 100644 --- a/src/dune_rules/pkg_dev_tool.ml +++ b/src/dune_rules/pkg_dev_tool.ml @@ -28,3 +28,5 @@ let exe_path t = (package_install_path t) ("target" :: exe_path_components_within_package t) ;; + +let bin_path () = Path.Build.relative (Lazy.force install_path_base) "bin" diff --git a/src/dune_rules/pkg_dev_tool.mli b/src/dune_rules/pkg_dev_tool.mli index fc48474b233..8de4a8bf41e 100644 --- a/src/dune_rules/pkg_dev_tool.mli +++ b/src/dune_rules/pkg_dev_tool.mli @@ -14,3 +14,6 @@ val package_install_path : t -> Path.Build.t (** The path to the executable for running the given dev tool *) val exe_path : t -> Path.Build.t + +(** The path to the shell executables for running a dev tool with dune *) +val bin_path : unit -> Path.Build.t diff --git a/test/blackbox-tests/test-cases/pkg/ocamllsp/dev-tool-ocamllsp-env.t b/test/blackbox-tests/test-cases/pkg/ocamllsp/dev-tool-ocamllsp-env.t new file mode 100644 index 00000000000..4f159492531 --- /dev/null +++ b/test/blackbox-tests/test-cases/pkg/ocamllsp/dev-tool-ocamllsp-env.t @@ -0,0 +1,59 @@ +Check that `dune tools exec ocamllsp` can call the dune tools version of ocamlformat: + + $ . ../helpers.sh + $ . ./helpers.sh + + $ mkrepo + $ mkpkg ocaml 5.2.0 + $ mkpkg ocaml-lsp-server < install: [ + > [ "sh" "-c" "echo '#!/bin/sh' > %{bin}%/ocamllsp" ] + > [ "sh" "-c" "echo 'echo hello from fake ocamllsp' >> %{bin}%/ocamllsp" ] + > [ "sh" "-c" "echo ocamlformat >> %{bin}%/ocamllsp" ] + > [ "sh" "-c" "chmod a+x %{bin}%/ocamllsp" ] + > ] + > EOF + $ mkpkg ocamlformat < install: [ + > [ "sh" "-c" "echo '#!/bin/sh' > %{bin}%/ocamlformat" ] + > [ "sh" "-c" "echo 'echo hello from fake ocamlformat' >> %{bin}%/ocamlformat" ] + > [ "sh" "-c" "chmod a+x %{bin}%/ocamlformat" ] + > ] + > EOF + + $ setup_ocamllsp_workspace + + $ cat > dune-project < (lang dune 3.16) + > EOF + + $ make_lockdir + $ cat > dune.lock/ocaml.pkg < (version 5.2.0) + > EOF + + $ dune tools exec ocamllsp + Solution for dev-tools.locks/ocaml-lsp-server: + - ocaml.5.2.0 + - ocaml-lsp-server.0.0.1 + Running 'ocamllsp' + hello from fake ocamllsp + Solution for dev-tools.locks/ocamlformat: + - ocamlformat.0.0.1 + Running 'ocamlformat' + hello from fake ocamlformat + +Users can also configure their PATH variable environment: + + $ eval $(dune tools env) + $ echo $PATH | sed 's/:.*//' + $TESTCASE_ROOT/_build/_private/default/.dev-tool/bin + $ which ocamllsp + $TESTCASE_ROOT/_build/_private/default/.dev-tool/bin/ocamllsp + $ which ocamlformat + $TESTCASE_ROOT/_build/_private/default/.dev-tool/bin/ocamlformat + $ ocamllsp + Running 'ocamllsp' + hello from fake ocamllsp + Running 'ocamlformat' + hello from fake ocamlformat diff --git a/test/blackbox-tests/test-cases/pkg/ocamllsp/helpers.sh b/test/blackbox-tests/test-cases/pkg/ocamllsp/helpers.sh index 41f8da91a85..31bd1597f5b 100644 --- a/test/blackbox-tests/test-cases/pkg/ocamllsp/helpers.sh +++ b/test/blackbox-tests/test-cases/pkg/ocamllsp/helpers.sh @@ -6,8 +6,11 @@ setup_ocamllsp_workspace() { (lock_dir (path "dev-tools.locks/ocaml-lsp-server") (repositories mock)) - (lock_dir - (repositories mock)) +(lock_dir + (path "dev-tools.locks/ocamlformat") + (repositories mock)) +(lock_dir + (repositories mock)) (repository (name mock) (url "file://$(pwd)/mock-opam-repository"))