diff --git a/bin/describe/aliases_targets.ml b/bin/describe/aliases_targets.ml index 976c0f4a77e..3bf075c5469 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 @@ -49,33 +49,47 @@ let ls_term (fetch_results : Path.Build.t -> string list Action_builder.t) = (Path.to_string_maybe_quoted dir) ] in - (* Check if the directory exists. *) + (* Check if the directory exists (in source tree, as a directory target, + or as a build-only directory). *) let* () = Action_builder.of_memo @@ let open Memo.O in - Source_tree.find_dir src_dir + 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 _ -> + (* Directory target - allow it through for inspection *) + Memo.return () + | 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 + 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 []) @@ -92,7 +106,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 = @@ -105,7 +119,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 @@ -114,28 +128,52 @@ 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+ 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 + let* targets = + Action_builder.of_memo (Build_system.targets_of ~dir:(Path.build dir)) + in + let+ subdirs = + Action_builder.of_memo + (let open Memo.O in + Load_rules.load_dir ~dir:(Path.build dir) + >>| function + | Load_rules.Loaded.Build { allowed_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.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 target_names = + match Dune_targets.validate targets with + | Valid validated -> + Dune_targets.Validated.fold + validated + ~init:[] + ~file:(fun p acc -> (Path.Build.basename p |> Filename.to_string) :: acc) + ~dir:(fun p acc -> + ((Path.Build.basename p |> Filename.to_string) ^ Filename.dir_sep) :: acc) + | No_targets -> [] + | Inconsistent_parent_dir | File_and_directory_target_with_the_same_name _ -> [] + in + List.sort ~compare:String.compare (target_names @ subdirs) + ;; + + let extra_args = + Arg.( + value + & flag + & info + [ "a"; "all" ] + ~doc:(Some "Show hidden directories (those starting with '.').")) ;; - let term = ls_term fetch_results + 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/changes/changed/14666.md b/doc/changes/changed/14666.md new file mode 100644 index 00000000000..24ffd0142fe --- /dev/null +++ b/doc/changes/changed/14666.md @@ -0,0 +1,2 @@ +- `dune show targets` now looks inside directory targets, so users can inspect + the files produced under a directory target. (#14666, fixes #13780, @Alizter) 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..b298a58edd8 100644 --- a/doc/reference/cli.rst +++ b/doc/reference/cli.rst @@ -71,7 +71,12 @@ 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, a build-only directory under + ``_build/`` (such as ``_build/default/.lib.objs``), or a directory + target (including subdirectories of one). 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/src/dune_engine/build_system.ml b/src/dune_engine/build_system.ml index 3edbadd92fa..50b35f07abe 100644 --- a/src/dune_engine/build_system.ml +++ b/src/dune_engine/build_system.ml @@ -1062,6 +1062,57 @@ let files_of ~dir = Filename_set.create ~dir filenames ;; +let targets_of ~dir = + Load_rules.load_dir ~dir + >>= function + | Source _ | External _ -> + (* Source/external dirs have no build targets *) + Memo.return Targets.empty + | Build { rules_here; _ } -> + let files = + Path.Build.Map.keys rules_here.by_file_targets |> Path.Build.Set.of_list + in + let dirs = + Path.Build.Map.keys rules_here.by_directory_targets |> Path.Build.Set.of_list + in + Memo.return (Targets.create ~files ~dirs) + | Build_under_directory_target { directory_target_ancestor } -> + let+ produced = build_dir (Path.build directory_target_ancestor) in + let build_dir_path = Path.as_in_build_dir_exn dir in + (* Navigate to the requested directory within the produced targets *) + let rec find_dir_contents (contents : _ Targets.Produced.dir_contents) path = + match path with + | [] -> Some contents + | component :: rest -> + (match Filename.Map.find contents.subdirs component with + | Some sub -> find_dir_contents sub rest + | None -> None) + in + let path_within = + match + Path.Local.descendant + (Path.Build.local build_dir_path) + ~of_:(Path.Build.local produced.root) + with + | Some p -> Path.Local.explode p + | None -> [] + in + (match find_dir_contents produced.contents path_within with + | Some contents -> + let files = + Filename.Map.keys contents.files + |> List.map ~f:(Path.Build.relative_fname build_dir_path) + |> Path.Build.Set.of_list + in + let dirs = + Filename.Map.keys contents.subdirs + |> List.map ~f:(Path.Build.relative_fname build_dir_path) + |> Path.Build.Set.of_list + in + Targets.create ~files ~dirs + | None -> Targets.empty) +;; + let caused_by_cancellation (exn : Exn_with_backtrace.t) = match exn.exn with | Scheduler.Run.Build_cancelled -> true diff --git a/src/dune_engine/build_system.mli b/src/dune_engine/build_system.mli index aadf2ee8437..d120b1c16f8 100644 --- a/src/dune_engine/build_system.mli +++ b/src/dune_engine/build_system.mli @@ -35,6 +35,10 @@ val eval_pred : File_selector.t -> Filename_set.t Memo.t (** Same as [eval_pred] with [Predicate.true_] as predicate. *) val files_of : dir:Path.t -> Filename_set.t Memo.t +(** Return all targets (files and directories) in a directory. + Handles directory targets by building them first. *) +val targets_of : dir:Path.t -> Targets.t Memo.t + (** Execute an action. The execution is cached. *) val execute_action : observing_facts:Dep.Facts.t -> Rule.Anonymous_action.t -> unit Memo.t diff --git a/test/blackbox-tests/test-cases/describe/targets.t/dune b/test/blackbox-tests/test-cases/describe/targets.t/dune index b848d9cd25e..3d2aa22c2e8 100644 --- a/test/blackbox-tests/test-cases/describe/targets.t/dune +++ b/test/blackbox-tests/test-cases/describe/targets.t/dune @@ -5,5 +5,5 @@ (targets (dir d)) (action (progn - (run mkdir d) - (run cat > d/foo)))) + (run mkdir -p d/subdir) + (run touch d/foo d/bar d/subdir/nested)))) 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..392cc9a01ab 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 @@ -64,21 +73,54 @@ The command also works with files in the _build directory. simple2.cmxs simple2.ml-gen -We cannot see inside directory targets +We can see inside directory targets. The directory target `d` contains files +and a subdirectory. $ dune show targets d - Error: Directory d is a directory target. This command does not support the - inspection of directory targets. + bar + foo + subdir/ -We cannot show targets in build-only directories that don't exist in the source -tree, like .simple.objs: +We can also inspect directory targets using the _build path: + + $ dune show targets _build/default/d + bar + foo + subdir/ + +We can also look inside subdirectories of directory targets: + + $ dune show targets d/subdir + nested + + $ dune show targets _build/default/d/subdir + nested + +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. + byte/ + cctx.ocaml-index + jsoo/ + native/ -CR-soon Alizter: This should work $ dune show targets _build/default/.simple.objs - Error: Directory _build/default/.simple.objs does not exist. + 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