Skip to content

Commit f6a4b55

Browse files
authored
One Directory per Package (#12614)
* Return package id instead of the entire package In future work, we'd like to introduce a package to every single stanza, but doing that naively will create cycles. * refactor: move Package.Id to own module to break some cycles * feature: Add a dir stanza to packages The dir stanza allows us to associate a directory tree with a particular package. This gives us two new facts: 1. If a stanza exists under this directory tree, it must belong to said package 2. If a stanza exists under another directory tree, it cannot belong to said package. Signed-off-by: Rudi Grinberg <[email protected]>
1 parent 2b51b80 commit f6a4b55

26 files changed

+421
-103
lines changed

bin/dune_init.ml

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ module File = struct
5959
| _ -> false
6060
;;
6161

62-
let csts_conflict project (a : Cst.t) (b : Cst.t) =
63-
let of_ast = Dune_rules.Stanzas.of_ast project in
62+
let csts_conflict project ~dir (a : Cst.t) (b : Cst.t) =
63+
let of_ast = Dune_rules.Stanzas.of_ast project ~dir in
6464
(let open Option.O in
6565
let* a_ast = Cst.abstract a in
6666
let+ b_ast = Cst.abstract b in
@@ -71,19 +71,19 @@ module File = struct
7171
;;
7272

7373
(* TODO(shonfeder): replace with stanza merging *)
74-
let find_conflicting project new_stanzas existing_stanzas =
74+
let find_conflicting project ~dir new_stanzas existing_stanzas =
7575
let conflicting_stanza stanza =
76-
match List.find ~f:(csts_conflict project stanza) existing_stanzas with
76+
match List.find ~f:(csts_conflict ~dir project stanza) existing_stanzas with
7777
| Some conflict -> Some (stanza, conflict)
7878
| None -> None
7979
in
8080
List.find_map ~f:conflicting_stanza new_stanzas
8181
;;
8282

83-
let add (project : Dune_project.t) stanzas = function
83+
let add (project : Dune_project.t) ~dir stanzas = function
8484
| Text f -> Text f (* Adding a stanza to a text file isn't meaningful *)
8585
| Dune f ->
86-
(match find_conflicting project stanzas f.content with
86+
(match find_conflicting project ~dir stanzas f.content with
8787
| None -> Dune { f with content = f.content @ stanzas }
8888
| Some (a, b) ->
8989
User_error.raise
@@ -430,6 +430,7 @@ module Component = struct
430430
; constraint_ = None
431431
}
432432
]
433+
~contents_basename:None
433434
in
434435
let packages = Package.Name.Map.singleton (Package.name package) package in
435436
let info =
@@ -457,7 +458,7 @@ module Component = struct
457458

458459
(* TODO Support for merging in changes to an existing stanza *)
459460
let add_stanza_to_dune_file ~(project : Dune_project.t) ~dir stanza =
460-
File.load_dune_file ~dir |> File.Stanza.add project stanza
461+
File.load_dune_file ~dir |> File.Stanza.add ~dir project stanza
461462
;;
462463

463464
(* Functions to make the various components, represented as lists of files *)
@@ -466,7 +467,7 @@ module Component = struct
466467
let dir = context.dir in
467468
let bin_dune =
468469
Stanza_cst.executable common options
469-
|> add_stanza_to_dune_file ~project:context.project ~dir
470+
|> add_stanza_to_dune_file ~dir ~project:context.project
470471
in
471472
let bin_ml =
472473
let name = sprintf "%s.ml" (Dune_lang.Atom.to_string common.name) in

doc/changes/added/12614.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- Introduce a `(dir ..)` field on packages defined in the `dune-project`. This
2+
field allows to associate a directory with a particular package. This makes
3+
dune automatically filter out all stanzas in this directory and its
4+
descendants with `--only-packages`. All users are recommended to switch to
5+
using this field. (#12614, fixes #3255, @rgrinberg)

src/dune_lang/dune_lang.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module Mode_conf = Mode_conf
6262
module Oxcaml = Oxcaml
6363
module Modules_settings = Modules_settings
6464
module Stanza_pkg = Stanza_pkg
65+
module Package_mask = Package_mask
6566

6667
(* CR-someday rgrinberg: perhaps wrap these under [Stanzas]? *)
6768
module Copy_files = Copy_files

src/dune_lang/dune_project.ml

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type t =
7373
; dune_version : Syntax.Version.t
7474
; info : Package_info.t
7575
; packages : Package.t Package.Name.Map.t
76+
; exclusive_dir_packages : Package_id.t Path.Source.Map.t
7677
; stanza_parser : Stanza.t list Decoder.t
7778
; project_file : Path.Source.t option
7879
; extension_args : Univ_map.t
@@ -114,7 +115,13 @@ let packages t = t.packages
114115
let name t = t.name
115116
let version t = t.version
116117
let root t = t.root
117-
let stanza_parser t = Decoder.set key t t.stanza_parser
118+
119+
let stanza_parser t ~dir =
120+
let mask = Package_mask.package_env ~dir ~packages:t.exclusive_dir_packages in
121+
let parser = Decoder.set key t t.stanza_parser in
122+
Decoder.set Package_mask.key mask parser
123+
;;
124+
118125
let file t = t.project_file
119126

120127
let implicit_transitive_deps t ocaml_version =
@@ -162,6 +169,7 @@ let to_dyn
162169
; expand_aliases_in_sandbox
163170
; opam_file_location
164171
; including_hidden_packages = _
172+
; exclusive_dir_packages = _
165173
}
166174
=
167175
let open Dyn in
@@ -489,6 +497,25 @@ let default_name ~dir ~(packages : Package.t Package.Name.Map.t) =
489497
Dune_project_name.named loc (Package.Name.to_string name)
490498
;;
491499

500+
let make_exclusive_dir_packages packages =
501+
match
502+
Package.Name.Map.values packages
503+
|> List.filter_map ~f:(fun package ->
504+
Package.exclusive_dir package
505+
|> Option.map ~f:(fun (loc, dir) -> loc, dir, Package.id package))
506+
|> Path.Source.Map.of_list_map ~f:(fun (_loc, dir, id) -> dir, id)
507+
with
508+
| Ok s -> s
509+
| Error (dir, (loc, _, _), (_, _, id)) ->
510+
User_error.raise
511+
~loc
512+
[ Pp.textf
513+
"package %s is already defined in %S"
514+
(Path.Source.to_string_maybe_quoted dir)
515+
(Package.Name.to_string id.name)
516+
]
517+
;;
518+
492519
let infer ~dir info packages =
493520
let lang = get_dune_lang () in
494521
let name = default_name ~dir ~packages in
@@ -534,6 +561,7 @@ let infer ~dir info packages =
534561
; expand_aliases_in_sandbox
535562
; opam_file_location
536563
; including_hidden_packages = packages
564+
; exclusive_dir_packages = make_exclusive_dir_packages packages
537565
}
538566
;;
539567

@@ -576,6 +604,7 @@ let encode : t -> Dune_sexp.t list =
576604
; expand_aliases_in_sandbox
577605
; opam_file_location = _
578606
; including_hidden_packages = _
607+
; exclusive_dir_packages = _
579608
} ->
580609
let open Encoder in
581610
let lang = Lang.get_exn "dune" in
@@ -1030,6 +1059,7 @@ let parse ~dir ~(lang : Lang.Instance.t) ~file =
10301059
; expand_aliases_in_sandbox
10311060
; opam_file_location
10321061
; including_hidden_packages = packages
1062+
; exclusive_dir_packages = make_exclusive_dir_packages packages
10331063
}
10341064
;;
10351065

src/dune_lang/dune_project.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ val packages : t -> Package.t Package.Name.Map.t
1919
val name : t -> Dune_project_name.t
2020
val version : t -> Package_version.t option
2121
val root : t -> Path.Source.t
22-
val stanza_parser : t -> Stanza.t list Decoder.t
22+
val stanza_parser : t -> dir:Path.Source.t -> Stanza.t list Decoder.t
2323
val generate_opam_files : t -> bool
2424
val set_generate_opam_files : bool -> t -> t
2525

src/dune_lang/package.ml

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,6 @@
11
open Import
22
module Name = Package_name
3-
4-
module Id = struct
5-
module T = struct
6-
type t =
7-
{ name : Name.t
8-
; dir : Path.Source.t
9-
}
10-
11-
let compare { name; dir } pkg =
12-
match Name.compare name pkg.name with
13-
| Eq -> Path.Source.compare dir pkg.dir
14-
| e -> e
15-
;;
16-
17-
let to_dyn { dir; name } =
18-
Dyn.record [ "name", Name.to_dyn name; "dir", Path.Source.to_dyn dir ]
19-
;;
20-
end
21-
22-
include T
23-
24-
let hash { name; dir } = Tuple.T2.hash Name.hash Path.Source.hash (name, dir)
25-
let name t = t.name
26-
27-
module C = Comparable.Make (T)
28-
module Set = C.Set
29-
module Map = C.Map
30-
end
3+
module Id = Package_id
314

325
type opam_file =
336
| Exists of bool
@@ -57,6 +30,7 @@ type t =
5730
; sites : Section.t Site.Map.t
5831
; allow_empty : bool
5932
; original_opam_file : original_opam_file option
33+
; exclusive_dir : (Loc.t * Path.Source.t) option
6034
}
6135

6236
(* Package name are globally unique, so we can reasonably expect that there will
@@ -80,6 +54,7 @@ let id t = t.id
8054
let original_opam_file t = t.original_opam_file
8155
let set_inside_opam_dir t ~dir = { t with opam_file = Name.file t.id.name ~dir }
8256
let set_version_and_info t ~version ~info = { t with version; info }
57+
let exclusive_dir t = t.exclusive_dir
8358

8459
let encode
8560
(name : Name.t)
@@ -99,6 +74,7 @@ let encode
9974
; allow_empty
10075
; opam_file = _
10176
; original_opam_file = _
77+
; exclusive_dir
10278
}
10379
=
10480
let open Encoder in
@@ -119,6 +95,10 @@ let encode
11995
(Name.Map.keys deprecated_package_names)
12096
; field_l "sites" (pair Site.encode Section.encode) (Site.Map.to_list sites)
12197
; field_b "allow_empty" allow_empty
98+
; field_o
99+
"dir"
100+
(fun (_, dir) -> Path.Source.basename dir |> Dune_sexp.atom_or_quoted_string)
101+
exclusive_dir
122102
]
123103
in
124104
list sexp (string "package" :: fields)
@@ -155,6 +135,11 @@ let decode =
155135
and+ depopts = field ~default:[] "depopts" (repeat Package_dependency.decode)
156136
and+ info = Package_info.decode ~since:(2, 0) ()
157137
and+ tags = field "tags" (enter (repeat string)) ~default:[]
138+
and+ exclusive_dir =
139+
field_o
140+
"dir"
141+
(let+ loc, s = Syntax.since Stanza.syntax (3, 21) >>> located string in
142+
loc, Path.Source.relative ~error_loc:loc dir s)
158143
and+ deprecated_package_names =
159144
name_map
160145
(Syntax.since Stanza.syntax (2, 0))
@@ -176,7 +161,7 @@ let decode =
176161
and+ allow_empty = field_b "allow_empty" ~check:(Syntax.since Stanza.syntax (3, 0))
177162
and+ lang_version = Syntax.get_exn Stanza.syntax in
178163
let allow_empty = lang_version < (3, 0) || allow_empty in
179-
let id = { Id.name; dir } in
164+
let id = Id.create ~name ~dir in
180165
let opam_file = Name.file id.name ~dir:id.dir in
181166
{ id
182167
; loc
@@ -194,6 +179,7 @@ let decode =
194179
; allow_empty
195180
; opam_file
196181
; original_opam_file = None
182+
; exclusive_dir
197183
}
198184
;;
199185

@@ -221,6 +207,7 @@ let to_dyn
221207
; allow_empty
222208
; opam_file = _
223209
; original_opam_file = _
210+
; exclusive_dir = _
224211
}
225212
=
226213
let open Dyn in
@@ -264,8 +251,9 @@ let create
264251
~tags
265252
~original_opam_file
266253
~deprecated_package_names
254+
~contents_basename
267255
=
268-
let id = { Id.name; dir } in
256+
let id = Id.create ~name ~dir in
269257
{ id
270258
; loc
271259
; version
@@ -282,5 +270,7 @@ let create
282270
; allow_empty
283271
; opam_file = Name.file name ~dir
284272
; original_opam_file
273+
; exclusive_dir =
274+
Option.map contents_basename ~f:(fun (loc, s) -> loc, Path.Source.relative dir s)
285275
}
286276
;;

src/dune_lang/package.mli

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
(** Information about a package defined in the workspace *)
22

33
open Import
4+
module Id : module type of Package_id with type t = Package_id.t
45

56
module Name : sig
67
type t = Package_name.t
78

89
include module type of Package_name with type t := t
910
end
1011

11-
module Id : sig
12-
type t
13-
14-
val name : t -> Name.t
15-
16-
include Comparable_intf.S with type key := t
17-
end
18-
1912
type opam_file =
2013
| Exists of bool
2114
| Generated
@@ -27,6 +20,7 @@ val deprecated_package_names : t -> Loc.t Name.Map.t
2720
val sites : t -> Section.t Site.Map.t
2821
val name : t -> Name.t
2922
val dir : t -> Path.Source.t
23+
val exclusive_dir : t -> (Loc.t * Path.Source.t) option
3024
val set_inside_opam_dir : t -> dir:Path.Source.t -> t
3125
val encode : Name.t -> t Encoder.t
3226
val decode : dir:Path.Source.t -> t Decoder.t
@@ -76,6 +70,7 @@ val create
7670
-> tags:string list
7771
-> original_opam_file:original_opam_file option
7872
-> deprecated_package_names:Loc.t Name.Map.t
73+
-> contents_basename:(Loc.t * Filename.t) option
7974
-> t
8075

8176
val original_opam_file : t -> original_opam_file option

src/dune_lang/package_id.ml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
open Import
2+
3+
module T = struct
4+
type t =
5+
{ name : Package_name.t
6+
; dir : Path.Source.t
7+
}
8+
9+
let compare { name; dir } pkg =
10+
match Package_name.compare name pkg.name with
11+
| Eq -> Path.Source.compare dir pkg.dir
12+
| e -> e
13+
;;
14+
15+
let to_dyn { dir; name } =
16+
Dyn.record [ "name", Package_name.to_dyn name; "dir", Path.Source.to_dyn dir ]
17+
;;
18+
end
19+
20+
include T
21+
22+
let create ~name ~dir = { name; dir }
23+
let hash { name; dir } = Tuple.T2.hash Package_name.hash Path.Source.hash (name, dir)
24+
let name t = t.name
25+
26+
module C = Comparable.Make (T)
27+
module Set = C.Set
28+
module Map = C.Map

src/dune_lang/package_id.mli

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
open Import
2+
3+
type t = private
4+
{ name : Package_name.t
5+
; dir : Path.Source.t
6+
}
7+
8+
val create : name:Package_name.t -> dir:Path.Source.t -> t
9+
val name : t -> Package_name.t
10+
val hash : t -> int
11+
val to_dyn : t -> Dyn.t
12+
val compare : t -> t -> Ordering.t
13+
14+
include Comparable_intf.S with type key := t

0 commit comments

Comments
 (0)