From 718e39f83296139fde79575e65a8d353ef133083 Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Thu, 12 Mar 2026 13:14:42 +0100 Subject: [PATCH 1/3] fix(show): dune show targets now accepts build-only directories Use Load_rules instead of Source_tree to check directory existence. Fixes #13782 Signed-off-by: Ali Caglayan --- bin/describe/aliases_targets.ml | 92 +++++++++++-------- doc/changes/fixed/14664.md | 2 + doc/reference/cli.rst | 4 +- .../test-cases/describe/targets.t/run.t | 10 +- 4 files changed, 66 insertions(+), 42 deletions(-) create mode 100644 doc/changes/fixed/14664.md diff --git a/bin/describe/aliases_targets.ml b/bin/describe/aliases_targets.ml index 976c0f4a77e..eafd9ecea73 100644 --- a/bin/describe/aliases_targets.ml +++ b/bin/describe/aliases_targets.ml @@ -54,26 +54,44 @@ let ls_term (fetch_results : Path.Build.t -> string list Action_builder.t) = Action_builder.of_memo @@ let open Memo.O in - Source_tree.find_dir src_dir + (* First check if it's a directory target *) + Load_rules.load_dir ~dir:(Path.build build_dir) >>= function - | Some _ -> Memo.return () - | None -> - (* The directory didn't exist. We therefore check if it was a - directory target and error for the user accordingly. *) - let+ is_dir_target = - Load_rules.is_under_directory_target (Path.build build_dir) - in - if is_dir_target - then - User_error.raise - [ Pp.textf - "Directory %s is a directory target. This command does not support \ - the inspection of directory targets." - (Path.to_string dir) - ] - else - User_error.raise - [ Pp.textf "Directory %s does not exist." (Path.to_string dir) ] + | Load_rules.Loaded.Build_under_directory_target _ -> + User_error.raise + [ Pp.textf + "Directory %s is a directory target. This command does not support the \ + inspection of directory targets." + (Path.to_string dir) + ] + | External _ | Build _ | Source _ -> + (* Check if directory exists in source tree or is a valid build-only directory *) + Source_tree.find_dir src_dir + >>= (function + | Some _ -> Memo.return () (* Exists in source tree *) + | None -> + (* Not in source tree, check if it's a valid build-only subdirectory *) + (match Path.Build.parent build_dir with + | None -> + (* Build context root always exists *) + Memo.return () + | Some parent_dir -> + Load_rules.load_dir ~dir:(Path.build parent_dir) + >>| (function + | Load_rules.Loaded.Build { allowed_subdirs; _ } -> + let subdir_set = + Dune_engine.Dir_set.descend + allowed_subdirs + (Path.Build.basename build_dir) + in + if Dune_engine.Dir_set.here subdir_set + then () + else + User_error.raise + [ Pp.textf "Directory %s does not exist." (Path.to_string dir) ] + | _ -> + User_error.raise + [ Pp.textf "Directory %s does not exist." (Path.to_string dir) ]))) in let+ targets = fetch_results build_dir in (* If we are printing multiple directories, we print the directory @@ -116,23 +134,25 @@ end module Targets_cmd = struct let fetch_results (dir : Path.Build.t) = let open Action_builder.O in - let+ targets = - let open Memo.O in - Target.all_direct_targets (Some (Path.Build.drop_build_context_exn dir)) - >>| Path.Build.Map.to_list - |> Action_builder.of_memo - in - List.filter_map targets ~f:(fun (path, kind) -> - match Path.Build.equal (Path.Build.parent_exn path) dir with - | false -> None - | true -> - (* directory targets can be distinguied by the trailing path separator - *) - Some - (match kind with - | Target.File -> Path.Build.basename path |> Filename.to_string - | Directory -> - (Path.Build.basename path |> Filename.to_string) ^ Filename.dir_sep)) + let+ load_dir = Action_builder.of_memo (Load_rules.load_dir ~dir:(Path.build dir)) in + match load_dir with + | Load_rules.Loaded.Build { rules_here; _ } -> + let file_targets = + Path.Build.Map.keys rules_here.by_file_targets + |> List.filter_map ~f:(fun path -> + if Path.Build.equal (Path.Build.parent_exn path) dir + then Some (Path.Build.basename path |> Filename.to_string) + else None) + in + let dir_targets = + Path.Build.Map.keys rules_here.by_directory_targets + |> List.filter_map ~f:(fun path -> + if Path.Build.equal (Path.Build.parent_exn path) dir + then Some ((Path.Build.basename path |> Filename.to_string) ^ Filename.dir_sep) + else None) + in + List.sort ~compare:String.compare (file_targets @ dir_targets) + | _ -> [] ;; let term = ls_term fetch_results diff --git a/doc/changes/fixed/14664.md b/doc/changes/fixed/14664.md new file mode 100644 index 00000000000..c4f66283751 --- /dev/null +++ b/doc/changes/fixed/14664.md @@ -0,0 +1,2 @@ +- `dune show targets` now accepts build-only directories such as + `_build/default/.simple.objs`. (#14664, fixes #13782, @Alizter) diff --git a/doc/reference/cli.rst b/doc/reference/cli.rst index 3e9aca7f7f7..e4c2c67503a 100644 --- a/doc/reference/cli.rst +++ b/doc/reference/cli.rst @@ -71,7 +71,9 @@ documentation for each command is available through ``dune COMMAND --help``. .. describe:: dune describe targets - Print targets in a given directory. Works similarly to ls. + Print targets in a given directory. Works similarly to ls. The directory + may be a path in the source tree, or a build-only directory under + ``_build/`` (such as ``_build/default/.lib.objs``). .. describe:: dune describe workspace diff --git a/test/blackbox-tests/test-cases/describe/targets.t/run.t b/test/blackbox-tests/test-cases/describe/targets.t/run.t index e7f701a39fd..225c4435c57 100644 --- a/test/blackbox-tests/test-cases/describe/targets.t/run.t +++ b/test/blackbox-tests/test-cases/describe/targets.t/run.t @@ -70,15 +70,15 @@ We cannot see inside directory targets Error: Directory d is a directory target. This command does not support the inspection of directory targets. -We cannot show targets in build-only directories that don't exist in the source -tree, like .simple.objs: +Build-only directories that don't exist in the source tree, like .simple.objs +(Dune's internal object directory for the `simple` library), can now be +queried: $ dune show targets .simple.objs - Error: Directory .simple.objs does not exist. + cctx.ocaml-index -CR-soon Alizter: This should work $ dune show targets _build/default/.simple.objs - Error: Directory _build/default/.simple.objs does not exist. + cctx.ocaml-index And we error on non-existent directories From 10ffc791e92dbcfe4aafb89751fb22ac8dfac3d6 Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Thu, 12 Mar 2026 13:49:04 +0100 Subject: [PATCH 2/3] refactor(show): add ls_term_gen to support extra arguments Extract the shared term-building logic of `dune show targets` and `dune show aliases` into a generic `ls_term_gen` that accepts an extra argument term. This lets descendant commits expose additional flags without duplicating the path/context handling. Signed-off-by: Ali Caglayan --- bin/describe/aliases_targets.ml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/describe/aliases_targets.ml b/bin/describe/aliases_targets.ml index eafd9ecea73..f22263429fc 100644 --- a/bin/describe/aliases_targets.ml +++ b/bin/describe/aliases_targets.ml @@ -1,13 +1,13 @@ open Import -let ls_term (fetch_results : Path.Build.t -> string list Action_builder.t) = +let ls_term_gen extra_args fetch_results = let+ builder = Common.Builder.term (* CR-someday Alizter: document this option *) and+ paths = Arg.(value & pos_all string [ "." ] & info [] ~docv:"DIR" ~doc:None) and+ context = Common.context_arg ~doc:(Some "The context to look in. Defaults to the default context.") - in + and+ extra = extra_args in let common, config = Common.init builder in let request (_ : Dune_rules.Main.build_system) = let header = List.length paths > 1 in @@ -93,7 +93,7 @@ let ls_term (fetch_results : Path.Build.t -> string list Action_builder.t) = User_error.raise [ Pp.textf "Directory %s does not exist." (Path.to_string dir) ]))) in - let+ targets = fetch_results build_dir in + let+ targets = fetch_results extra build_dir in (* If we are printing multiple directories, we print the directory name as a header. *) (if header then [ Pp.textf "%s:" (Path.to_string dir) ] else []) @@ -110,7 +110,7 @@ let ls_term (fetch_results : Path.Build.t -> string list Action_builder.t) = ;; module Aliases_cmd = struct - let fetch_results (dir : Path.Build.t) = + let fetch_results () (dir : Path.Build.t) = let open Action_builder.O in let+ alias_targets = let+ load_dir = @@ -123,7 +123,7 @@ module Aliases_cmd = struct List.map ~f:Dune_engine.Alias.Name.to_string alias_targets ;; - let term = ls_term fetch_results + let term = ls_term_gen (Term.const ()) fetch_results let command = let doc = "Print aliases in a given directory. Works similarly to ls." in @@ -132,7 +132,7 @@ module Aliases_cmd = struct end module Targets_cmd = struct - let fetch_results (dir : Path.Build.t) = + let fetch_results () (dir : Path.Build.t) = let open Action_builder.O in let+ load_dir = Action_builder.of_memo (Load_rules.load_dir ~dir:(Path.build dir)) in match load_dir with @@ -155,7 +155,7 @@ module Targets_cmd = struct | _ -> [] ;; - let term = ls_term fetch_results + let term = ls_term_gen (Term.const ()) fetch_results let command = let doc = "Print targets in a given directory. Works similarly to ls." in From 7405cd39d3de1d5c3eae56a3c43dd2661803d31c Mon Sep 17 00:00:00 2001 From: Ali Caglayan Date: Thu, 12 Mar 2026 13:41:08 +0100 Subject: [PATCH 3/3] feat(show): include directories with targets in dune show targets Walk the `allowed_subdirs` of the loaded directory and surface those containing build targets in the listing. Add an `-a`/`--all` flag to also include hidden directories (those starting with `.`). Fixes #13783 Signed-off-by: Ali Caglayan --- bin/describe/aliases_targets.ml | 26 ++++++++++++++++--- doc/changes/changed/14665.md | 3 +++ doc/reference/cli.rst | 4 ++- .../test-cases/describe/targets.t/run.t | 25 ++++++++++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 doc/changes/changed/14665.md diff --git a/bin/describe/aliases_targets.ml b/bin/describe/aliases_targets.ml index f22263429fc..71c2db340cc 100644 --- a/bin/describe/aliases_targets.ml +++ b/bin/describe/aliases_targets.ml @@ -132,11 +132,11 @@ module Aliases_cmd = struct end module Targets_cmd = struct - let fetch_results () (dir : Path.Build.t) = + let fetch_results all (dir : Path.Build.t) = let open Action_builder.O in let+ load_dir = Action_builder.of_memo (Load_rules.load_dir ~dir:(Path.build dir)) in match load_dir with - | Load_rules.Loaded.Build { rules_here; _ } -> + | Load_rules.Loaded.Build { rules_here; allowed_subdirs; _ } -> let file_targets = Path.Build.Map.keys rules_here.by_file_targets |> List.filter_map ~f:(fun path -> @@ -151,11 +151,29 @@ module Targets_cmd = struct then Some ((Path.Build.basename path |> Filename.to_string) ^ Filename.dir_sep) else None) in - List.sort ~compare:String.compare (file_targets @ dir_targets) + let subdirs = + match Dune_engine.Dir_set.toplevel_subdirs allowed_subdirs with + | Infinite -> [] + | Finite set -> + Filename.Set.to_list set + |> Filename.L.to_string + |> List.filter ~f:(fun name -> all || String.length name = 0 || name.[0] <> '.') + |> List.map ~f:(fun name -> name ^ Filename.dir_sep) + in + List.sort ~compare:String.compare (file_targets @ dir_targets @ subdirs) | _ -> [] ;; - let term = ls_term_gen (Term.const ()) fetch_results + let extra_args = + Arg.( + value + & flag + & info + [ "a"; "all" ] + ~doc:(Some "Show hidden directories (those starting with '.').")) + ;; + + let term = ls_term_gen extra_args fetch_results let command = let doc = "Print targets in a given directory. Works similarly to ls." in diff --git a/doc/changes/changed/14665.md b/doc/changes/changed/14665.md new file mode 100644 index 00000000000..52ba9cb042e --- /dev/null +++ b/doc/changes/changed/14665.md @@ -0,0 +1,3 @@ +- `dune show targets` now includes subdirectories that contain build targets, + and accepts an `-a`/`--all` flag to also include hidden directories (those + starting with `.`). (#14665, fixes #13783, @Alizter) diff --git a/doc/reference/cli.rst b/doc/reference/cli.rst index e4c2c67503a..e3fd1942059 100644 --- a/doc/reference/cli.rst +++ b/doc/reference/cli.rst @@ -73,7 +73,9 @@ documentation for each command is available through ``dune COMMAND --help``. Print targets in a given directory. Works similarly to ls. The directory may be a path in the source tree, or a build-only directory under - ``_build/`` (such as ``_build/default/.lib.objs``). + ``_build/`` (such as ``_build/default/.lib.objs``). Subdirectories + containing build targets are listed alongside the targets themselves; + pass ``-a``/``--all`` to also include hidden directories. .. describe:: dune describe workspace diff --git a/test/blackbox-tests/test-cases/describe/targets.t/run.t b/test/blackbox-tests/test-cases/describe/targets.t/run.t index 225c4435c57..0a86df0d807 100644 --- a/test/blackbox-tests/test-cases/describe/targets.t/run.t +++ b/test/blackbox-tests/test-cases/describe/targets.t/run.t @@ -8,7 +8,10 @@ With no directory provided to the command, it should default to the current working directory. $ dune show targets + _doc/ + _doc_new/ a.ml + b/ d/ dune dune-project @@ -23,7 +26,10 @@ used, and only the targets available in that directory will be displayed. $ dune show targets . b/ .: + _doc/ + _doc_new/ a.ml + b/ d/ dune dune-project @@ -45,7 +51,10 @@ used, and only the targets available in that directory will be displayed. The command also works with files in the _build directory. $ dune show targets _build/default/ + _doc/ + _doc_new/ a.ml + b/ d/ dune dune-project @@ -75,10 +84,26 @@ Build-only directories that don't exist in the source tree, like .simple.objs queried: $ dune show targets .simple.objs + byte/ cctx.ocaml-index + jsoo/ + native/ $ dune show targets _build/default/.simple.objs + byte/ cctx.ocaml-index + jsoo/ + native/ + +With --all, hidden directories are also shown: + + $ dune show targets --all .simple.objs + .bin/ + .utop/ + byte/ + cctx.ocaml-index + jsoo/ + native/ And we error on non-existent directories