From edecc738dc7ae1283581d228e3eeae3246397552 Mon Sep 17 00:00:00 2001
From: Etienne Millon <me@emillon.org>
Date: Thu, 13 Jan 2022 17:14:16 +0100
Subject: [PATCH 1/3] Warn when dune-project does not exist

We used to display a message when creating or editing dune-project files, but now that we do not do this anymore, a "default" dune-project is assumed silently. This PR adds a warning when it happens. This warning is fatal when --release is passed.

Signed-off-by: Etienne Millon <me@emillon.org>
---
 CHANGES.md                                    |  2 +
 bin/common.ml                                 | 22 ++++++++--
 src/dune_engine/clflags.ml                    |  2 +
 src/dune_engine/clflags.mli                   |  3 ++
 src/dune_engine/source_tree.ml                | 19 +++++++++
 .../test-cases/coq/github3624.t/run.t         |  3 ++
 .../test-cases/dune-init.t/run.t              | 18 +++++++++
 .../test-cases/github1529.t/run.t             |  3 ++
 .../test-cases/github4682.t/dune-project      |  1 +
 test/blackbox-tests/test-cases/github4684.t   |  3 ++
 test/blackbox-tests/test-cases/github4936.t   |  3 ++
 .../test-cases/no-dune-project.t              | 40 +++++++++++++++++++
 .../promote/dep-on-promoted-target.t/run.t    |  4 ++
 13 files changed, 119 insertions(+), 4 deletions(-)
 create mode 100644 test/blackbox-tests/test-cases/github4682.t/dune-project
 create mode 100644 test/blackbox-tests/test-cases/no-dune-project.t

diff --git a/CHANGES.md b/CHANGES.md
index 0740a43bc15..0737832d7d2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -130,6 +130,8 @@ Unreleased
 - Dune no longer automatically create or edit `dune-project` files
   (#4239, fixes #4108, @jeremiedimino)
 
+- Warn if `dune-project` is not found (fatal in release mode) (#5343, @emillon)
+
 - Cleanup temporary files after running `$ dune exec`. (#4260, fixes #4243,
   @rgrinberg)
 
diff --git a/bin/common.ml b/bin/common.ml
index 7a55b5f8d7f..e762bff5098 100644
--- a/bin/common.ml
+++ b/bin/common.ml
@@ -51,6 +51,7 @@ type t =
   ; workspace_config : Dune_rules.Workspace.Clflags.t
   ; cache_debug_flags : Dune_engine.Cache_debug_flags.t
   ; report_errors_config : Dune_engine.Report_errors_config.t
+  ; require_dune_project_file : bool
   }
 
 let capture_outputs t = t.capture_outputs
@@ -204,6 +205,7 @@ let init ?log_file c =
   Clflags.promote_install_files := c.promote_install_files;
   Clflags.always_show_command_line := c.always_show_command_line;
   Clflags.ignore_promoted_rules := c.ignore_promoted_rules;
+  Clflags.require_dune_project_file := c.require_dune_project_file;
   Dune_util.Log.info
     [ Pp.textf "Workspace root: %s"
         (Path.to_absolute_filename Path.root |> String.maybe_quoted)
@@ -313,6 +315,7 @@ module Options_implied_by_dash_p = struct
     ; default_target : Arg.Dep.t
     ; always_show_command_line : bool
     ; promote_install_files : bool
+    ; require_dune_project_file : bool
     }
 
   let docs = copts_sect
@@ -413,6 +416,12 @@ module Options_implied_by_dash_p = struct
         last
         & opt_all ~vopt:true bool [ false ]
         & info [ "promote-install-files" ] ~docs ~doc)
+    and+ require_dune_project_file =
+      let doc = "Fail if a dune-project file is missing." in
+      Arg.(
+        last
+        & opt_all ~vopt:true bool [ false ]
+        & info [ "require-dune-project-file" ] ~docs ~doc)
     in
     { root
     ; only_packages = No_restriction
@@ -422,6 +431,7 @@ module Options_implied_by_dash_p = struct
     ; default_target
     ; always_show_command_line
     ; promote_install_files
+    ; require_dune_project_file
     }
 
   let dash_dash_release =
@@ -436,6 +446,7 @@ module Options_implied_by_dash_p = struct
           ; "release"
           ; "--always-show-command-line"
           ; "--promote-install-files"
+          ; "--require-dune-project-file"
           ; "--default-target"
           ; "@install"
           ]
@@ -444,10 +455,11 @@ module Options_implied_by_dash_p = struct
             "Put $(b,dune) into a reproducible $(i,release) mode. This is in \
              fact a shorthand for $(b,--root . --ignore-promoted-rules \
              --no-config --profile release --always-show-command-line \
-             --promote-install-files --default-target @install). You should \
-             use this option for release builds. For instance, you must use \
-             this option in your $(i,<package>.opam) files. Except if you \
-             already use $(b,-p), as $(b,-p) implies this option.")
+             --promote-install-files --default-target @install \
+             --require-dune-project-file). You should use this option for \
+             release builds. For instance, you must use this option in your \
+             $(i,<package>.opam) files. Except if you already use $(b,-p), as \
+             $(b,-p) implies this option.")
 
   let options =
     let+ t = options
@@ -872,6 +884,7 @@ let term ~default_root_is_cwd =
        ; default_target
        ; always_show_command_line
        ; promote_install_files
+       ; require_dune_project_file
        } =
     Options_implied_by_dash_p.term
   and+ x =
@@ -1042,6 +1055,7 @@ let term ~default_root_is_cwd =
       }
   ; cache_debug_flags
   ; report_errors_config
+  ; require_dune_project_file
   }
 
 let set_rpc t rpc = { t with rpc = Some rpc }
diff --git a/src/dune_engine/clflags.ml b/src/dune_engine/clflags.ml
index 591d4049ac0..2b8ba6ebfb7 100644
--- a/src/dune_engine/clflags.ml
+++ b/src/dune_engine/clflags.ml
@@ -37,3 +37,5 @@ let always_show_command_line = ref false
 let promote_install_files = ref false
 
 let ignore_promoted_rules = ref false
+
+let require_dune_project_file = ref false
diff --git a/src/dune_engine/clflags.mli b/src/dune_engine/clflags.mli
index ed637cd95ad..4b64786e2ec 100644
--- a/src/dune_engine/clflags.mli
+++ b/src/dune_engine/clflags.mli
@@ -53,3 +53,6 @@ val promote_install_files : bool ref
 
 (** Whether we are ignoring rules with [(mode promote)] *)
 val ignore_promoted_rules : bool ref
+
+(** Whether the "no dune-project file in tree" warning should be fatal *)
+val require_dune_project_file : bool ref
diff --git a/src/dune_engine/source_tree.ml b/src/dune_engine/source_tree.ml
index fcbca1e8fd6..bbe17018bc1 100644
--- a/src/dune_engine/source_tree.ml
+++ b/src/dune_engine/source_tree.ml
@@ -476,6 +476,23 @@ end = struct
       (visited, init)
   end
 
+  let ensure_dune_project_file_exists =
+    Memo.create "ensure-dune-project-file-exists"
+      ~input:(module Dune_project)
+      (fun project ->
+        let open Memo.Build.O in
+        let+ exists =
+          Dune_project.file project |> Path.source |> Fs_memo.file_exists
+        in
+        if not exists then
+          User_warning.emit
+            ~is_error:!Clflags.require_dune_project_file
+            [ Pp.text
+                "No dune-project file has been found. A default one is assumed \
+                 but the project might break when dune is upgraded. Please \
+                 create a dune-project file."
+            ])
+
   let dune_file ~(dir_status : Sub_dirs.Status.t) ~path ~files ~project =
     let file_exists =
       if dir_status = Data_only then
@@ -511,6 +528,8 @@ end = struct
       | None, Some (path, _) -> Some path
     in
     Memo.Build.Option.map file ~f:(fun file ->
+        let open Memo.Build.O in
+        let* () = Memo.exec ensure_dune_project_file_exists project in
         let file_exists = Option.is_some file_exists in
         let from_parent = Option.map from_parent ~f:snd in
         Dune_file.load file ~file_exists ~project ~from_parent)
diff --git a/test/blackbox-tests/test-cases/coq/github3624.t/run.t b/test/blackbox-tests/test-cases/coq/github3624.t/run.t
index c3914ff4f2b..10aa2314825 100644
--- a/test/blackbox-tests/test-cases/coq/github3624.t/run.t
+++ b/test/blackbox-tests/test-cases/coq/github3624.t/run.t
@@ -8,6 +8,9 @@ that the error message is good when the coq extension is not enabled.
   >  (name foo))
   > EOF
   $ dune build
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
   File "dune", line 1, characters 0-24:
   1 | (coq.theory
   2 |  (name foo))
diff --git a/test/blackbox-tests/test-cases/dune-init.t/run.t b/test/blackbox-tests/test-cases/dune-init.t/run.t
index 33ffb22f015..6c0536b3617 100644
--- a/test/blackbox-tests/test-cases/dune-init.t/run.t
+++ b/test/blackbox-tests/test-cases/dune-init.t/run.t
@@ -20,6 +20,9 @@ Can init a public library
 Can build the public library
 
   $ (cd _test_lib_dir && touch test_lib.opam && dune build)
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
   $ cat ./_test_lib_dir/dune
   (library
    (public_name test_lib)
@@ -68,10 +71,16 @@ Can init a public executable
 Can build an executable
 
   $ (cd _test_bin_dir && touch test_bin.opam && dune build)
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
 
 Can run the created executable
 
   $ (cd _test_bin_dir && dune exec test_bin)
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
   Hello, World!
 
 Clean up the executable tests
@@ -145,10 +154,16 @@ Can init a library and dependent executable in a combo project
 Can build the combo project
 
   $ (cd _test_lib_exe_dir && touch test_bin.opam && dune build)
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
 
 Can run the combo project
 
   $ (cd _test_lib_exe_dir && dune exec test_bin)
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
   Hello, World!
 
 Clean up the combo project
@@ -175,6 +190,9 @@ Can add multiple libraries in the same directory
 Can build the multiple library project
 
   $ (cd _test_lib && touch test_lib1.opam && dune build)
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
 
 Clan up the multiple library project
 
diff --git a/test/blackbox-tests/test-cases/github1529.t/run.t b/test/blackbox-tests/test-cases/github1529.t/run.t
index c258d1e2d8f..090bb9c01a4 100644
--- a/test/blackbox-tests/test-cases/github1529.t/run.t
+++ b/test/blackbox-tests/test-cases/github1529.t/run.t
@@ -2,6 +2,9 @@ Reproduction case for #1529: using an extension when no dune-project
 file is present.
 
   $ dune build @install 2>&1 | sed "s/(lang dune .*)/(lang dune <version>)/" | sed "s/(using menhir .*)/(using menhir <version>)/"
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
   File "dune", line 1, characters 0-25:
   1 | (menhir (modules parser))
       ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/test/blackbox-tests/test-cases/github4682.t/dune-project b/test/blackbox-tests/test-cases/github4682.t/dune-project
new file mode 100644
index 00000000000..c2e46604eed
--- /dev/null
+++ b/test/blackbox-tests/test-cases/github4682.t/dune-project
@@ -0,0 +1 @@
+(lang dune 2.8)
diff --git a/test/blackbox-tests/test-cases/github4684.t b/test/blackbox-tests/test-cases/github4684.t
index 99935e1df9f..989798e7a2e 100644
--- a/test/blackbox-tests/test-cases/github4684.t
+++ b/test/blackbox-tests/test-cases/github4684.t
@@ -3,6 +3,9 @@ build failure:
 
   $ echo "module X = Root.Unix" > main.ml
   $ touch main.opam
+  $ cat >dune-project <<EOF
+  > (lang dune 2.8)
+  > EOF
   $ cat >dune <<EOF
   > (library
   >  (name main)
diff --git a/test/blackbox-tests/test-cases/github4936.t b/test/blackbox-tests/test-cases/github4936.t
index 8ef85234903..25c9260b585 100644
--- a/test/blackbox-tests/test-cases/github4936.t
+++ b/test/blackbox-tests/test-cases/github4936.t
@@ -1,6 +1,9 @@
 C stubs and the tests stanza
 
   $ touch e.ml stubs.c
+  $ cat > dune-project << EOF
+  > (lang dune 2.0)
+  > EOF
   $ cat > dune << EOF
   > (test
   >  (name e)
diff --git a/test/blackbox-tests/test-cases/no-dune-project.t b/test/blackbox-tests/test-cases/no-dune-project.t
new file mode 100644
index 00000000000..4d820b86693
--- /dev/null
+++ b/test/blackbox-tests/test-cases/no-dune-project.t
@@ -0,0 +1,40 @@
+When no dune-project file is present, a warning is printed.
+
+  $ touch dune
+  $ dune build
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
+
+In release mode, this is fatal.
+
+  $ dune build --release
+  Error: No dune-project file has been found. A default one is assumed but the
+  project might break when dune is upgraded. Please create a dune-project file.
+  [1]
+
+This corresponds to a flag:
+
+  $ dune build --require-dune-project-file
+  Error: No dune-project file has been found. A default one is assumed but the
+  project might break when dune is upgraded. Please create a dune-project file.
+  [1]
+
+Test case: warning should be emitted
+
+  $ mkdir nested-case && cd nested-case
+  $ mkdir a && touch a/dune
+  $ dune build
+  Warning: No dune-project file has been found. A default one is assumed but
+  the project might break when dune is upgraded. Please create a dune-project
+  file.
+  $ cd ..
+
+Test case: warning should not be emitted
+
+  $ mkdir another-case && cd another-case
+  $ mkdir a && touch a/dune
+  $ echo "(lang dune 3.0)" > a/dune-project
+  $ cp -R a b
+  $ dune build
+  $ cd ..
diff --git a/test/blackbox-tests/test-cases/promote/dep-on-promoted-target.t/run.t b/test/blackbox-tests/test-cases/promote/dep-on-promoted-target.t/run.t
index 63c13e1aedd..e1ffc1a1fc5 100644
--- a/test/blackbox-tests/test-cases/promote/dep-on-promoted-target.t/run.t
+++ b/test/blackbox-tests/test-cases/promote/dep-on-promoted-target.t/run.t
@@ -1,5 +1,9 @@
 Depending on a promoted file works.
 
+  $ cat > dune-project <<EOF
+  > (lang dune 2.0)
+  > EOF
+
   $ cat > dune <<EOF
   > (rule
   >   (mode promote)

From 5c8dec97bb8ca763c11babd059f41ef1b4ab0c7d Mon Sep 17 00:00:00 2001
From: Etienne Millon <me@emillon.org>
Date: Fri, 14 Jan 2022 10:23:32 +0100
Subject: [PATCH 2/3] Remove unused type

Signed-off-by: Etienne Millon <me@emillon.org>
---
 src/dune_engine/dune_project.ml  | 4 ----
 src/dune_engine/dune_project.mli | 4 ----
 2 files changed, 8 deletions(-)

diff --git a/src/dune_engine/dune_project.ml b/src/dune_engine/dune_project.ml
index 758b3a6fdcd..3637ffa3b97 100644
--- a/src/dune_engine/dune_project.ml
+++ b/src/dune_engine/dune_project.ml
@@ -267,10 +267,6 @@ let default_dune_language_version =
 let get_dune_lang () =
   { (Lang.get_exn "dune") with version = !default_dune_language_version }
 
-type created_or_already_exist =
-  | Created
-  | Already_exist
-
 module Extension = struct
   type 'a t = 'a Univ_map.Key.t
 
diff --git a/src/dune_engine/dune_project.mli b/src/dune_engine/dune_project.mli
index dcd52483d25..8672f74aeb9 100644
--- a/src/dune_engine/dune_project.mli
+++ b/src/dune_engine/dune_project.mli
@@ -152,10 +152,6 @@ val anonymous :
 (** "dune-project" *)
 val filename : string
 
-type created_or_already_exist =
-  | Created
-  | Already_exist
-
 (** Default language version to use for projects that don't have a
     [dune-project] file. The default value is the latest version of the dune
     language. *)

From f3a87fb2e479c7bd096e3f0aed8138c87a6cb07d Mon Sep 17 00:00:00 2001
From: Rudi Grinberg <me@rgrinberg.com>
Date: Mon, 7 Feb 2022 18:33:56 -0700
Subject: [PATCH 3/3] fix: get rid of project check for dune init

Signed-off-by: Rudi Grinberg <me@rgrinberg.com>
---
 bin/common.ml                                 |  6 ++-
 bin/init.ml                                   |  1 +
 src/dune_engine/clflags.ml                    |  7 ++-
 src/dune_engine/clflags.mli                   |  9 +++-
 src/dune_engine/source_tree.ml                | 53 +++++++++++++------
 .../test-cases/coq/github3624.t/run.t         |  1 +
 .../test-cases/dune-init.t/run.t              |  6 +++
 .../test-cases/github1529.t/run.t             |  1 +
 .../test-cases/no-dune-project.t              |  4 ++
 9 files changed, 68 insertions(+), 20 deletions(-)

diff --git a/bin/common.ml b/bin/common.ml
index e762bff5098..53361513fb1 100644
--- a/bin/common.ml
+++ b/bin/common.ml
@@ -205,7 +205,11 @@ let init ?log_file c =
   Clflags.promote_install_files := c.promote_install_files;
   Clflags.always_show_command_line := c.always_show_command_line;
   Clflags.ignore_promoted_rules := c.ignore_promoted_rules;
-  Clflags.require_dune_project_file := c.require_dune_project_file;
+  Clflags.on_missing_dune_project_file :=
+    if c.require_dune_project_file then
+      Error
+    else
+      Warn;
   Dune_util.Log.info
     [ Pp.textf "Workspace root: %s"
         (Path.to_absolute_filename Path.root |> String.maybe_quoted)
diff --git a/bin/init.ml b/bin/init.ml
index 2ba95de2786..e5556146b84 100644
--- a/bin/init.ml
+++ b/bin/init.ml
@@ -210,6 +210,7 @@ let term =
       & info [ "pkg" ] ~docv ~doc)
   in
   let _config = Common.init common_term in
+  Dune_engine.Clflags.on_missing_dune_project_file := Dune_engine.Clflags.Ignore;
   let open Component in
   let context = Init_context.make path in
   let common : Options.Common.t = { name; libraries; pps } in
diff --git a/src/dune_engine/clflags.ml b/src/dune_engine/clflags.ml
index 2b8ba6ebfb7..01d27f818a1 100644
--- a/src/dune_engine/clflags.ml
+++ b/src/dune_engine/clflags.ml
@@ -38,4 +38,9 @@ let promote_install_files = ref false
 
 let ignore_promoted_rules = ref false
 
-let require_dune_project_file = ref false
+type on_missing_dune_project_file =
+  | Error
+  | Warn
+  | Ignore
+
+let on_missing_dune_project_file = ref Warn
diff --git a/src/dune_engine/clflags.mli b/src/dune_engine/clflags.mli
index 4b64786e2ec..446aeb5865a 100644
--- a/src/dune_engine/clflags.mli
+++ b/src/dune_engine/clflags.mli
@@ -54,5 +54,10 @@ val promote_install_files : bool ref
 (** Whether we are ignoring rules with [(mode promote)] *)
 val ignore_promoted_rules : bool ref
 
-(** Whether the "no dune-project file in tree" warning should be fatal *)
-val require_dune_project_file : bool ref
+type on_missing_dune_project_file =
+  | Error
+  | Warn
+  | Ignore
+
+(** Desired behavior when dune project file is absent *)
+val on_missing_dune_project_file : on_missing_dune_project_file ref
diff --git a/src/dune_engine/source_tree.ml b/src/dune_engine/source_tree.ml
index bbe17018bc1..72d688bf401 100644
--- a/src/dune_engine/source_tree.ml
+++ b/src/dune_engine/source_tree.ml
@@ -477,21 +477,42 @@ end = struct
   end
 
   let ensure_dune_project_file_exists =
-    Memo.create "ensure-dune-project-file-exists"
-      ~input:(module Dune_project)
-      (fun project ->
-        let open Memo.Build.O in
-        let+ exists =
-          Dune_project.file project |> Path.source |> Fs_memo.file_exists
-        in
-        if not exists then
-          User_warning.emit
-            ~is_error:!Clflags.require_dune_project_file
-            [ Pp.text
-                "No dune-project file has been found. A default one is assumed \
-                 but the project might break when dune is upgraded. Please \
-                 create a dune-project file."
-            ])
+    let memo =
+      let module Input = struct
+        type t = [ `Is_error of bool ] * Dune_project.t
+
+        let equal (a1, p1) (a2, p2) =
+          Poly.equal a1 a2 && Dune_project.equal p1 p2
+
+        let hash = Tuple.T2.hash Poly.hash Dune_project.hash
+
+        let to_dyn (`Is_error b, project) =
+          Dyn.(pair bool Dune_project.to_dyn) (b, project)
+      end in
+      Memo.create "ensure-dune-project-file-exists"
+        ~input:(module Input)
+        (fun (`Is_error is_error, project) ->
+          let open Memo.Build.O in
+          let+ exists =
+            Dune_project.file project |> Path.source |> Fs_memo.file_exists
+          in
+          if not exists then
+            User_warning.emit ~is_error
+              ~hints:
+                [ Pp.text
+                    "generate the project file with: $ dune init project <name>"
+                ]
+              [ Pp.text
+                  "No dune-project file has been found. A default one is \
+                   assumed but the project might break when dune is upgraded. \
+                   Please create a dune-project file."
+              ])
+    in
+    fun inp ->
+      match !Clflags.on_missing_dune_project_file with
+      | Ignore -> Memo.Build.return ()
+      | Warn -> Memo.exec memo (`Is_error false, inp)
+      | Error -> Memo.exec memo (`Is_error true, inp)
 
   let dune_file ~(dir_status : Sub_dirs.Status.t) ~path ~files ~project =
     let file_exists =
@@ -529,7 +550,7 @@ end = struct
     in
     Memo.Build.Option.map file ~f:(fun file ->
         let open Memo.Build.O in
-        let* () = Memo.exec ensure_dune_project_file_exists project in
+        let* () = ensure_dune_project_file_exists project in
         let file_exists = Option.is_some file_exists in
         let from_parent = Option.map from_parent ~f:snd in
         Dune_file.load file ~file_exists ~project ~from_parent)
diff --git a/test/blackbox-tests/test-cases/coq/github3624.t/run.t b/test/blackbox-tests/test-cases/coq/github3624.t/run.t
index 10aa2314825..1a2899d945c 100644
--- a/test/blackbox-tests/test-cases/coq/github3624.t/run.t
+++ b/test/blackbox-tests/test-cases/coq/github3624.t/run.t
@@ -11,6 +11,7 @@ that the error message is good when the coq extension is not enabled.
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
   File "dune", line 1, characters 0-24:
   1 | (coq.theory
   2 |  (name foo))
diff --git a/test/blackbox-tests/test-cases/dune-init.t/run.t b/test/blackbox-tests/test-cases/dune-init.t/run.t
index 6c0536b3617..f12640d48c0 100644
--- a/test/blackbox-tests/test-cases/dune-init.t/run.t
+++ b/test/blackbox-tests/test-cases/dune-init.t/run.t
@@ -23,6 +23,7 @@ Can build the public library
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
   $ cat ./_test_lib_dir/dune
   (library
    (public_name test_lib)
@@ -74,6 +75,7 @@ Can build an executable
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
 
 Can run the created executable
 
@@ -81,6 +83,7 @@ Can run the created executable
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
   Hello, World!
 
 Clean up the executable tests
@@ -157,6 +160,7 @@ Can build the combo project
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
 
 Can run the combo project
 
@@ -164,6 +168,7 @@ Can run the combo project
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
   Hello, World!
 
 Clean up the combo project
@@ -193,6 +198,7 @@ Can build the multiple library project
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
 
 Clan up the multiple library project
 
diff --git a/test/blackbox-tests/test-cases/github1529.t/run.t b/test/blackbox-tests/test-cases/github1529.t/run.t
index 090bb9c01a4..574bb73490c 100644
--- a/test/blackbox-tests/test-cases/github1529.t/run.t
+++ b/test/blackbox-tests/test-cases/github1529.t/run.t
@@ -5,6 +5,7 @@ file is present.
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
   File "dune", line 1, characters 0-25:
   1 | (menhir (modules parser))
       ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/test/blackbox-tests/test-cases/no-dune-project.t b/test/blackbox-tests/test-cases/no-dune-project.t
index 4d820b86693..ab1489b836a 100644
--- a/test/blackbox-tests/test-cases/no-dune-project.t
+++ b/test/blackbox-tests/test-cases/no-dune-project.t
@@ -5,12 +5,14 @@ When no dune-project file is present, a warning is printed.
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
 
 In release mode, this is fatal.
 
   $ dune build --release
   Error: No dune-project file has been found. A default one is assumed but the
   project might break when dune is upgraded. Please create a dune-project file.
+  Hint: generate the project file with: $ dune init project <name>
   [1]
 
 This corresponds to a flag:
@@ -18,6 +20,7 @@ This corresponds to a flag:
   $ dune build --require-dune-project-file
   Error: No dune-project file has been found. A default one is assumed but the
   project might break when dune is upgraded. Please create a dune-project file.
+  Hint: generate the project file with: $ dune init project <name>
   [1]
 
 Test case: warning should be emitted
@@ -28,6 +31,7 @@ Test case: warning should be emitted
   Warning: No dune-project file has been found. A default one is assumed but
   the project might break when dune is upgraded. Please create a dune-project
   file.
+  Hint: generate the project file with: $ dune init project <name>
   $ cd ..
 
 Test case: warning should not be emitted