diff --git a/doc/changes/11404.md b/doc/changes/11404.md new file mode 100644 index 00000000000..ddfe1d2522e --- /dev/null +++ b/doc/changes/11404.md @@ -0,0 +1 @@ +- Support `not` in package dependencies constraints (#11404, @art-w, reported by @hannesm) diff --git a/src/dune_lang/package_constraint.ml b/src/dune_lang/package_constraint.ml index f475cd66ede..8349ba10c5e 100644 --- a/src/dune_lang/package_constraint.ml +++ b/src/dune_lang/package_constraint.ml @@ -39,6 +39,7 @@ module T = struct | Bop of Relop.t * Value.t * Value.t | And of t list | Or of t list + | Not of t let rec to_dyn = let open Dyn in @@ -48,6 +49,7 @@ module T = struct | Bop (b, x, y) -> variant "Bop" [ Relop.to_dyn b; Value.to_dyn x; Value.to_dyn y ] | And t -> variant "And" (List.map ~f:to_dyn t) | Or t -> variant "Or" (List.map ~f:to_dyn t) + | Not t -> variant "Not" [ to_dyn t ] ;; let rec compare a b = @@ -71,6 +73,9 @@ module T = struct | And _, _ -> Lt | _, And _ -> Gt | Or a, Or b -> List.compare a b ~compare + | Or _, _ -> Lt + | _, Or _ -> Gt + | Not a, Not b -> compare a b ;; end @@ -85,6 +90,7 @@ let rec encode c = | Bop (op, x, y) -> triple Relop.encode Value.encode Value.encode (op, x, y) | And conjuncts -> list sexp (string "and" :: List.map ~f:encode conjuncts) | Or disjuncts -> list sexp (string "or" :: List.map ~f:encode disjuncts) + | Not x -> list sexp [ string "not"; encode x ] ;; let logical_op t = @@ -138,6 +144,10 @@ let decode = ; ( "or" , let+ x = logical_op t in Or x ) + ; ( "not" + , let+ x = t + and+ () = Dune_sexp.Syntax.since Stanza.syntax (3, 18) ~what:"Not operator" in + Not x ) ] in peek_exn diff --git a/src/dune_lang/package_constraint.mli b/src/dune_lang/package_constraint.mli index 1e75528fb68..c54704076ea 100644 --- a/src/dune_lang/package_constraint.mli +++ b/src/dune_lang/package_constraint.mli @@ -23,6 +23,7 @@ type t = (** A binary operator applied to LHS and RHS values *) | And of t list (** The conjunction of a list of boolean expressions *) | Or of t list (** The disjunction of a list of boolean expressions *) + | Not of t (** The negation of a boolean expression *) val encode : t Dune_sexp.Encoder.t val decode : t Dune_sexp.Decoder.t diff --git a/src/dune_pkg/package_dependency.ml b/src/dune_pkg/package_dependency.ml index ad0baa0a577..0bdd1f90f12 100644 --- a/src/dune_pkg/package_dependency.ml +++ b/src/dune_pkg/package_dependency.ml @@ -85,6 +85,12 @@ module Constraint = struct (Value.to_opam_filter lhs, Op.to_relop_pelem op, Value.to_opam_filter rhs))) | And conjunction -> OpamFormula.ands (List.map conjunction ~f:to_opam_condition) | Or disjunction -> OpamFormula.ors (List.map disjunction ~f:to_opam_condition) + | Not constraint_ -> + OpamFormula.neg + (function + | OpamTypes.Constraint (op, v) -> Constraint (OpamFormula.neg_relop op, v) + | Filter f -> Filter (FNot f)) + (to_opam_condition constraint_) ;; let rec of_opam_filter (filter : OpamTypes.filter) = @@ -104,6 +110,9 @@ module Constraint = struct let+ lhs = of_opam_filter lhs and+ rhs = of_opam_filter rhs in Or [ lhs; rhs ] + | FNot constraint_ -> + let+ constraint_ = of_opam_filter constraint_ in + Not constraint_ | _ -> Error (Convert_from_opam_error.Can't_convert_opam_filter_to_condition filter) ;; @@ -138,6 +147,7 @@ type context = | Root | Ctx_and | Ctx_or + | Ctx_not (* The printer in opam-file-format does not insert parentheses on its own, but it is possible to use the [Group] constructor with a singleton to @@ -165,15 +175,27 @@ let opam_constraint t : OpamParserTypes.FullPos.value = ( nopos @@ Constraint.Op.to_opam op , Constraint.Value.to_opam x , Constraint.Value.to_opam y )) - | And cs -> logical_op `And cs ~inner_ctx:Ctx_and ~group_needed:false + | And cs -> + let group_needed = + match context with + | Root -> false + | Ctx_and -> false + | Ctx_or -> false + | Ctx_not -> true + in + logical_op `And cs ~inner_ctx:Ctx_and ~group_needed | Or cs -> let group_needed = match context with | Root -> false | Ctx_and -> true | Ctx_or -> false + | Ctx_not -> true in logical_op `Or cs ~inner_ctx:Ctx_or ~group_needed + | Not c -> + let _c = opam_constraint Ctx_not c in + nopos (Pfxop (nopos `Not, _c)) and logical_op op cs ~inner_ctx ~group_needed = List.map cs ~f:(opam_constraint inner_ctx) |> op_list op |> group_if group_needed in diff --git a/src/dune_rules/opam_create.ml b/src/dune_rules/opam_create.ml index 28b4d8c8f27..8f9205326fc 100644 --- a/src/dune_rules/opam_create.ml +++ b/src/dune_rules/opam_create.ml @@ -179,6 +179,7 @@ let rec already_requires_odoc : Package_constraint.t -> bool = function | Bvar var -> Dune_lang.Package_variable_name.(one_of var [ with_doc; build; post ]) | And l -> List.for_all ~f:already_requires_odoc l | Or l -> List.exists ~f:already_requires_odoc l + | Not t -> not (already_requires_odoc t) ;; let insert_odoc_dep depends = diff --git a/test/blackbox-tests/test-cases/dune-project-meta/binops.t b/test/blackbox-tests/test-cases/dune-project-meta/binops.t deleted file mode 100644 index 7f8265e369a..00000000000 --- a/test/blackbox-tests/test-cases/dune-project-meta/binops.t +++ /dev/null @@ -1,36 +0,0 @@ -Using binary operators for dependencies ---------------------------------------- - -Not supported before 2.1: - - $ cat > dune-project < (lang dune 2.0) - > (name foo) - > (generate_opam_files true) - > (package - > (name foo) - > (depends (conf-libX11 (<> :os win32)))) - > EOF - - $ dune build @install - File "dune-project", line 6, characters 23-37: - 6 | (depends (conf-libX11 (<> :os win32)))) - ^^^^^^^^^^^^^^ - Error: Passing two arguments to <> is only available since version 2.1 of the - dune language. Please update your dune-project file to have (lang dune 2.1). - [1] - -Supported since 2.1: - - $ cat > dune-project < (lang dune 2.1) - > (name foo) - > (generate_opam_files true) - > (package - > (name foo) - > (depends (conf-libX11 (<> :os win32)))) - > EOF - - $ dune build @install - $ grep conf-libX11 foo.opam - "conf-libX11" {os != "win32"} diff --git a/test/blackbox-tests/test-cases/dune-project-meta/operators.t b/test/blackbox-tests/test-cases/dune-project-meta/operators.t new file mode 100644 index 00000000000..a06fc05e3db --- /dev/null +++ b/test/blackbox-tests/test-cases/dune-project-meta/operators.t @@ -0,0 +1,107 @@ +Using binary operators for dependencies +--------------------------------------- + +Not supported before 2.1: + + $ cat > dune-project < (lang dune 2.0) + > (name foo) + > (generate_opam_files true) + > (package + > (name foo) + > (depends (conf-libX11 (<> :os win32)))) + > EOF + + $ dune build @install + File "dune-project", line 6, characters 23-37: + 6 | (depends (conf-libX11 (<> :os win32)))) + ^^^^^^^^^^^^^^ + Error: Passing two arguments to <> is only available since version 2.1 of the + dune language. Please update your dune-project file to have (lang dune 2.1). + [1] + +Supported since 2.1: + + $ cat > dune-project < (lang dune 2.1) + > (name foo) + > (generate_opam_files true) + > (package + > (name foo) + > (depends (conf-libX11 (<> :os win32)))) + > EOF + + $ dune build @install + $ grep conf-libX11 foo.opam + "conf-libX11" {os != "win32"} + + +Using negation operator for dependencies +---------------------------------------- + +Not supported before 3.18: + + $ cat > dune-project < (lang dune 3.17) + > (generate_opam_files) + > (package + > (name foo) + > (allow_empty) + > (depends + > (ocp-indent + > (not :with-test)))) + > EOF + $ dune build + File "dune-project", line 8, characters 4-20: + 8 | (not :with-test)))) + ^^^^^^^^^^^^^^^^ + Error: Not operator is only available since version 3.18 of the dune + language. Please update your dune-project file to have (lang dune 3.18). + [1] + +Supported since 3.18: + + $ cat > dune-project < (lang dune 3.18) + > (generate_opam_files) + > (package + > (name foo) + > (allow_empty) + > (depends + > (ocp-indent + > (not + > (or + > (and + > (not :with-test) + > (>= 1.0)) + > (not + > (and + > (not (= :os win32)) + > (not (>= 1.5))))))) + > (not (>= 2.0)))) + > EOF + $ dune build + $ cat foo.opam + # This file is generated by dune, edit dune-project instead + opam-version: "2.0" + depends: [ + "dune" {>= "3.18"} + "ocp-indent" {!(!with-test & >= "1.0" | !(!os = "win32" & !>= "1.5"))} + "not" {>= "2.0"} + "odoc" {with-doc} + ] + build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] + ] + x-maintenance-intent: ["(latest)"] diff --git a/test/blackbox-tests/test-cases/pkg/additional-constraints.t b/test/blackbox-tests/test-cases/pkg/additional-constraints.t index 3fc4e6b61cf..61005722122 100644 --- a/test/blackbox-tests/test-cases/pkg/additional-constraints.t +++ b/test/blackbox-tests/test-cases/pkg/additional-constraints.t @@ -30,3 +30,46 @@ Notice that the constraints field doesn't introduce additional packages. The Solution for dune.lock: - bar.1.0.0 - foo.1.0.0 + +Constraint negation is supported since 3.18: + + $ cat >dune-workspace < (lang dune 3.18) + > (lock_dir + > (constraints doesnotexist (foo (not (= 1.0.0))) (bar (not (= 1.0.0)))) + > (repositories mock)) + > (repository + > (name mock) + > (url "file://$(pwd)/mock-opam-repository")) + > EOF + +There are no valid version of foo at the moment: + + $ solve_project < (lang dune 3.18) + > (package + > (name x) + > (depends foo bar)) + > EOF + Error: Unable to solve dependencies for the following lock directories: + Lock directory dune.lock: + Couldn't solve the package dependency formula. + Selected candidates: bar.1.9.1 x.dev + - foo -> (problem) + No usable implementations: + foo.1.0.0: Package does not satisfy constraints of local package x + [1] + +If we add one: + + $ mkpkg foo 0.9.0 + + $ solve_project < (lang dune 3.18) + > (package + > (name x) + > (depends foo bar)) + > EOF + Solution for dune.lock: + - bar.1.9.1 + - foo.0.9.0 diff --git a/test/blackbox-tests/test-cases/pkg/opam-solver-or.t b/test/blackbox-tests/test-cases/pkg/opam-solver-or.t index f2395d7c7c9..6521f5c713a 100644 --- a/test/blackbox-tests/test-cases/pkg/opam-solver-or.t +++ b/test/blackbox-tests/test-cases/pkg/opam-solver-or.t @@ -54,6 +54,17 @@ which is completely omitted from the solution). - a2.0.0.2 - b.0.0.2 +Same solution if a1 only known version is excluded: + + $ mkpkg b 0.0.2 < depends: [ "a1" {!= "0.0.1" } | "a2" {= "0.0.2" } ] + > EOF + + $ solve b + Solution for dune.lock: + - a2.0.0.2 + - b.0.0.2 + Update a2.0.0.2 marking it as avoid-version which should tell the solver to try to find a solution which doesn't include it. @@ -65,3 +76,4 @@ $ mkpkg a2 0.0.2 <