Skip to content

Commit a574375

Browse files
committed
Looking up paths to executables with dune exec
The "dune exec" command has three different ways of resolving the names of executables to paths to executables: - public executables defined in the current project - executables in the "bin" directories of dependencies - executables in directories listed in $PATH This can lead to unexpected shadowing, especially in the case of executables from dependecies, as users may not be aware that one of the packages in their project's dependency cone defines an executable with the same name of an executable that's also insalled system-wide. Short of fixing the problem for now, this change introduces a tool for helping investigate specifically which executable will be run by "dune exec". This adds a flag "--which" to "dune exec" which causes the path to the executable to be printed, rather than running the executable. Signed-off-by: Stephen Sherratt <stephen@sherra.tt>
1 parent 88ec2d4 commit a574375

2 files changed

Lines changed: 130 additions & 39 deletions

File tree

bin/exec.ml

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,15 @@ let build_prog ~no_rebuild ~prog p =
111111
p
112112
;;
113113

114-
let get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog =
114+
let sctx_of_context context =
115115
let open Memo.O in
116+
let* setup = Import.Main.setup () |> Memo.of_reproducible_fiber in
117+
setup >>| Import.Main.find_scontext_exn ~name:context
118+
;;
119+
120+
let get_path_and_build_if_necessary context ~no_rebuild ~dir ~prog =
121+
let open Memo.O in
122+
let* sctx = sctx_of_context context in
116123
match Filename.analyze_program_name prog with
117124
| In_path ->
118125
Super_context.resolve_program_memo sctx ~dir ~loc:None prog
@@ -140,18 +147,28 @@ let get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog =
140147
| None -> not_found ~dir ~prog)
141148
;;
142149

143-
let step ~setup ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
150+
let dir_of_context common context =
151+
let open Memo.O in
152+
let+ sctx = sctx_of_context context in
153+
let context = Dune_rules.Super_context.context sctx in
154+
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
155+
;;
156+
157+
let expand common context prog =
158+
let open Memo.O in
159+
let* sctx = sctx_of_context context in
160+
Cmd_arg.expand ~root:(Common.root common) ~sctx prog
161+
;;
162+
163+
let step ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
144164
let open Memo.O in
145-
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
165+
let* sctx = sctx_of_context context in
146166
let* env = Super_context.context_env sctx in
147-
let expand = Cmd_arg.expand ~root:(Common.root common) ~sctx in
167+
let expand = expand common context in
148168
let* path =
149-
let dir =
150-
let context = Dune_rules.Super_context.context sctx in
151-
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
152-
in
169+
let* dir = dir_of_context common context in
153170
let* prog = expand prog in
154-
get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog
171+
get_path_and_build_if_necessary context ~no_rebuild ~dir ~prog
155172
and* args = Memo.parallel_map args ~f:expand in
156173
Memo.of_non_reproducible_fiber
157174
@@ Dune_engine.Process.run_inherit_std_in_out
@@ -170,43 +187,55 @@ let term : unit Term.t =
170187
and+ prog = Arg.(required & pos 0 (some Cmd_arg.conv) None (Arg.info [] ~docv:"PROG"))
171188
and+ no_rebuild =
172189
Arg.(value & flag & info [ "no-build" ] ~doc:"don't rebuild target before executing")
190+
and+ which = Arg.(value & flag & info [ "which" ] ~doc:"todo")
173191
and+ args = Arg.(value & pos_right 0 Cmd_arg.conv [] (Arg.info [] ~docv:"ARGS")) in
174192
(* TODO we should make sure to finalize the current backend before exiting dune.
175193
For watch mode, we should finalize the backend and then restart it in between
176194
runs. *)
177195
let common, config = Common.init builder in
178-
match Common.watch common with
179-
| Yes Passive ->
180-
User_error.raise [ Pp.textf "passive watch mode is unsupported by exec" ]
181-
| Yes Eager ->
182-
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config
183-
@@ fun () ->
184-
let open Fiber.O in
185-
let* setup = Import.Main.setup () in
186-
let on_exit = Console.printf "Program exited with code [%d]" in
187-
Scheduler.Run.poll
188-
@@
189-
let* () = Fiber.return @@ Scheduler.maybe_clear_screen ~details_hum:[] config in
190-
build @@ step ~setup ~prog ~args ~common ~no_rebuild ~context ~on_exit
191-
| No ->
196+
if which
197+
then
192198
Scheduler.go_with_rpc_server ~common ~config
193199
@@ fun () ->
194-
let open Fiber.O in
195-
let* setup = Import.Main.setup () in
196-
build_exn (fun () ->
197-
let open Memo.O in
198-
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
199-
let* env = Super_context.context_env sctx in
200-
let expand = Cmd_arg.expand ~root:(Common.root common) ~sctx in
201-
let* prog =
202-
let dir =
203-
let context = Dune_rules.Super_context.context sctx in
204-
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
205-
in
206-
let* prog = expand prog in
207-
get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog >>| Path.to_string
208-
and* args = Memo.parallel_map ~f:expand args in
209-
restore_cwd_and_execve (Common.root common) prog args env)
200+
build_exn
201+
@@ fun () ->
202+
let open Memo.O in
203+
let+ path =
204+
let* dir = dir_of_context common context
205+
and* prog = expand common context prog in
206+
get_path_and_build_if_necessary context ~no_rebuild ~dir ~prog >>| Path.to_string
207+
in
208+
Dune_console.printf "%s" path
209+
else (
210+
match Common.watch common with
211+
| Yes Passive ->
212+
User_error.raise [ Pp.textf "passive watch mode is unsupported by exec" ]
213+
| Yes Eager ->
214+
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config
215+
@@ fun () ->
216+
let open Fiber.O in
217+
let on_exit = Console.printf "Program exited with code [%d]" in
218+
Scheduler.Run.poll
219+
@@
220+
let* () = Fiber.return @@ Scheduler.maybe_clear_screen ~details_hum:[] config in
221+
build @@ step ~prog ~args ~common ~no_rebuild ~context ~on_exit
222+
| No ->
223+
Scheduler.go_with_rpc_server ~common ~config
224+
@@ fun () ->
225+
let open Fiber.O in
226+
let* setup = Import.Main.setup () in
227+
build_exn (fun () ->
228+
let open Memo.O in
229+
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
230+
let* env = Super_context.context_env sctx in
231+
let expand = expand common context in
232+
let* prog =
233+
let* dir = dir_of_context common context
234+
and* prog = expand prog in
235+
get_path_and_build_if_necessary context ~no_rebuild ~dir ~prog
236+
>>| Path.to_string
237+
and* args = Memo.parallel_map ~f:expand args in
238+
restore_cwd_and_execve (Common.root common) prog args env))
210239
;;
211240

212241
let command = Cmd.v info term
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
Exercise the various ways of resolving executable names with `dune exec`.
2+
3+
$ cat > dune-project << EOF
4+
> (lang dune 3.20)
5+
>
6+
> (package
7+
> (name foo))
8+
> EOF
9+
10+
$ cat > dune << EOF
11+
> (executable
12+
> (public_name foo))
13+
> EOF
14+
15+
$ cat > foo.ml << EOF
16+
> let () = print_endline "hello foo"
17+
> EOF
18+
19+
An executable that would be installed by the current package:
20+
$ dune exec --which foo
21+
_build/install/default/bin/foo
22+
23+
An executable from the current project:
24+
$ dune exec --which ./foo.exe
25+
_build/default/foo.exe
26+
27+
Test that executables from dependencies are located correctly:
28+
$ mkdir dune.lock
29+
$ cat > dune.lock/lock.dune << EOF
30+
> (lang package 0.1)
31+
> EOF
32+
$ cat > dune.lock/bar.pkg << EOF
33+
> (version 0.1)
34+
> (install
35+
> (progn
36+
> (run sh -c "echo '#!/bin/sh\necho hello bar' > %{bin}/bar")
37+
> (run chmod a+x %{bin}/bar)))
38+
> EOF
39+
40+
$ cat > dune-project << EOF
41+
> (lang dune 3.20)
42+
>
43+
> (package
44+
> (name foo)
45+
> (depends bar))
46+
> EOF
47+
48+
$ dune exec --which bar
49+
_build/_private/default/.pkg/bar/target/bin/bar
50+
51+
Test that executables from PATH are located correctly:
52+
$ mkdir bin
53+
$ cat > bin/baz << EOF
54+
> #!/bin/sh
55+
> echo hello baz
56+
> EOF
57+
58+
$ chmod a+x bin/baz
59+
$ export PATH=$PWD/bin:$PATH
60+
61+
$ dune exec --which baz
62+
$TESTCASE_ROOT/bin/baz

0 commit comments

Comments
 (0)