Skip to content

Commit f181cca

Browse files
authored
Add (files) stanza (#12879)
Signed-off-by: Nicolás Ojeda Bär <n.oje.bar@gmail.com>
1 parent 0bdb227 commit f181cca

8 files changed

Lines changed: 148 additions & 21 deletions

File tree

doc/changes/added/12879.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Added `(files)` stanza, similar to `(dirs)` to control which files are visible
2+
to Dune on a per-directory basis. (#12879, @nojb)

doc/reference/dune/files.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
files
2+
-----
3+
4+
.. versionadded:: 3.21
5+
6+
The ``files`` stanza allows restricting which files Dune should consider in the
7+
current directory. Its syntax mirrors the :doc:`/reference/predicate-language`
8+
used by the ``dirs`` stanza and supports ``:standard`` (which expands to all
9+
files), globs, and set operations.
10+
11+
This is useful in mixed build setups where external tools such as ``make``
12+
produce artifacts that Dune should ignore.
13+
14+
Examples:
15+
16+
.. code:: dune
17+
18+
(files :standard \ *.cm*) ;; ignore bytecode/native artifacts
19+
(files *.ml *.mli) ;; only keep OCaml sources

doc/reference/dune/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ The following pages describe the available stanzas and their meanings.
5656
dynamic_include
5757
env
5858
dirs
59+
files
5960
data_only_dirs
6061
ignored_subdirs
6162
include_subdirs

src/source/dune_file.ml

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,40 +41,62 @@ let dyn_of_kind = function
4141

4242
module Dir_map = struct
4343
module Per_dir = struct
44+
let no_dupes =
45+
Option.merge ~f:(fun (loc, _) (loc2, _) ->
46+
let main_message = Pp.text "This stanza was already specified at:" in
47+
let annots =
48+
let main = User_message.make ~loc [ main_message ] in
49+
let related =
50+
[ User_message.make ~loc:loc2 [ Pp.text "Already defined here" ] ]
51+
in
52+
User_message.Annots.singleton
53+
Compound_user_error.annot
54+
[ Compound_user_error.make ~main ~related ]
55+
in
56+
User_error.raise
57+
~loc
58+
~annots
59+
[ main_message; Pp.verbatim (Loc.to_file_colon_line loc2) ])
60+
;;
61+
62+
module Files = struct
63+
type t = (Loc.t * Predicate_lang.Glob.t) option
64+
65+
let default = None
66+
67+
let eval t ~files =
68+
match t with
69+
| None -> files
70+
| Some (_, glob) ->
71+
Filename.Set.filter files ~f:(fun filename ->
72+
Predicate_lang.Glob.test glob ~standard:Predicate_lang.true_ filename)
73+
;;
74+
end
75+
4476
type t =
4577
{ sexps : Dune_lang.Ast.t list
4678
; subdir_status : Source_dir_status.Spec.input
79+
; files : Files.t
4780
}
4881

49-
let to_dyn { sexps; subdir_status = _ } =
82+
let to_dyn { sexps; subdir_status = _; files = _ } =
5083
let open Dyn in
5184
record
5285
[ "sexps", list Dune_lang.to_dyn (List.map ~f:Dune_lang.Ast.remove_locs sexps) ]
5386
;;
5487

5588
let empty =
56-
{ sexps = []; subdir_status = Source_dir_status.Map.init ~f:(fun _ -> None) }
89+
{ sexps = []
90+
; subdir_status = Source_dir_status.Map.init ~f:(fun _ -> None)
91+
; files = None
92+
}
5793
;;
5894

5995
let merge d1 d2 =
6096
{ sexps = d1.sexps @ d2.sexps
6197
; subdir_status =
62-
Source_dir_status.Map.merge d1.subdir_status d2.subdir_status ~f:(fun l r ->
63-
Option.merge l r ~f:(fun (loc, _) (loc2, _) ->
64-
let main_message = Pp.text "This stanza stanza was already specified at:" in
65-
let annots =
66-
let main = User_message.make ~loc [ main_message ] in
67-
let related =
68-
[ User_message.make ~loc:loc2 [ Pp.text "Already defined here" ] ]
69-
in
70-
User_message.Annots.singleton
71-
Compound_user_error.annot
72-
[ Compound_user_error.make ~main ~related ]
73-
in
74-
User_error.raise
75-
~loc
76-
~annots
77-
[ main_message; Pp.verbatim (Loc.to_file_colon_line loc2) ]))
98+
Source_dir_status.Map.merge d1.subdir_status d2.subdir_status ~f:no_dupes
99+
; files = no_dupes d1.files d2.files
78100
}
79101
;;
80102
end
@@ -113,12 +135,15 @@ module Dir_map = struct
113135
let merge_all = List.fold_left ~f:merge ~init:empty
114136
end
115137

138+
module Files = Dir_map.Per_dir.Files
139+
116140
module Ast = struct
117141
type t =
118142
| Ignored_sub_dirs of Loc.t * Predicate_lang.Glob.t
119143
| Data_only_dirs of Loc.t * Predicate_lang.Glob.t
120144
| Vendored_dirs of Loc.t * Predicate_lang.Glob.Element.t Predicate_lang.t
121145
| Dirs of Loc.t * Predicate_lang.Glob.t
146+
| Files of Loc.t * Predicate_lang.Glob.t
122147
| Subdir of Path.Local.t * t list
123148
| Include of
124149
{ loc : Loc.t
@@ -212,6 +237,15 @@ module Ast = struct
212237
Dirs (loc, dirs)
213238
;;
214239

240+
let files =
241+
let+ loc, files =
242+
Dune_lang.Syntax.since Stanza.syntax (3, 21)
243+
>>> Predicate_lang.Glob.decode
244+
|> located
245+
in
246+
Files (loc, files)
247+
;;
248+
215249
let data_only_dirs =
216250
let+ loc, glob =
217251
located
@@ -257,6 +291,7 @@ module Ast = struct
257291
@@
258292
let+ subdirs = multi_field "subdir" (subdir ~inside_include)
259293
and+ dirs = field_o "dirs" dirs
294+
and+ files = field_o "files" files
260295
and+ ignored_sub_dirs =
261296
multi_field "ignored_subdirs" (ignored_sub_dirs ~inside_subdir)
262297
and+ vendored_dirs = field_o "vendored_dirs" vendored_dirs
@@ -266,6 +301,7 @@ module Ast = struct
266301
let ast =
267302
List.concat
268303
[ Option.to_list dirs
304+
; Option.to_list files
269305
; Option.to_list vendored_dirs
270306
; subdirs
271307
; ignored_sub_dirs
@@ -281,7 +317,7 @@ module Ast = struct
281317
let statically_evaluated_stanzas =
282318
(* This list must be kept in sync with [decode]
283319
[include] is excluded b/c it's also a normal stanza *)
284-
[ "data_only_dirs"; "vendored_dirs"; "ignored_sub_dirs"; "subdir"; "dirs" ]
320+
[ "data_only_dirs"; "vendored_dirs"; "ignored_sub_dirs"; "subdir"; "dirs"; "files" ]
285321
;;
286322

287323
let decode ~inside_subdir ~inside_include =
@@ -340,6 +376,7 @@ module Group = struct
340376
; data_only_dirs : (Loc.t * Predicate_lang.Glob.t) option
341377
; vendored_dirs : (Loc.t * Predicate_lang.Glob.Element.t Predicate_lang.t) option
342378
; dirs : (Loc.t * Predicate_lang.Glob.t) option
379+
; files : (Loc.t * Predicate_lang.Glob.t) option
343380
; leftovers : Dune_lang.Ast.t list
344381
; subdirs : (Path.Local.t * Ast.t list) list
345382
}
@@ -349,6 +386,7 @@ module Group = struct
349386
; data_only_dirs = None
350387
; vendored_dirs = None
351388
; dirs = None
389+
; files = None
352390
; subdirs = []
353391
; leftovers = []
354392
}
@@ -385,6 +423,7 @@ module Group = struct
385423
| Vendored_dirs (loc, glob) ->
386424
{ t with vendored_dirs = Some (no_dupes "vendored_dirs" loc t.vendored_dirs glob) }
387425
| Dirs (loc, glob) -> { t with dirs = Some (no_dupes "dirs" loc t.dirs glob) }
426+
| Files (loc, glob) -> { t with files = Some (no_dupes "files" loc t.files glob) }
388427
| Subdir (path, stanzas) -> { t with subdirs = (path, stanzas) :: t.subdirs }
389428
| Leftovers stanzas -> { t with leftovers = List.rev_append stanzas t.leftovers }
390429
| Include _ -> assert false
@@ -412,7 +451,8 @@ let rec to_dir_map ast =
412451
let group = Group.of_ast ast in
413452
let node =
414453
let subdir_status = Group.subdir_status group in
415-
Dir_map.singleton { Dir_map.Per_dir.sexps = group.leftovers; subdir_status }
454+
let files = group.files in
455+
Dir_map.singleton { Dir_map.Per_dir.sexps = group.leftovers; subdir_status; files }
416456
in
417457
let subdirs =
418458
List.map group.subdirs ~f:(fun (path, stanzas) ->
@@ -463,6 +503,7 @@ let get_static_sexp t = (Dir_map.root t.plain).sexps
463503
let kind t = t.kind
464504
let path t = t.path
465505
let sub_dir_status t = Source_dir_status.Spec.create (Dir_map.root t.plain).subdir_status
506+
let files t = (Dir_map.root t.plain).files
466507

467508
let load_plain sexps ~file ~from_parent ~project =
468509
let+ parsed =

src/source/dune_file.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ val path : t -> Path.Source.t option
2525

2626
val sub_dir_status : t -> Source_dir_status.Spec.t
2727

28+
module Files : sig
29+
type t
30+
31+
val default : t
32+
val eval : t -> files:Filename.Set.t -> Filename.Set.t
33+
end
34+
35+
val files : t -> Files.t
36+
2837
(** Directories introduced via [(subdir ..)] *)
2938
val sub_dirnames : t -> Filename.t list
3039

src/source/source_tree.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ and contents
194194
=
195195
let files = Dir_contents.files readdir in
196196
let+ dune_file = Dune_file.load ~dir:path dir_status project ~files ~parent:dune_file in
197+
let files =
198+
let predicate =
199+
match dune_file with
200+
| None -> Dune_file.Files.default
201+
| Some dune_file -> Dune_file.files dune_file
202+
in
203+
Dune_file.Files.eval predicate ~files
204+
in
197205
let vcs = Dir0.Vcs.get_vcs ~default:default_vcs ~readdir ~path in
198206
let sub_dirs =
199207
let sub_dirs =
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
The ``files`` stanza lets us ignore source artifacts produced by other build
2+
tools.
3+
4+
$ cat >dune-project <<EOF
5+
> (lang dune 3.21)
6+
> EOF
7+
8+
First, without the stanza, a pre-existing artifact conflicts with an explicit
9+
rule targeting the same filename.
10+
11+
$ touch mymod.ml
12+
$ cat >dune <<EOF
13+
> (library
14+
> (name mylib)
15+
> (wrapped false))
16+
> (rule (with-stdout-to foo.xyz (progn)))
17+
> EOF
18+
$ touch mylib.cma
19+
$ dune build
20+
Error: Multiple rules generated for _build/default/mylib.cma:
21+
- dune:1
22+
- file present in source tree
23+
Hint: rm -f mylib.cma
24+
[1]
25+
$ touch foo.xyz
26+
$ dune build
27+
Error: Multiple rules generated for _build/default/foo.xyz:
28+
- dune:4
29+
- file present in source tree
30+
Hint: rm -f foo.xyz
31+
[1]
32+
33+
With ``(files ...)`` the source artifact is ignored and the rule can build the
34+
target.
35+
36+
$ cat >>dune <<EOF
37+
> (files :standard \ *.cma *.xyz)
38+
> EOF
39+
40+
$ dune build
41+
$ ls _build/default
42+
foo.xyz
43+
mylib.a
44+
mylib.cma
45+
mylib.cmxa
46+
mylib.cmxs
47+
mymod.ml

test/blackbox-tests/test-cases/stanzas/subdir-stanza/basic.t

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ dir.
3838
File "bar/dune", line 1, characters 16-19:
3939
1 | (data_only_dirs foo)
4040
^^^
41-
Error: This stanza stanza was already specified at:
41+
Error: This stanza was already specified at:
4242
dune:1
4343
[1]
4444

0 commit comments

Comments
 (0)