Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto format dune-project files with dune fmt #10716

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions doc/howto/formatting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ configuration happens in that file. If you want to format OCaml sources and
``dune`` files, you don't have anything to add. Otherwise, refer to the
:doc:`/reference/dune-project/formatting` stanza.

Starting with version ``3.17`` ``dune`` will also automatically format ``dune-project``
files.

Next we need to install some code formatting tools. For OCaml code, this means
installing OCamlFormat_ with ``opam install ocamlformat``. Formatting ``dune``
files is built into Dune and does not require any extra tools. For Reason code,
Expand Down
4 changes: 2 additions & 2 deletions doc/reference/dune-project/formatting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ formatting
(formatting
(enabled_for <languages>))

The list of `<languages>` can be either ``dune`` (formatting of ``dune``
files) or a :term:`dialect` name.
Valid entries for the list of `<languages>` are ``dune``, ``dune-project``,
or a :term:`dialect` name.

.. seealso:: :doc:`/howto/formatting`
2 changes: 1 addition & 1 deletion src/dune_engine/load_rules.mli
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ end
(** Load the rules for this directory. *)
val load_dir : dir:Path.t -> Loaded.t Memo.t

(** Return the rule that has the given file has target, if any *)
(** Return the rule that has the given file as target, if any *)
val get_rule : Path.t -> Rule.t option Memo.t

(** Return the definition of an alias. *)
Expand Down
43 changes: 33 additions & 10 deletions src/dune_lang/format_config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,35 @@ module Language = struct
type t =
| Dialect of string
| Dune
| DuneProject
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dune_project is the convention


let compare t1 t2 =
match t1, t2 with
| Dialect s1, Dialect s2 -> String.compare s1 s2
| Dialect _, Dune -> Gt
| Dialect _, DuneProject -> Gt
| Dune, Dune -> Eq
| Dune, Dialect _ -> Lt
| Dialect _, Dune -> Gt
| Dialect s1, Dialect s2 -> String.compare s1 s2
| Dune, DuneProject -> Gt
| DuneProject, DuneProject -> Eq
| DuneProject, Dialect _ -> Lt
| DuneProject, Dune -> Lt
;;

let to_dyn =
let open Dyn in
function
| Dialect name -> variant "dialect" [ string name ]
| Dune -> variant "dune" []
| DuneProject -> variant "dune-project" []
;;
end

include Comparable.Make (T)
include T

let of_string = function
| "dune-project" -> DuneProject
| "dune" -> Dune
| s -> Dialect s
;;
Expand All @@ -46,25 +54,33 @@ module Language = struct
let open Encoder in
function
| Dune -> string "dune"
| DuneProject -> string "dune-project"
| Dialect d -> string d
;;
end

module Enabled_for = struct
type t =
| Only of Language.Set.t
| Dune_2_All
| All

let to_dyn =
let open Dyn in
function
| Only l -> variant "only" [ Language.Set.to_dyn l ]
| Dune_2_All -> string "dune_2_all"
| All -> string "all"
;;

let includes t =
match t with
| Only l -> Language.Set.mem l
| Dune_2_All ->
fun l ->
(match l with
| Language.Dialect _ | Language.Dune -> true
| _ -> false)
| All -> fun _ -> true
;;

Expand All @@ -76,8 +92,7 @@ module Enabled_for = struct
match list_opt, ext_version with
| Some l, _ -> Only (Language.Set.of_list l)
| None, (1, 0) -> Only Language.in_ext_1_0
| None, (1, 1) -> Only Language.in_ext_1_1
| None, (1, 2) -> All
| None, (1, 1) | None, (1, 2) -> Only Language.in_ext_1_1
| None, _ ->
Code_error.raise
"This fmt version does not exist"
Expand Down Expand Up @@ -132,10 +147,12 @@ let dune2_dec =

let enabled_for_all = { loc = Loc.none; enabled_for = Enabled_for.All }
let disabled = { loc = Loc.none; enabled_for = Enabled_for.Only Language.Set.empty }
let enabled_dune_2 = { loc = Loc.none; enabled_for = Enabled_for.Dune_2_All }
let field ~since = field_o "formatting" (Syntax.since Stanza.syntax since >>> dune2_dec)

let is_empty = function
| { enabled_for = Enabled_for.Only l; _ } -> Language.Set.is_empty l
| { enabled_for = Enabled_for.Dune_2_All; _ } -> false
| { enabled_for = All; _ } -> false
;;

Expand All @@ -159,19 +176,25 @@ let encode_explicit conf =
let to_explicit { loc; enabled_for } =
match enabled_for with
| Enabled_for.All -> None
| Enabled_for.Dune_2_All -> Some { loc; enabled_for = Language.in_ext_1_1 }
| Only l -> Some { loc; enabled_for = l }
;;

let encode_opt t = to_explicit t |> Option.map ~f:(fun c -> encode_explicit c.enabled_for)

let of_config ~ext ~dune_lang ~version =
let dune2 = version >= (2, 0) in
match ext, dune_lang, dune2 with
| None, None, true -> enabled_for_all
| None, None, false -> disabled
| Some x, None, false | None, Some x, true -> x
| _, Some _, false -> Code_error.raise "(formatting ...) stanza requires version 2.0" []
| Some ext, _, true ->
let dune3_17 = version >= (3, 17) in
match ext, dune_lang, dune2, dune3_17 with
| None, None, _, true -> enabled_for_all
| None, None, true, false -> enabled_dune_2
| None, None, false, _ -> disabled
| Some x, None, false, _ | None, Some x, true, true -> x
| None, Some x, true, false ->
if x.enabled_for == Enabled_for.All then enabled_dune_2 else x
| _, Some _, false, _ ->
Code_error.raise "(formatting ...) stanza requires version 2.0" []
| Some ext, _, true, _ ->
let suggestion =
match to_explicit ext with
| Some { enabled_for; _ } ->
Expand Down
1 change: 1 addition & 0 deletions src/dune_lang/format_config.mli
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Language : sig
type t =
| Dialect of string
| Dune
| DuneProject
end

type t
Expand Down
38 changes: 27 additions & 11 deletions src/dune_rules/format_rules.ml
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,27 @@ module Alias = struct
let fmt ~dir = Alias.make Alias0.fmt ~dir
end

let add_diff_rule ~sctx ~loc ~alias_formatted ~dir ~output_dir ~version path =
let open Memo.O in
let input_basename = Path.Source.basename path in
let input = Path.build (Path.Build.relative dir input_basename) in
let output = Path.Build.relative output_dir input_basename in
(let open Action_builder.O in
let+ () = Action_builder.path input in
Action.Full.make (action ~version input output))
|> Action_builder.with_file_targets ~file_targets:[ output ]
|> Super_context.add_rule sctx ~mode:Standard ~loc ~dir
>>> add_diff sctx loc alias_formatted ~dir ~input ~output
;;

let gen_rules_output
sctx
(config : Format_config.t)
~version
~dialects
~expander
~output_dir
~project
=
assert (formatted_dir_basename = Path.Build.basename output_dir);
let loc = Format_config.loc config in
Expand Down Expand Up @@ -133,16 +147,18 @@ let gen_rules_output
Source_tree.Dir.dune_file source_dir
|> Memo.Option.iter ~f:(fun f ->
Dune_file0.path f
|> Memo.Option.iter ~f:(fun path ->
let input_basename = Path.Source.basename path in
let input = Path.build (Path.Build.relative dir input_basename) in
let output = Path.Build.relative output_dir input_basename in
(let open Action_builder.O in
let+ () = Action_builder.path input in
Action.Full.make (action ~version input output))
|> Action_builder.with_file_targets ~file_targets:[ output ]
|> Super_context.add_rule sctx ~mode:Standard ~loc ~dir
>>> add_diff sctx loc alias_formatted ~dir ~input ~output)))
|> Memo.Option.iter
~f:(add_diff_rule ~sctx ~loc ~alias_formatted ~dir ~output_dir ~version)))
and* () =
match Format_config.includes config DuneProject with
| false -> Memo.return ()
| true ->
Dune_project.file project
|> Memo.Option.iter ~f:(fun path ->
let build_dir = Super_context.context sctx |> Context.build_dir in
if Path.Build.equal build_dir dir
then add_diff_rule ~sctx ~loc ~alias_formatted ~dir ~output_dir ~version path
else Memo.return ())
in
Rules.Produce.Alias.add_deps alias_formatted (Action_builder.return ())
;;
Expand Down Expand Up @@ -181,7 +197,7 @@ let gen_rules sctx ~output_dir =
let* project = Dune_load.find_project ~dir in
let dialects = Dune_project.dialects project in
let version = Dune_project.dune_version project in
gen_rules_output sctx config ~version ~dialects ~expander ~output_dir)
gen_rules_output sctx config ~version ~dialects ~expander ~output_dir ~project)
;;

let setup_alias ~dir =
Expand Down
2 changes: 1 addition & 1 deletion src/dune_sexp/syntax.ml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ end = struct
we'd have the following map (in associative list syntax):

{[
[ 1, [ 0, (1, 4); 1, (1, 6); 2, (2, 3) ]; 2, [ 0, (2, 3) ] ]
[ 1, [ 0, (1, 4); 1, (1, 6); 2, (2, 3) ]; 2, [ 0, (2, 4) ] ]
]} *)
type t =
{ version_map : Version.t Int.Map.t Int.Map.t
Expand Down
38 changes: 37 additions & 1 deletion test/blackbox-tests/test-cases/formatting/feature.t/run.t
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ Sometimes, the suggestion is to just remove the configuration.
5 | (using fmt 1.2)
^^^^^^^^^^^^^^^
Error: Starting with (lang dune 2.0), formatting is enabled by default.
To port it to the new syntax, you can delete this part.
To port it to the new syntax, you can replace this part by:
(formatting (enabled_for dune ocaml reason))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure whether we should extend the help message by mentioning the differences between > 3.17 dune and 3.17+
i.e. that you can remove the stanza, but that it will also format dune-project files.

[1]

Formatting can also be set in the (env ...) stanza
Expand Down Expand Up @@ -250,3 +251,38 @@ settings as dictated by the dune language version.
> ;; formatting disabled by default
> EOF
$ (cd using-env && dune build @fmt)

dune fmt automatically formats dune-project files starting from dune 3.17.

$ mkdir dune-project-files
$ touch dune-project-files/.ocamlformat
$ cat > dune-project-files/dune-project << EOF
> (lang dune 3.17)
> (name
> format_project)
> EOF
$ (cd dune-project-files && dune fmt)
File "dune-project", line 1, characters 0-0:
Error: Files _build/default/dune-project and
_build/default/.formatted/dune-project differ.
Promoting _build/default/.formatted/dune-project to dune-project.
[1]
$ cat dune-project-files/dune-project
(lang dune 3.17)

(name format_project)

But does not format if formatting is disabled for dune-project files.

$ cat > dune-project-files/dune-project << EOF
> (lang dune 3.17)
> (name
> format_project)
> (formatting (enabled_for ocaml dune reason))
> EOF
$ (cd dune-project-files && dune fmt)
$ cat dune-project-files/dune-project
(lang dune 3.17)
(name
format_project)
(formatting (enabled_for ocaml dune reason))
Loading