diff --git a/bin/common.ml b/bin/common.ml index d57b8aaa84d..a1418eeaca6 100644 --- a/bin/common.ml +++ b/bin/common.ml @@ -488,6 +488,7 @@ let shared_with_config_file = ; cache_storage_mode ; action_stdout_on_success ; action_stderr_on_success + ; project_defaults = None ; experimental = None } ;; diff --git a/bin/dune_init.ml b/bin/dune_init.ml index a01e608db72..409949d597f 100644 --- a/bin/dune_init.ml +++ b/bin/dune_init.ml @@ -169,12 +169,15 @@ end (** The context in which the initialization is executed *) module Init_context = struct + open Dune_config_file + type t = { dir : Path.t ; project : Dune_project.t + ; defaults : Dune_config.Project_defaults.t } - let make path = + let make path defaults = let open Memo.O in let+ project = (* CR-rgrinberg: why not get the project from the source tree? *) @@ -196,7 +199,7 @@ module Init_context = struct | Some p -> Path.of_string p in File.create_dir dir; - { dir; project } + { dir; project; defaults } ;; end @@ -373,7 +376,12 @@ module Component = struct let test common (() : Options.Test.t) = make "test" common [] (* A list of CSTs for dune-project file content *) - let dune_project ~opam_file_gen dir (common : Options.Common.t) = + let dune_project + ~opam_file_gen + ~(defaults : Dune_config_file.Dune_config.Project_defaults.t) + dir + (common : Options.Common.t) + = let cst = let package = Package.create @@ -400,7 +408,12 @@ module Component = struct ] in let packages = Package.Name.Map.singleton (Package.name package) package in - let info = Package_info.example in + let info = + Package_info.example + ~authors:defaults.authors + ~maintainers:defaults.maintainers + ~license:defaults.license + in Dune_project.anonymous ~dir info packages |> Dune_project.set_generate_opam_files opam_file_gen |> Dune_project.encode @@ -476,6 +489,7 @@ module Component = struct let content = Stanza_cst.dune_project ~opam_file_gen + ~defaults:context.defaults Path.(as_in_source_tree_exn context.dir) common in diff --git a/bin/dune_init.mli b/bin/dune_init.mli index 3c4dc97cec8..4e9a4127773 100644 --- a/bin/dune_init.mli +++ b/bin/dune_init.mli @@ -4,12 +4,15 @@ open Import (** The context in which the initialization is executed *) module Init_context : sig + open Dune_config_file + type t = { dir : Path.t ; project : Dune_project.t + ; defaults : Dune_config.Project_defaults.t } - val make : string option -> t Memo.t + val make : string option -> Dune_config.Project_defaults.t -> t Memo.t end module Public_name : sig diff --git a/bin/init.ml b/bin/init.ml index 0c4493085a9..1763f03ce49 100644 --- a/bin/init.ml +++ b/bin/init.ml @@ -74,7 +74,9 @@ let context_cwd : Init_context.t Term.t = and+ path = path in let builder = Common.Builder.set_default_root_is_cwd builder true in let common, config = Common.init builder in - Scheduler.go ~common ~config (fun () -> Memo.run (Init_context.make path)) + let project_defaults = config.project_defaults in + Scheduler.go ~common ~config (fun () -> + Memo.run (Init_context.make path project_defaults)) ;; module Public_name = struct @@ -228,7 +230,8 @@ let project = let builder = Builder.set_root common_builder root in let (_ : Fpath.mkdir_p_result) = Fpath.mkdir_p root in let common, config = Common.init builder in - Scheduler.go ~common ~config (fun () -> Memo.run init_context) + let project_defaults = config.project_defaults in + Scheduler.go ~common ~config (fun () -> Memo.run @@ init_context project_defaults) in Component.init (Project { context; common; options = { template; inline_tests; pkg } }); diff --git a/doc/changes/10835.md b/doc/changes/10835.md new file mode 100644 index 00000000000..2b1028b98a2 --- /dev/null +++ b/doc/changes/10835.md @@ -0,0 +1,3 @@ +- Add support for specifying default values of the `authors`, `maintainers`, and + `license` stanzas of the `dune-project` file via the dune config file. Default + values are set using the `(project_defaults)` stanza (#10835, @H-ANSEN) diff --git a/doc/reference/config/index.rst b/doc/reference/config/index.rst index 5b78bb94512..667b3a36b3e 100644 --- a/doc/reference/config/index.rst +++ b/doc/reference/config/index.rst @@ -22,5 +22,6 @@ The ``config`` file can contain the following stanzas: cache_storage_mode display jobs + project_defaults sandboxing_preference terminal_persistence diff --git a/doc/reference/config/project_defaults.rst b/doc/reference/config/project_defaults.rst new file mode 100644 index 00000000000..0bf5968b5d0 --- /dev/null +++ b/doc/reference/config/project_defaults.rst @@ -0,0 +1,53 @@ +project_defaults +---------------- + +.. versionadded:: 3.17 + +Specify default values for stanzas ``authors``, ``maintainers``, and ``license`` +of the :doc:`../dune-project/index` file when initializing a project with +``dune init proj``. The format of the 'project_defaults' stanza is as follows: + +.. code:: dune + + (project_defaults + ) + +```` are: + +.. describe:: (authors ) + + Specify authors. + + Example: + + .. code:: dune + + (project_defaults + (authors + "Jane Doe " + "John Doe ")) + +.. describe:: (maintainers ) + + Specify maintainers. + + Example: + + .. code:: dune + + (project_defaults + (maintainers + "Jane Doe " + "John Doe ")) + +.. describe:: (license ) + + Specify license, ideally as an identifier from the `SPDX License List + `__. + + Example: + + .. code:: dune + + (project_defaults + (license "MIT")) diff --git a/src/dune_config_file/dune_config_file.ml b/src/dune_config_file/dune_config_file.ml index ba01baaff28..a2901197ebb 100644 --- a/src/dune_config_file/dune_config_file.ml +++ b/src/dune_config_file/dune_config_file.ml @@ -16,6 +16,28 @@ module Dune_config = struct simplicity *) let syntax = Stanza.syntax + module Project_defaults = struct + type t = + { authors : string list option + ; maintainers : string list option + ; license : string list option + } + + let decode = + fields + (let+ authors = field_o "authors" (repeat string) + and+ maintainers = field_o "maintainers" (repeat string) + and+ license = field_o "license" (repeat string) in + { authors; maintainers; license }) + ;; + + let to_dyn t = + let f = Dyn.(option (list string)) in + Dyn.record + [ "authors", f t.authors; "maintainers", f t.maintainers; "license", f t.license ] + ;; + end + module Terminal_persistence = struct type t = | Preserve @@ -136,6 +158,7 @@ module Dune_config = struct ; cache_storage_mode : Cache.Storage_mode.t field ; action_stdout_on_success : Action_output_on_success.t field ; action_stderr_on_success : Action_output_on_success.t field + ; project_defaults : Project_defaults.t field ; experimental : (string * (Loc.t * string)) list field } end @@ -163,6 +186,7 @@ module Dune_config = struct field a.action_stdout_on_success b.action_stdout_on_success ; action_stderr_on_success = field a.action_stderr_on_success b.action_stderr_on_success + ; project_defaults = field a.project_defaults b.project_defaults ; experimental = field a.experimental b.experimental } ;; @@ -186,6 +210,7 @@ module Dune_config = struct ; cache_storage_mode ; action_stdout_on_success ; action_stderr_on_success + ; project_defaults ; experimental } = @@ -205,6 +230,7 @@ module Dune_config = struct , field Action_output_on_success.to_dyn action_stdout_on_success ) ; ( "action_stderr_on_success" , field Action_output_on_success.to_dyn action_stderr_on_success ) + ; "project_defaults", field Project_defaults.to_dyn project_defaults ; ( "experimental" , field Dyn.(list (pair string (fun (_, v) -> string v))) experimental ) ] @@ -228,6 +254,7 @@ module Dune_config = struct ; cache_storage_mode = None ; action_stdout_on_success = None ; action_stderr_on_success = None + ; project_defaults = None ; experimental = None } ;; @@ -294,6 +321,11 @@ module Dune_config = struct ; cache_storage_mode = Some (Dune_cache_storage.Mode.default ()) ; action_stdout_on_success = Print ; action_stderr_on_success = Print + ; project_defaults = + { authors = Some [ "Author Name " ] + ; maintainers = Some [ "Maintainer Name " ] + ; license = Some [ "LICENSE" ] + } ; experimental = [] } ;; @@ -357,6 +389,7 @@ module Dune_config = struct field_o "action_stdout_on_success" (3, 0) Action_output_on_success.decode and+ action_stderr_on_success = field_o "action_stderr_on_success" (3, 0) Action_output_on_success.decode + and+ project_defaults = field_o "project_defaults" (3, 17) Project_defaults.decode and+ experimental = field_o "experimental" (3, 8) (repeat (pair string (located string))) in @@ -377,6 +410,7 @@ module Dune_config = struct ; cache_storage_mode ; action_stdout_on_success ; action_stderr_on_success + ; project_defaults ; experimental } ;; diff --git a/src/dune_config_file/dune_config_file.mli b/src/dune_config_file/dune_config_file.mli index 0f33225084b..c08d3969734 100644 --- a/src/dune_config_file/dune_config_file.mli +++ b/src/dune_config_file/dune_config_file.mli @@ -5,6 +5,16 @@ module Dune_config : sig open Dune_config module Display : module type of Display + module Project_defaults : sig + type t = + { authors : string list option + ; maintainers : string list option + ; license : string list option + } + + val decode : t Dune_lang.Decoder.t + end + module Concurrency : sig type t = | Fixed of int @@ -56,6 +66,7 @@ module Dune_config : sig ; cache_storage_mode : Cache.Storage_mode.t field ; action_stdout_on_success : Action_output_on_success.t field ; action_stderr_on_success : Action_output_on_success.t field + ; project_defaults : Project_defaults.t field ; experimental : (string * (Loc.t * string)) list field } end diff --git a/src/dune_lang/package_info.ml b/src/dune_lang/package_info.ml index e1124f97198..cfcce1b6eb8 100644 --- a/src/dune_lang/package_info.ml +++ b/src/dune_lang/package_info.ml @@ -41,12 +41,14 @@ let empty = } ;; -let example = +let example ~authors ~maintainers ~license = { source = Some (Host (Source_kind.Host.Github { user = "username"; repo = "reponame" })) - ; license = Some [ "LICENSE" ] - ; authors = Some [ "Author Name " ] - ; maintainers = Some [ "Maintainer Name " ] + ; license = Some (Option.value license ~default:[ "LICENSE" ]) + ; authors = Some (Option.value authors ~default:[ "Author Name " ]) + ; maintainers = + Some + (Option.value maintainers ~default:[ "Maintainer Name " ]) ; documentation = Some "https://url/to/documentation" (* homepage and bug_reports are inferred from the source *) diff --git a/src/dune_lang/package_info.mli b/src/dune_lang/package_info.mli index 4e9003b410a..7a5dd3e1dba 100644 --- a/src/dune_lang/package_info.mli +++ b/src/dune_lang/package_info.mli @@ -9,7 +9,11 @@ val documentation : t -> string option val maintainers : t -> string list option (** example package info (used for project initialization ) *) -val example : t +val example + : authors:string list option + -> maintainers:string list option + -> license:string list option + -> t val empty : t val to_dyn : t Dyn.builder diff --git a/test/blackbox-tests/test-cases/config-project-defaults.t b/test/blackbox-tests/test-cases/config-project-defaults.t new file mode 100644 index 00000000000..4bfd71e62d3 --- /dev/null +++ b/test/blackbox-tests/test-cases/config-project-defaults.t @@ -0,0 +1,95 @@ +Create a config file to use in all test that follow, adding the +'project_defaults' stanza to specify default values for various fields of the +generated 'dune-project' file. + + $ touch dune-config + $ cat >dune-config < (lang dune 3.17) + > (project_defaults + > (authors AuthorTest) + > (maintainers MaintainerTest) + > (license MIT)) + > EOF + +Initialize a new dune project providing the config file we just created and +check each of the stanzas that we set defaults for in the config file. + + $ dune init proj test_proj --config-file=dune-config + Entering directory 'test_proj' + Success: initialized project component named test_proj + Leaving directory 'test_proj' + + $ cat test_proj/dune-project | grep -i authors + (authors AuthorTest) + + $ cat test_proj/dune-project | grep -i maintainers + (maintainers MaintainerTest) + + $ cat test_proj/dune-project | grep -i license + (license MIT) + +Change the version of the config file to one which does not support the +'project_defaults' stanza to ensure the proper error is raised. + + $ sed -i -e '1s|.*|(lang dune 3.16)|' dune-config + $ dune init proj test_proj1 --config-file=dune-config + File "$TESTCASE_ROOT/dune-config", lines 2-5, characters 0-85: + 2 | (project_defaults + 3 | (authors AuthorTest) + 4 | (maintainers MaintainerTest) + 5 | (license MIT)) + Error: 'project_defaults' is only available since version 3.17 of the dune + language. Please update your dune-project file to have (lang dune 3.17). + [1] + + $ sed -i -e '1s|.*|(lang dune 3.17)|' dune-config + +Check to ensure that the default values are used when optional stanzas are +removed/not used. + + $ sed -i -e '3,5c\ + > )' dune-config + $ dune init proj test_proj1 --config-file=dune-config + Entering directory 'test_proj1' + Success: initialized project component named test_proj1 + Leaving directory 'test_proj1' + + $ cat test_proj1/dune-project | grep -i authors + (authors "Author Name ") + + $ cat test_proj1/dune-project | grep -i maintainers + (maintainers "Maintainer Name ") + + $ cat test_proj1/dune-project | grep -i license + (license LICENSE) + +In the previous test all sub stanzas of the 'project_default' stanza where +removed so we will create a new config file continue testing. This time we will +used quoted string values and test the ability to add multiple +authors/maintainers. + + $ rm dune-config; touch dune-config + $ cat >dune-config < (lang dune 3.17) + > (project_defaults + > (authors "AuthorTest1" "AuthorTest2") + > (maintainers "Maintainer1" "Maintainer2" "Maintainer3") + > (license "BSD")) + > EOF + +Now we test to see if quoted list values are properly generated in the +dune-project file. + + $ dune init proj test_proj2 --config-file=dune-config + Entering directory 'test_proj2' + Success: initialized project component named test_proj2 + Leaving directory 'test_proj2' + + $ cat test_proj2/dune-project | grep -i authors + (authors AuthorTest1 AuthorTest2) + + $ cat test_proj2/dune-project | grep -i maintainers + (maintainers Maintainer1 Maintainer2 Maintainer3) + + $ cat test_proj2/dune-project | grep -i license + (license BSD) diff --git a/test/expect-tests/dune_config_file/dune_config_test.ml b/test/expect-tests/dune_config_file/dune_config_test.ml index 57ae3ee8d29..41caa4df0d3 100644 --- a/test/expect-tests/dune_config_file/dune_config_test.ml +++ b/test/expect-tests/dune_config_file/dune_config_test.ml @@ -27,6 +27,11 @@ let%expect_test "cache-check-probability 0.1" = ; cache_storage_mode = Some Hardlink ; action_stdout_on_success = Print ; action_stderr_on_success = Print + ; project_defaults = + { authors = [ "Author Name " ] + ; maintainers = [ "Maintainer Name " ] + ; license = [ "LICENSE" ] + } ; experimental = [] } |}] @@ -45,6 +50,11 @@ let%expect_test "cache-storage-mode copy" = ; cache_storage_mode = Some Copy ; action_stdout_on_success = Print ; action_stderr_on_success = Print + ; project_defaults = + { authors = [ "Author Name " ] + ; maintainers = [ "Maintainer Name " ] + ; license = [ "LICENSE" ] + } ; experimental = [] } |}] @@ -63,6 +73,11 @@ let%expect_test "cache-storage-mode hardlink" = ; cache_storage_mode = Some Hardlink ; action_stdout_on_success = Print ; action_stderr_on_success = Print + ; project_defaults = + { authors = [ "Author Name " ] + ; maintainers = [ "Maintainer Name " ] + ; license = [ "LICENSE" ] + } ; experimental = [] } |}]