diff --git a/bin/clean.ml b/bin/clean.ml index 4c44aae51d8..6873c7c5011 100644 --- a/bin/clean.ml +++ b/bin/clean.ml @@ -6,7 +6,7 @@ let command = [ `S "DESCRIPTION" ; `P {|Removes files added by dune such as _build, .install, and .merlin|} ; `P - {|If one or more PATH arguments are given, only remove those paths from the build directory.|} + {|If one or more PATH arguments are given, only those paths are removed from the build directory. A PATH may be a path inside the build directory (e.g. _build/default/foo) or a source directory (e.g. foo), in which case the corresponding artifacts are removed from every build context.|} ; `Blocks Common.help_secs ] in @@ -18,7 +18,7 @@ let command = log file be useless but with some FS this also causes [dune clean] to fail (cf https://github.com/ocaml/dune/issues/2964). *) let builder = Common.Builder.disable_log_file builder in - let common, _config = Common.init builder in + let common, config = Common.init builder in Global_lock.lock_exn (); match paths with | [] -> @@ -26,22 +26,42 @@ let command = |> Path.Source.Set.iter ~f:(fun p -> Fpath.unlink_no_err (Path.Source.to_string p)); Path.rm_rf Path.build_dir | paths -> - let build_path path = - let path = Path.of_string (Common.prefix_target common path) in - match Path.as_in_build_dir path with - | Some path -> path - | None -> - User_error.raise - [ Pp.textf - "%s is not inside the build directory" - (Path.to_string_maybe_quoted path) - ] + let remove_build_path path = + Dune_engine.Rule_cache.Workspace_local.remove_target path; + Dune_engine.Rule_cache.Workspace_local.remove_subtree path; + Path.rm_rf (Path.build path) in - List.iter paths ~f:(fun path -> - let path = build_path path in - let () = Dune_engine.Rule_cache.Workspace_local.remove_target path in - let () = Dune_engine.Rule_cache.Workspace_local.remove_subtree path in - Path.rm_rf (Path.build path)) + (* Paths already inside the build directory are removed as is. Source paths + (including the project root) are resolved against every build context, + mirroring how [dune build] accepts source directories. *) + let build_paths, source_paths = + List.partition_map paths ~f:(fun path -> + let path = Path.of_string (Common.prefix_target common path) in + match Path.as_in_build_dir path with + | Some path -> Left path + | None -> + (match Path.as_in_source_tree path with + | Some src -> Right src + | None -> + User_error.raise + [ Pp.textf + "%s is not inside the build directory or the source tree" + (Path.to_string_maybe_quoted path) + ])) + in + let source_build_paths = + match source_paths with + | [] -> [] + | _ :: _ -> + Scheduler_setup.go_without_rpc_server ~common ~config (fun () -> + Memo.run + (let open Memo.O in + let+ contexts = Context.DB.all () in + List.concat_map source_paths ~f:(fun src -> + List.map contexts ~f:(fun ctx -> + Path.Build.append_source (Context.build_dir ctx) src)))) + in + List.iter (build_paths @ source_build_paths) ~f:remove_build_path in Cmd.v (Cmd.info "clean" ~doc ~man) term ;; diff --git a/doc/changes/added/15305.md b/doc/changes/added/15305.md new file mode 100644 index 00000000000..0e058b3e96f --- /dev/null +++ b/doc/changes/added/15305.md @@ -0,0 +1,5 @@ +- `dune clean` now accepts source directories (and the project root) in + addition to paths inside the build directory. For example, `dune clean foo` + removes the build artifacts under the source directory `foo` from every build + context, mirroring how `dune build foo` accepts a source directory. + (#15305, fixes #2288, @rgrinberg) diff --git a/test/blackbox-tests/test-cases/clean.t b/test/blackbox-tests/test-cases/clean.t index b6ab46f9a03..76a6f00b38e 100644 --- a/test/blackbox-tests/test-cases/clean.t +++ b/test/blackbox-tests/test-cases/clean.t @@ -36,9 +36,9 @@ We can clean the entire workspace to re-run everything: foo bar -Issue #2288: unlike `dune build`, `dune clean` does not accept source -directories (or package names); it only accepts paths inside the build -directory. So cleaning a source directory, or the project root, is rejected. +Issue #2288: `dune clean` also accepts source directories, not just paths +inside the build directory, mirroring `dune build`. The corresponding artifacts +are removed from every build context. $ mkdir sub $ cat >sub/dune < (action (bash "touch baz && echo baz"))) > EOF -`dune build` accepts the source directory: - $ dune build sub baz + $ test -e _build/default/sub/baz && echo built + built -But `dune clean` rejects it: +Cleaning the source directory removes its artifacts, leaving the rest intact: $ dune clean sub - Error: sub is not inside the build directory - [1] + $ test -e _build/default/sub && echo present || echo removed + removed + $ test -e _build/default/foo && echo present || echo removed + present -The project root is rejected too: +Cleaning the project root cleans the whole build context: $ dune clean . - Error: . is not inside the build directory - [1] + $ test -e _build/default && echo present || echo removed + removed