Skip to content

Commit c55634c

Browse files
committed
Looking up paths to executables with dune describe location
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 command "dune describe location" which prints the path to the executable. Signed-off-by: Stephen Sherratt <[email protected]>
1 parent 7a29368 commit c55634c

File tree

7 files changed

+172
-26
lines changed

7 files changed

+172
-26
lines changed

bin/describe/describe.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let subcommands =
2121
; Describe_pkg.command
2222
; Describe_contexts.command
2323
; Describe_depexts.command
24+
; Describe_location.command
2425
]
2526
;;
2627

bin/describe/describe_location.ml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
open! Import
2+
3+
let doc =
4+
"Print the path to the executable using the same resolution logic as [dune exec]."
5+
;;
6+
7+
let man =
8+
[ `S "DESCRIPTION"
9+
; `P
10+
{|$(b,dune describe location NAME) prints the path to the executable NAME using the same logic as:
11+
|}
12+
; `Pre "$ dune exec NAME"
13+
; `P
14+
"Dune will first try to resolve the executable within the public executables in \
15+
the current project, then inside the \"bin\" directory of each package among the \
16+
project's dependencies (when using dune package management), and finally within \
17+
the directories listed in the $PATH environment variable."
18+
]
19+
;;
20+
21+
let info = Cmd.info "location" ~doc ~man
22+
23+
let term : unit Term.t =
24+
let+ builder = Common.Builder.term
25+
and+ context = Common.context_arg ~doc:{|Run the command in this build context.|}
26+
and+ prog =
27+
Arg.(required & pos 0 (some Exec.Cmd_arg.conv) None (Arg.info [] ~docv:"PROG"))
28+
in
29+
let common, config = Common.init builder in
30+
Scheduler.go_with_rpc_server ~common ~config
31+
@@ fun () ->
32+
let open Fiber.O in
33+
let* setup = Import.Main.setup () in
34+
build_exn
35+
@@ fun () ->
36+
let open Memo.O in
37+
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
38+
let* prog = Exec.Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
39+
let+ path = Exec.get_path common context ~prog >>| Path.to_string in
40+
Dune_console.printf "%s" path
41+
;;
42+
43+
let command = Cmd.v info term

bin/describe/describe_location.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
open! Import
2+
3+
val command : unit Cmd.t

bin/exec.ml

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,28 @@ let build_prog ~no_rebuild ~prog p =
115115
p
116116
;;
117117

118-
let get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog =
118+
let dir_of_context common context =
119119
let open Memo.O in
120+
let+ sctx = Super_context.find_exn context in
121+
let context = Dune_rules.Super_context.context sctx in
122+
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
123+
;;
124+
125+
let get_path common context ~prog =
126+
let open Memo.O in
127+
let* sctx = Super_context.find_exn context
128+
and* dir = dir_of_context common context in
120129
match Filename.analyze_program_name prog with
121130
| In_path ->
122131
Super_context.resolve_program_memo sctx ~dir ~loc:None prog
123132
>>= (function
124133
| Error (_ : Action.Prog.Not_found.t) -> not_found_with_suggestions ~dir ~prog
125-
| Ok p -> build_prog ~no_rebuild ~prog p)
134+
| Ok p -> Memo.return p)
126135
| Relative_to_current_dir ->
127136
let path = Path.relative_to_source_in_build_or_external ~dir prog in
128137
Build_system.file_exists path
129138
>>= (function
130-
| true -> build_prog ~no_rebuild ~prog path
139+
| true -> Memo.return path
131140
| false -> not_found_with_suggestions ~dir ~prog)
132141
| Absolute ->
133142
(match
@@ -144,19 +153,24 @@ let get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog =
144153
| None -> not_found_with_suggestions ~dir ~prog)
145154
;;
146155

147-
let step ~setup ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
156+
let get_path_and_build_if_necessary common context ~no_rebuild ~prog =
148157
let open Memo.O in
149-
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
150-
let* env = Super_context.context_env sctx in
151-
let expand = Cmd_arg.expand ~root:(Common.root common) ~sctx in
158+
let* path = get_path common context ~prog in
159+
match Filename.analyze_program_name prog with
160+
| In_path | Relative_to_current_dir -> build_prog ~no_rebuild ~prog path
161+
| Absolute -> Memo.return path
162+
;;
163+
164+
let step ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
165+
let open Memo.O in
166+
let* sctx = Super_context.find_exn context in
152167
let* path =
153-
let dir =
154-
let context = Dune_rules.Super_context.context sctx in
155-
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
156-
in
157-
let* prog = expand prog in
158-
get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog
159-
and* args = Memo.parallel_map args ~f:expand in
168+
let* prog = Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
169+
get_path_and_build_if_necessary common context ~no_rebuild ~prog
170+
and* args =
171+
Memo.parallel_map args ~f:(Cmd_arg.expand ~root:(Common.root common) ~sctx)
172+
in
173+
let* env = Super_context.context_env sctx in
160174
Memo.of_non_reproducible_fiber
161175
@@ Dune_engine.Process.run_inherit_std_in_out
162176
~dir:(Path.of_string Fpath.initial_cwd)
@@ -252,12 +266,11 @@ let exec_building_directly ~common ~config ~context ~prog ~args ~no_rebuild =
252266
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config
253267
@@ fun () ->
254268
let open Fiber.O in
255-
let* setup = Import.Main.setup () in
256269
let on_exit = Console.printf "Program exited with code [%d]" in
257270
Scheduler.Run.poll
258271
@@
259272
let* () = Fiber.return @@ Scheduler.maybe_clear_screen ~details_hum:[] config in
260-
build @@ step ~setup ~prog ~args ~common ~no_rebuild ~context ~on_exit
273+
build @@ step ~prog ~args ~common ~no_rebuild ~context ~on_exit
261274
| No ->
262275
Scheduler.go_with_rpc_server ~common ~config
263276
@@ fun () ->
@@ -266,16 +279,14 @@ let exec_building_directly ~common ~config ~context ~prog ~args ~no_rebuild =
266279
build_exn (fun () ->
267280
let open Memo.O in
268281
let* sctx = setup >>| Import.Main.find_scontext_exn ~name:context in
269-
let* env = Super_context.context_env sctx in
270-
let expand = Cmd_arg.expand ~root:(Common.root common) ~sctx in
271-
let* prog =
272-
let dir =
273-
let context = Dune_rules.Super_context.context sctx in
274-
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
275-
in
276-
let* prog = expand prog in
277-
get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog >>| Path.to_string
278-
and* args = Memo.parallel_map ~f:expand args in
282+
let* env = Super_context.context_env sctx
283+
and* prog =
284+
let* prog = Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
285+
get_path_and_build_if_necessary common context ~no_rebuild ~prog
286+
>>| Path.to_string
287+
and* args =
288+
Memo.parallel_map ~f:(Cmd_arg.expand ~root:(Common.root common) ~sctx) args
289+
in
279290
restore_cwd_and_execve (Common.root common) prog args env)
280291
;;
281292

bin/exec.mli

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
11
open Import
22

3+
module Cmd_arg : sig
4+
type t
5+
6+
val conv : t Arg.conv
7+
val expand : t -> root:Workspace_root.t -> sctx:Super_context.t -> string Memo.t
8+
end
9+
10+
(** Returns the path to the executable [prog] as it will be resolved by dune:
11+
- if [prog] is the name of an executable defined by the project then the path
12+
to that executable will be returned, and evaluating the returned memo will
13+
build the executable if necessary.
14+
- otherwise if [prog] is the name of an executable in the "bin" directory of
15+
a package in this project's dependency cone then the path to that executable
16+
file will be returned. Note that for this reason all dependencies of the
17+
project will be built when the returned memo is evaluated (unless the first
18+
case is hit).
19+
- otherwise if [prog] is the name of an executable in one of the directories
20+
listed in the PATH environment variable, the path to that executable will be
21+
returned. *)
22+
val get_path : Common.t -> Context_name.t -> prog:string -> Path.t Memo.t
23+
324
val command : unit Cmd.t

doc/changes/11905.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Add `dune describe location` for printing the path to the executable that
2+
would be run (#11905, @gridbugs)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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 describe location foo
21+
_build/install/default/bin/foo
22+
23+
An executable from the current project:
24+
$ dune describe location ./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+
> (write-file %{bin}/bar "#!/bin/sh\necho hello 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 describe location 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 describe location baz
62+
$TESTCASE_ROOT/bin/baz
63+
64+
$ dune exec echo '%{bin:foo}'
65+
_build/install/default/bin/foo

0 commit comments

Comments
 (0)