Skip to content

Commit b409963

Browse files
pkg: Use filtered_formula to represent dependencies (ocaml#10918)
* pkg: Pass-through `filtered_formula` from opam files to solver The `Dependency_set.t` representation can't deal with disjunctions but in most cases that is not even necessary as the set gets turned into a `filtered_formula` again. Thus it might be easier to keep the original representation and implement the necessary dependency set functionality on top of that. Signed-off-by: Marek Kubica <[email protected]> * Remove unused `Dependency_set` Signed-off-by: Marek Kubica <[email protected]> * Add a test showing that the disjunction in OPAM files is supported now Signed-off-by: Marek Kubica <[email protected]> * Move `filtered_formula` into our own module Signed-off-by: Marek Kubica <[email protected]> * Determine the hash from the Sexp Signed-off-by: Marek Kubica <[email protected]> * Move reachability into the formula Signed-off-by: Marek Kubica <[email protected]> * Add test for dependency formula changes Signed-off-by: Marek Kubica <[email protected]> * Clean up the awkward API Signed-off-by: Marek Kubica <[email protected]> * Promote expected hash changes Signed-off-by: Marek Kubica <[email protected]> * Update the test wording and show the difference Signed-off-by: Marek Kubica <[email protected]> * test(pkg): demonstrate unreachable packages being included Signed-off-by: Rudi Grinberg <[email protected]> Signed-off-by: Marek Kubica <[email protected]> * Do not include post dependencies in reachable packages Signed-off-by: Marek Kubica <[email protected]> * Replace sexp by dyn Signed-off-by: Marek Kubica <[email protected]> * Promote expected hash changes Signed-off-by: Marek Kubica <[email protected]> * `post` deps are excluded now Signed-off-by: Marek Kubica <[email protected]> * Simplify Signed-off-by: Marek Kubica <[email protected]> * Use `Resolve_opam_formula` to determine dependencies Signed-off-by: Marek Kubica <[email protected]>
1 parent aff18a8 commit b409963

19 files changed

+291
-169
lines changed

bin/describe/describe_pkg.ml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ module Dependency_hash = struct
4646
>>| Package_name.Map.values
4747
>>| List.map ~f:Local_package.for_solver
4848
in
49-
match
50-
Local_package.(
51-
For_solver.list_non_local_dependency_set local_packages |> Dependency_set.hash)
52-
with
49+
let hash =
50+
Local_package.For_solver.non_local_dependencies local_packages
51+
|> Local_package.Dependency_hash.of_dependency_formula
52+
in
53+
match hash with
5354
| None -> User_error.raise [ Pp.text "No non-local dependencies" ]
5455
| Some dependency_hash ->
5556
print_endline (Local_package.Dependency_hash.to_string dependency_hash)

bin/lock_dev_tool.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ let make_local_package_wrapping_dev_tool ~dev_tool ~dev_tool_version ~extra_depe
5454
in
5555
{ Dune_pkg.Local_package.name = local_package_name
5656
; version = None
57-
; dependencies = dependency :: extra_dependencies
57+
; dependencies =
58+
Dune_pkg.Dependency_formula.of_dependencies (dependency :: extra_dependencies)
5859
; conflicts = []
5960
; depopts = []
6061
; pins = Package_name.Map.empty

src/dune_pkg/dependency_formula.ml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
open Import
2+
3+
type t = OpamTypes.filtered_formula
4+
5+
let of_dependencies deps = Package_dependency.list_to_opam_filtered_formula deps
6+
let to_filtered_formula v = v
7+
let of_filtered_formula v = v
8+
let to_dyn = Opam_dyn.filtered_formula
9+
let ands = OpamFormula.ands
10+
11+
let remove_packages (v : OpamTypes.filtered_formula) pkgs =
12+
OpamFormula.map_up_formula
13+
(function
14+
| Atom (name, _condition) as a ->
15+
if let name = Package_name.of_opam_package_name name in
16+
Package_name.Set.mem pkgs name
17+
then Empty
18+
else a
19+
| x -> x)
20+
v
21+
;;
22+
23+
exception Found of Package_name.t
24+
25+
let any_package_name (v : OpamTypes.filtered_formula) =
26+
try
27+
OpamFormula.iter
28+
(fun (name, _condition) ->
29+
let name = Package_name.of_opam_package_name name in
30+
raise_notrace (Found name))
31+
v;
32+
None
33+
with
34+
| Found name -> Some name
35+
;;
36+
37+
let has_entries v = v |> any_package_name |> Option.is_some
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
type t
2+
3+
(** Create a dependency formula out of a [Package_dependency.t] list where
4+
all packages are dependencies *)
5+
val of_dependencies : Package_dependency.t list -> t
6+
7+
(** Convert to the OPAM data type *)
8+
val to_filtered_formula : t -> OpamTypes.filtered_formula
9+
10+
(** Convert from the OPAM data type to this *)
11+
val of_filtered_formula : OpamTypes.filtered_formula -> t
12+
13+
(** Create a Dyn representation of the dependency formula *)
14+
val to_dyn : t -> Dyn.t
15+
16+
(* Join all dependencies in the list to a single conjunction *)
17+
val ands : t list -> t
18+
19+
(** Remove a package from the entire formula *)
20+
val remove_packages : t -> Package_name.Set.t -> t
21+
22+
(** Determine whether the dependency formula has any dependencies *)
23+
val has_entries : t -> bool
24+
25+
(** Returns the [Package_name.t] of a dependency from the formula, if it
26+
exists. *)
27+
val any_package_name : t -> Package_name.t option

src/dune_pkg/dune_pkg.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module Opam_solver = Opam_solver
88
module OpamUrl = OpamUrl0
99
module Package_variable = Package_variable
1010
module Package_dependency = Package_dependency
11+
module Dependency_formula = Dependency_formula
1112
module Rev_store = Rev_store
1213
module Solver_env = Solver_env
1314
module Solver_stats = Solver_stats

src/dune_pkg/local_package.ml

Lines changed: 21 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type pins = pin Package_name.Map.t
1515
type t =
1616
{ name : Package_name.t
1717
; version : Package_version.t option
18-
; dependencies : Package_dependency.t list
18+
; dependencies : Dependency_formula.t
1919
; conflicts : Package_dependency.t list
2020
; conflict_class : Package_name.t list
2121
; depopts : Package_dependency.t list
@@ -38,55 +38,20 @@ module Dependency_hash = struct
3838
~loc
3939
[ Pp.textf "Dependency hash is not a valid md5 hash: %s" hash ]
4040
;;
41-
end
42-
43-
module Dependency_set = struct
44-
type t = Package_constraint.Set.t Package_name.Map.t
45-
46-
let empty = Package_name.Map.empty
47-
48-
let of_list =
49-
List.fold_left ~init:empty ~f:(fun acc { Package_dependency.name; constraint_ } ->
50-
Package_name.Map.update acc name ~f:(fun existing ->
51-
match existing, constraint_ with
52-
| None, None -> Some Package_constraint.Set.empty
53-
| None, Some constraint_ -> Some (Package_constraint.Set.singleton constraint_)
54-
| Some existing, None -> Some existing
55-
| Some existing, Some constraint_ ->
56-
Some (Package_constraint.Set.add existing constraint_)))
57-
;;
5841

59-
let union =
60-
Package_name.Map.union ~f:(fun _name a b -> Some (Package_constraint.Set.union a b))
61-
;;
62-
63-
let union_all = List.fold_left ~init:empty ~f:union
64-
65-
let package_dependencies =
66-
Package_name.Map.to_list_map ~f:(fun name constraints ->
67-
let constraint_ =
68-
if Package_constraint.Set.is_empty constraints
69-
then None
70-
else Some (Package_constraint.And (Package_constraint.Set.to_list constraints))
71-
in
72-
{ Package_dependency.name; constraint_ })
73-
;;
74-
75-
let encode_for_hash t =
76-
package_dependencies t |> Dune_lang.Encoder.list Package_dependency.encode
77-
;;
78-
79-
let hash t =
80-
if Package_name.Map.is_empty t
81-
then None
82-
else Some (encode_for_hash t |> Dune_sexp.to_string |> Dune_digest.string)
42+
let of_dependency_formula formula =
43+
match Dependency_formula.has_entries formula with
44+
| false -> None
45+
| true ->
46+
let hashable = formula |> Dependency_formula.to_dyn |> Dyn.to_string in
47+
Some (string hashable)
8348
;;
8449
end
8550

8651
module For_solver = struct
8752
type t =
8853
{ name : Package_name.t
89-
; dependencies : Package_dependency.t list
54+
; dependencies : Dependency_formula.t
9055
; conflicts : Package_dependency.t list
9156
; depopts : Package_dependency.t list
9257
; conflict_class : Package_name.t list
@@ -98,8 +63,7 @@ module For_solver = struct
9863
them *)
9964
OpamFile.OPAM.empty
10065
|> OpamFile.OPAM.with_name (Package_name.to_opam_package_name name)
101-
|> OpamFile.OPAM.with_depends
102-
(Package_dependency.list_to_opam_filtered_formula dependencies)
66+
|> OpamFile.OPAM.with_depends (Dependency_formula.to_filtered_formula dependencies)
10367
|> OpamFile.OPAM.with_conflicts
10468
(Package_dependency.list_to_opam_filtered_formula conflicts)
10569
|> OpamFile.OPAM.with_conflict_class
@@ -108,16 +72,13 @@ module For_solver = struct
10872
(Package_dependency.list_to_opam_filtered_formula depopts)
10973
;;
11074

111-
let opam_filtered_dependency_formula { dependencies; _ } =
112-
Package_dependency.list_to_opam_filtered_formula dependencies
113-
;;
114-
115-
let dependency_set { dependencies; _ } = Dependency_set.of_list dependencies
116-
let list_dependency_set ts = List.map ts ~f:dependency_set |> Dependency_set.union_all
117-
118-
let list_non_local_dependency_set ts =
119-
List.fold_left ts ~init:(list_dependency_set ts) ~f:(fun acc { name; _ } ->
120-
Package_name.Map.remove acc name)
75+
let non_local_dependencies local_deps =
76+
let local_deps_names = Package_name.Set.of_list_map ~f:(fun d -> d.name) local_deps in
77+
let formula =
78+
List.map ~f:(fun { dependencies; _ } -> dependencies) local_deps
79+
|> Dependency_formula.ands
80+
in
81+
Dependency_formula.remove_packages formula local_deps_names
12182
;;
12283
end
12384

@@ -134,9 +95,10 @@ let of_package (t : Dune_lang.Package.t) =
13495
let name = Package.name t in
13596
match Package.original_opam_file t with
13697
| None ->
98+
let dependencies = t |> Package.depends |> Dependency_formula.of_dependencies in
13799
{ name
138100
; version
139-
; dependencies = Package.depends t
101+
; dependencies
140102
; conflicts = Package.conflicts t
141103
; depopts = Package.depopts t
142104
; loc
@@ -148,7 +110,9 @@ let of_package (t : Dune_lang.Package.t) =
148110
Opam_file.read_from_string_exn ~contents:opam_file_string (Path.source file)
149111
in
150112
let convert_filtered_formula = Package_dependency.list_of_opam_filtered_formula loc in
151-
let dependencies = convert_filtered_formula `And (OpamFile.OPAM.depends opam_file) in
113+
let dependencies =
114+
opam_file |> OpamFile.OPAM.depends |> Dependency_formula.of_filtered_formula
115+
in
152116
let conflicts = convert_filtered_formula `And (OpamFile.OPAM.conflicts opam_file) in
153117
let depopts = convert_filtered_formula `Or (OpamFile.OPAM.depopts opam_file) in
154118
let conflict_class =

src/dune_pkg/local_package.mli

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type pins = pin Package_name.Map.t
2020
type t =
2121
{ name : Package_name.t
2222
; version : Package_version.t option
23-
; dependencies : Package_dependency.t list
23+
; dependencies : Dependency_formula.t
2424
; conflicts : Package_dependency.t list
2525
; conflict_class : Package_name.t list
2626
; depopts : Package_dependency.t list
@@ -36,32 +36,14 @@ module Dependency_hash : sig
3636
val to_string : t -> string
3737
val encode : t Encoder.t
3838
val decode : t Decoder.t
39-
end
40-
41-
module Dependency_set : sig
42-
(** A set of dependencies belonging to one or more local packages. Two
43-
different local packages may depend on the same packages with different
44-
version constraints provided that the two constraints intersect
45-
(otherwise there will be no solution). In this case the conjunction of
46-
both constraints will form the constraint associated with that
47-
dependency. Package constraints are de-duplicated by comparing them only
48-
on their syntax. *)
49-
type t
50-
51-
(** Returns a hash of all dependencies in the set or [None] if the set is
52-
empty. The reason for behaving differently when the set is empty is so
53-
that callers are forced to explicitly handle the case where there are no
54-
dependencies which will likely lead to better user experience. *)
55-
val hash : t -> Dependency_hash.t option
56-
57-
val package_dependencies : t -> Package_dependency.t list
39+
val of_dependency_formula : Dependency_formula.t -> t option
5840
end
5941

6042
module For_solver : sig
6143
(** The minimum set of fields about a package needed by the solver. *)
6244
type t =
6345
{ name : Package_name.t
64-
; dependencies : Package_dependency.t list
46+
; dependencies : Dependency_formula.t
6547
; conflicts : Package_dependency.t list
6648
; depopts : Package_dependency.t list
6749
; conflict_class : Package_name.t list
@@ -73,14 +55,7 @@ module For_solver : sig
7355
on disk. *)
7456
val to_opam_file : t -> OpamFile.OPAM.t
7557

76-
(** Returns an opam dependency formula for this package *)
77-
val opam_filtered_dependency_formula : t -> OpamTypes.filtered_formula
78-
79-
(** Returns the set of dependencies of all given local packages excluding
80-
dependencies which are packages in the provided list. Pass this the list
81-
of all local package in a project to get a set of all non-local
82-
dependencies of the project. *)
83-
val list_non_local_dependency_set : t list -> Dependency_set.t
58+
val non_local_dependencies : t list -> Dependency_formula.t
8459
end
8560

8661
val for_solver : t -> For_solver.t

src/dune_pkg/lock_dir.ml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,9 @@ let create_latest_version
380380
|> Code_error.raise "Invalid package table");
381381
let version = Syntax.greatest_supported_version_exn Dune_lang.Pkg.syntax in
382382
let dependency_hash =
383-
Local_package.(
384-
For_solver.list_non_local_dependency_set local_packages |> Dependency_set.hash)
383+
local_packages
384+
|> Local_package.For_solver.non_local_dependencies
385+
|> Local_package.Dependency_hash.of_dependency_formula
385386
|> Option.map ~f:(fun dependency_hash -> Loc.none, dependency_hash)
386387
in
387388
let complete, used =

src/dune_pkg/opam_solver.ml

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -783,8 +783,21 @@ let reject_unreachable_packages =
783783
loop roots;
784784
!seen
785785
in
786-
fun ~local_packages ~pkgs_by_name ->
786+
fun solver_env ~local_packages ~pkgs_by_name ->
787787
let roots = Package_name.Map.keys local_packages in
788+
let pkgs_by_version =
789+
Package_name.Map.merge pkgs_by_name local_packages ~f:(fun name lhs rhs ->
790+
match lhs, rhs with
791+
| None, None -> assert false
792+
| Some _, Some _ ->
793+
Code_error.raise
794+
"package is both local and returned by solver"
795+
[ "name", Package_name.to_dyn name ]
796+
| Some (lock_dir_pkg : Lock_dir.Pkg.t), None -> Some lock_dir_pkg.info.version
797+
| None, Some _pkg ->
798+
let version = Package_version.of_string "dev" in
799+
Some version)
800+
in
788801
let pkgs_by_name =
789802
Package_name.Map.merge pkgs_by_name local_packages ~f:(fun name lhs rhs ->
790803
match lhs, rhs with
@@ -795,8 +808,28 @@ let reject_unreachable_packages =
795808
[ "name", Package_name.to_dyn name ]
796809
| Some (pkg : Lock_dir.Pkg.t), None -> Some (List.map pkg.depends ~f:snd)
797810
| None, Some (pkg : Local_package.For_solver.t) ->
811+
let formula = pkg.dependencies |> Dependency_formula.to_filtered_formula in
812+
(* Use `dev` because at this point we don't have any version *)
813+
let opam_package =
814+
OpamPackage.of_string (sprintf "%s.dev" (Package_name.to_string pkg.name))
815+
in
816+
let env = add_self_to_filter_env opam_package (Solver_env.to_env solver_env) in
817+
let resolved =
818+
Resolve_opam_formula.filtered_formula_to_package_names
819+
env
820+
~with_test:true
821+
pkgs_by_version
822+
formula
823+
in
798824
let deps =
799-
List.map pkg.dependencies ~f:(fun (d : Package_dependency.t) -> d.name)
825+
match resolved with
826+
| Ok { regular; post = _ } ->
827+
(* discard post deps *)
828+
regular
829+
| Error _ ->
830+
Code_error.raise
831+
"can't find a valid solution for the dependencies"
832+
[ "name", Package_name.to_dyn pkg.name ]
800833
in
801834
let depopts =
802835
List.filter_map pkg.depopts ~f:(fun (d : Package_dependency.t) ->
@@ -912,7 +945,9 @@ let solve_lock_dir
912945
(Package_name.to_string name)
913946
(Package_name.to_string dep_name)
914947
]));
915-
let reachable = reject_unreachable_packages ~local_packages ~pkgs_by_name in
948+
let reachable =
949+
reject_unreachable_packages solver_env ~local_packages ~pkgs_by_name
950+
in
916951
let pkgs_by_name =
917952
Package_name.Map.filteri pkgs_by_name ~f:(fun name _ ->
918953
Package_name.Set.mem reachable name)

0 commit comments

Comments
 (0)