Skip to content

Commit 99ffae6

Browse files
authored
Merge pull request ocaml#11905 from gridbugs/dune-exec-which
Looking up paths to executables with `dune describe location`
2 parents 7a29368 + 7cd0554 commit 99ffae6

File tree

7 files changed

+168
-26
lines changed

7 files changed

+168
-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 sctx ~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: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,25 @@ 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 sctx =
119+
let context = Dune_rules.Super_context.context sctx in
120+
Path.Build.relative (Context.build_dir context) (Common.prefix_target common "")
121+
;;
122+
123+
let get_path common sctx ~prog =
119124
let open Memo.O in
125+
let dir = dir_of_context common sctx in
120126
match Filename.analyze_program_name prog with
121127
| In_path ->
122128
Super_context.resolve_program_memo sctx ~dir ~loc:None prog
123129
>>= (function
124130
| Error (_ : Action.Prog.Not_found.t) -> not_found_with_suggestions ~dir ~prog
125-
| Ok p -> build_prog ~no_rebuild ~prog p)
131+
| Ok p -> Memo.return p)
126132
| Relative_to_current_dir ->
127133
let path = Path.relative_to_source_in_build_or_external ~dir prog in
128134
Build_system.file_exists path
129135
>>= (function
130-
| true -> build_prog ~no_rebuild ~prog path
136+
| true -> Memo.return path
131137
| false -> not_found_with_suggestions ~dir ~prog)
132138
| Absolute ->
133139
(match
@@ -144,19 +150,24 @@ let get_path_and_build_if_necessary sctx ~no_rebuild ~dir ~prog =
144150
| None -> not_found_with_suggestions ~dir ~prog)
145151
;;
146152

147-
let step ~setup ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
153+
let get_path_and_build_if_necessary common sctx ~no_rebuild ~prog =
148154
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
155+
let* path = get_path common sctx ~prog in
156+
match Filename.analyze_program_name prog with
157+
| In_path | Relative_to_current_dir -> build_prog ~no_rebuild ~prog path
158+
| Absolute -> Memo.return path
159+
;;
160+
161+
let step ~prog ~args ~common ~no_rebuild ~context ~on_exit () =
162+
let open Memo.O in
163+
let* sctx = Super_context.find_exn context in
152164
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
165+
let* prog = Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
166+
get_path_and_build_if_necessary common sctx ~no_rebuild ~prog
167+
and* args =
168+
Memo.parallel_map args ~f:(Cmd_arg.expand ~root:(Common.root common) ~sctx)
169+
in
170+
let* env = Super_context.context_env sctx in
160171
Memo.of_non_reproducible_fiber
161172
@@ Dune_engine.Process.run_inherit_std_in_out
162173
~dir:(Path.of_string Fpath.initial_cwd)
@@ -252,12 +263,11 @@ let exec_building_directly ~common ~config ~context ~prog ~args ~no_rebuild =
252263
Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config
253264
@@ fun () ->
254265
let open Fiber.O in
255-
let* setup = Import.Main.setup () in
256266
let on_exit = Console.printf "Program exited with code [%d]" in
257267
Scheduler.Run.poll
258268
@@
259269
let* () = Fiber.return @@ Scheduler.maybe_clear_screen ~details_hum:[] config in
260-
build @@ step ~setup ~prog ~args ~common ~no_rebuild ~context ~on_exit
270+
build @@ step ~prog ~args ~common ~no_rebuild ~context ~on_exit
261271
| No ->
262272
Scheduler.go_with_rpc_server ~common ~config
263273
@@ fun () ->
@@ -266,16 +276,13 @@ let exec_building_directly ~common ~config ~context ~prog ~args ~no_rebuild =
266276
build_exn (fun () ->
267277
let open Memo.O in
268278
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
279+
let* env = Super_context.context_env sctx
280+
and* prog =
281+
let* prog = Cmd_arg.expand ~root:(Common.root common) ~sctx prog in
282+
get_path_and_build_if_necessary common sctx ~no_rebuild ~prog >>| Path.to_string
283+
and* args =
284+
Memo.parallel_map ~f:(Cmd_arg.expand ~root:(Common.root common) ~sctx) args
285+
in
279286
restore_cwd_and_execve (Common.root common) prog args env)
280287
;;
281288

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 -> Super_context.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)