From 62d1413a1ed3cbb7d91abf70d5429abd458742ec Mon Sep 17 00:00:00 2001 From: Stephen Sherratt Date: Wed, 9 Aug 2023 14:06:48 +1000 Subject: [PATCH 1/2] refactor: Move Constraint out of Package Signed-off-by: Stephen Sherratt --- src/dune_rules/opam_create.ml | 6 +- src/dune_rules/package.ml | 189 ++++++++++++++++------------------ src/dune_rules/package.mli | 24 ++--- 3 files changed, 105 insertions(+), 114 deletions(-) diff --git a/src/dune_rules/opam_create.ml b/src/dune_rules/opam_create.ml index 6ca1c5b8d57..eb28109f099 100644 --- a/src/dune_rules/opam_create.ml +++ b/src/dune_rules/opam_create.ml @@ -155,7 +155,7 @@ let dune_name = Package.Name.of_string "dune" let odoc_name = Package.Name.of_string "odoc" let insert_dune_dep depends dune_version = - let constraint_ : Package.Dependency.Constraint.t = + let constraint_ : Package.Constraint.t = let dune_version = Dune_lang.Syntax.Version.to_string dune_version in Uop (Gte, String_literal dune_version) in @@ -186,7 +186,7 @@ let insert_dune_dep depends dune_version = loop [] depends ;; -let rec already_requires_odoc : Package.Dependency.Constraint.t -> bool = function +let rec already_requires_odoc : Package.Constraint.t -> bool = function | Bvar { name = "with-doc" | "build" | "post" } | Uop _ | Bop _ -> true | Bvar _ -> false | And l -> List.for_all ~f:already_requires_odoc l @@ -194,7 +194,7 @@ let rec already_requires_odoc : Package.Dependency.Constraint.t -> bool = functi ;; let insert_odoc_dep depends = - let with_doc : Package.Dependency.Constraint.t = Bvar { name = "with-doc" } in + let with_doc : Package.Constraint.t = Bvar { name = "with-doc" } in let odoc_dep = { Package.Dependency.name = odoc_name; constraint_ = Some with_doc } in let rec loop acc = function | [] -> List.rev (odoc_dep :: acc) diff --git a/src/dune_rules/package.ml b/src/dune_rules/package.ml index 3c496b0538f..095d54146b2 100644 --- a/src/dune_rules/package.ml +++ b/src/dune_rules/package.ml @@ -58,101 +58,64 @@ module Id = struct module Map = C.Map end -module Dependency = struct - let nopos pelem = { OpamParserTypes.FullPos.pelem; pos = Opam_file.nopos } - - module Constraint = struct - include Dune_lang.Package_constraint - - module Op = struct - include Op - - let to_relop = function - | Eq -> nopos `Eq - | Gte -> nopos `Geq - | Lte -> nopos `Leq - | Gt -> nopos `Gt - | Lt -> nopos `Lt - | Neq -> nopos `Neq - ;; - - let to_relop_pelem op = - let ({ pelem; _ } : OpamParserTypes.FullPos.relop) = to_relop op in - pelem - ;; - end - - module Variable = struct - include Variable - - let to_opam { name } = nopos (OpamParserTypes.FullPos.Ident name) - - let to_opam_filter { name } = - OpamTypes.FIdent ([], OpamVariable.of_string name, None) - ;; - end - - module Value = struct - include Value - - let to_opam v = - match v with - | String_literal x -> nopos (OpamParserTypes.FullPos.String x) - | Variable variable -> Variable.to_opam variable - ;; - - let to_opam_filter = function - | String_literal literal -> OpamTypes.FString literal - | Variable variable -> Variable.to_opam_filter variable - ;; - end - - let rec to_opam_condition = function - | Bvar variable -> - OpamTypes.Atom (OpamTypes.Filter (Variable.to_opam_filter variable)) - | Uop (op, value) -> - OpamTypes.Atom - (OpamTypes.Constraint (Op.to_relop_pelem op, Value.to_opam_filter value)) - | Bop (op, lhs, rhs) -> - OpamTypes.Atom - (OpamTypes.Filter - (OpamTypes.FOp - (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) +let nopos pelem = { OpamParserTypes.FullPos.pelem; pos = Opam_file.nopos } + +module Constraint = struct + include Dune_lang.Package_constraint + + module Op = struct + include Op + + let to_relop = function + | Eq -> nopos `Eq + | Gte -> nopos `Geq + | Lte -> nopos `Leq + | Gt -> nopos `Gt + | Lt -> nopos `Lt + | Neq -> nopos `Neq + ;; + + let to_relop_pelem op = + let ({ pelem; _ } : OpamParserTypes.FullPos.relop) = to_relop op in + pelem ;; end - type t = - { name : Name.t - ; constraint_ : Constraint.t option - } + module Variable = struct + include Variable - let encode { name; constraint_ } = - let open Dune_sexp.Encoder in - match constraint_ with - | None -> Name.encode name - | Some c -> pair Name.encode Constraint.encode (name, c) - ;; + let to_opam { name } = nopos (OpamParserTypes.FullPos.Ident name) + let to_opam_filter { name } = OpamTypes.FIdent ([], OpamVariable.of_string name, None) + end - let decode = - let open Dune_sexp.Decoder in - let constrained = - let+ name = Name.decode - and+ expr = Constraint.decode in - { name; constraint_ = Some expr } - in - enter constrained - <|> let+ name = Name.decode in - { name; constraint_ = None } - ;; + module Value = struct + include Value - let to_dyn { name; constraint_ } = - let open Dyn in - record - [ "name", Name.to_dyn name - ; "constr", Dyn.Option (Option.map ~f:Constraint.to_dyn constraint_) - ] + let to_opam v = + match v with + | String_literal x -> nopos (OpamParserTypes.FullPos.String x) + | Variable variable -> Variable.to_opam variable + ;; + + let to_opam_filter = function + | String_literal literal -> OpamTypes.FString literal + | Variable variable -> Variable.to_opam_filter variable + ;; + end + + let rec to_opam_condition = function + | Bvar variable -> + OpamTypes.Atom (OpamTypes.Filter (Variable.to_opam_filter variable)) + | Uop (op, value) -> + OpamTypes.Atom + (OpamTypes.Constraint (Op.to_relop_pelem op, Value.to_opam_filter value)) + | Bop (op, lhs, rhs) -> + OpamTypes.Atom + (OpamTypes.Filter + (OpamTypes.FOp + (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) ;; type context = @@ -178,15 +141,9 @@ module Dependency = struct let opam_constraint t : OpamParserTypes.FullPos.value = let open OpamParserTypes.FullPos in let rec opam_constraint context = function - | Constraint.Bvar v -> Constraint.Variable.to_opam v - | Uop (op, x) -> - nopos (Prefix_relop (Constraint.Op.to_relop op, Constraint.Value.to_opam x)) - | Bop (op, x, y) -> - nopos - (Relop - ( Constraint.Op.to_relop op - , Constraint.Value.to_opam x - , Constraint.Value.to_opam y )) + | Bvar v -> Variable.to_opam v + | Uop (op, x) -> nopos (Prefix_relop (Op.to_relop op, Value.to_opam x)) + | Bop (op, x, y) -> nopos (Relop (Op.to_relop op, Value.to_opam x, Value.to_opam y)) | And cs -> logical_op `And cs ~inner_ctx:Ctx_and ~group_needed:false | Or cs -> let group_needed = @@ -201,9 +158,43 @@ module Dependency = struct in opam_constraint Root t ;; +end + +module Dependency = struct + type t = + { name : Name.t + ; constraint_ : Constraint.t option + } + + let encode { name; constraint_ } = + let open Dune_sexp.Encoder in + match constraint_ with + | None -> Name.encode name + | Some c -> pair Name.encode Constraint.encode (name, c) + ;; + + let decode = + let open Dune_sexp.Decoder in + let constrained = + let+ name = Name.decode + and+ expr = Constraint.decode in + { name; constraint_ = Some expr } + in + enter constrained + <|> let+ name = Name.decode in + { name; constraint_ = None } + ;; + + let to_dyn { name; constraint_ } = + let open Dyn in + record + [ "name", Name.to_dyn name + ; "constr", Dyn.Option (Option.map ~f:Constraint.to_dyn constraint_) + ] + ;; let opam_depend { name; constraint_ } = - let constraint_ = Option.map ~f:opam_constraint constraint_ in + let constraint_ = Option.map ~f:Constraint.opam_constraint constraint_ in let pkg = nopos (OpamParserTypes.FullPos.String (Name.to_string name)) in match constraint_ with | None -> pkg diff --git a/src/dune_rules/package.mli b/src/dune_rules/package.mli index fa28491f4b9..6c67a56b5f0 100644 --- a/src/dune_rules/package.mli +++ b/src/dune_rules/package.mli @@ -28,23 +28,23 @@ module Id : sig include Comparable_intf.S with type key := t end -module Dependency : sig - module Constraint : sig - module Op : sig - type t = Dune_lang.Package_constraint.Op.t +module Constraint : sig + module Op : sig + type t = Dune_lang.Package_constraint.Op.t - val to_relop : t -> OpamParserTypes.FullPos.relop - end + val to_relop : t -> OpamParserTypes.FullPos.relop + end - module Value : sig - type t = Dune_lang.Package_constraint.Value.t - end + module Value : sig + type t = Dune_lang.Package_constraint.Value.t + end - type t = Dune_lang.Package_constraint.t + type t = Dune_lang.Package_constraint.t - val to_dyn : t -> Dyn.t - end + val to_dyn : t -> Dyn.t +end +module Dependency : sig type t = { name : Name.t ; constraint_ : Constraint.t option From ef2744340fb048b4ac971b8c4a2145627428bad6 Mon Sep 17 00:00:00 2001 From: Stephen Sherratt Date: Wed, 9 Aug 2023 14:39:54 +1000 Subject: [PATCH 2/2] Add `available` keyword to package spec This generates the corresponding `available` field when generating the opam file for a package. Signed-off-by: Stephen Sherratt --- src/dune_rules/opam_create.ml | 10 +++- src/dune_rules/package.ml | 12 +++++ src/dune_rules/package.mli | 2 + .../test-cases/opam-package-available-field.t | 51 +++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 test/blackbox-tests/test-cases/opam-package-available-field.t diff --git a/src/dune_rules/opam_create.ml b/src/dune_rules/opam_create.ml index eb28109f099..13dcd871afd 100644 --- a/src/dune_rules/opam_create.ml +++ b/src/dune_rules/opam_create.ml @@ -114,6 +114,7 @@ let package_fields ; depends ; conflicts ; depopts + ; available ; info = _ ; id = _ ; version = _ @@ -143,7 +144,14 @@ let package_fields | [] -> None | _ :: _ -> Some (k, list Package.Dependency.opam_depend v)) in - let fields = [ optional; dep_fields ] in + let available_field = + match available with + | None -> [] + | Some constraint_ -> + let opam_constraint = Package.Constraint.opam_constraint constraint_ in + [ "available", opam_constraint ] + in + let fields = [ optional; dep_fields; available_field ] in let fields = let dune_version = Dune_project.dune_version project in if dune_version >= (2, 0) && tags <> [] then tags :: fields else fields diff --git a/src/dune_rules/package.ml b/src/dune_rules/package.ml index 095d54146b2..95fa4c51113 100644 --- a/src/dune_rules/package.ml +++ b/src/dune_rules/package.ml @@ -500,6 +500,7 @@ type t = ; depends : Dependency.t list ; conflicts : Dependency.t list ; depopts : Dependency.t list + ; available : Constraint.t option ; info : Info.t ; version : string option ; has_opam_file : opam_file @@ -530,6 +531,7 @@ let encode ; depends ; conflicts ; depopts + ; available ; info ; version ; tags @@ -549,6 +551,7 @@ let encode ; field_l "depends" Dependency.encode depends ; field_l "conflicts" Dependency.encode conflicts ; field_l "depopts" Dependency.encode depopts + ; field_o "available" Constraint.encode available ; field_o "version" string version ; field "tags" (list string) ~default:[] tags ; field_l @@ -586,6 +589,10 @@ let decode ~dir = and+ depends = field ~default:[] "depends" (repeat Dependency.decode) and+ conflicts = field ~default:[] "conflicts" (repeat Dependency.decode) and+ depopts = field ~default:[] "depopts" (repeat Dependency.decode) + and+ available = + field_o + "available" + (Dune_lang.Syntax.since Stanza.syntax (3, 11) >>> Constraint.decode) and+ info = Info.decode ~since:(2, 0) () and+ tags = field "tags" (enter (repeat string)) ~default:[] and+ deprecated_package_names = @@ -619,6 +626,7 @@ let decode ~dir = ; depends ; conflicts ; depopts + ; available ; info ; version ; has_opam_file = Exists false @@ -645,6 +653,7 @@ let to_dyn ; depends ; conflicts ; depopts + ; available ; info ; has_opam_file ; tags @@ -663,6 +672,7 @@ let to_dyn ; "depends", list Dependency.to_dyn depends ; "conflicts", list Dependency.to_dyn conflicts ; "depopts", list Dependency.to_dyn depopts + ; "available", option Constraint.to_dyn available ; "info", Info.to_dyn info ; "has_opam_file", dyn_of_opam_file has_opam_file ; "tags", list string tags @@ -694,6 +704,7 @@ let default name dir = ; conflicts = [] ; info = Info.empty ; depopts = [] + ; available = None ; has_opam_file = Exists false ; tags = [ "topics"; "to describe"; "your"; "project" ] ; deprecated_package_names = Name.Map.empty @@ -758,6 +769,7 @@ let load_opam_file file name = ; conflicts = [] ; depends = [] ; depopts = [] + ; available = None ; info = { maintainers = get_many "maintainer" ; authors = get_many "authors" diff --git a/src/dune_rules/package.mli b/src/dune_rules/package.mli index 6c67a56b5f0..0f21900c46e 100644 --- a/src/dune_rules/package.mli +++ b/src/dune_rules/package.mli @@ -42,6 +42,7 @@ module Constraint : sig type t = Dune_lang.Package_constraint.t val to_dyn : t -> Dyn.t + val opam_constraint : t -> OpamParserTypes.FullPos.value end module Dependency : sig @@ -120,6 +121,7 @@ type t = ; depends : Dependency.t list ; conflicts : Dependency.t list ; depopts : Dependency.t list + ; available : Constraint.t option ; info : Info.t ; version : string option ; has_opam_file : opam_file diff --git a/test/blackbox-tests/test-cases/opam-package-available-field.t b/test/blackbox-tests/test-cases/opam-package-available-field.t new file mode 100644 index 00000000000..3b888044564 --- /dev/null +++ b/test/blackbox-tests/test-cases/opam-package-available-field.t @@ -0,0 +1,51 @@ +Tests for generating opam files with the "available" field. + +Feature is only available in 3.11 and later. + $ cat > dune-project << EOF + > (lang dune 3.10) + > (generate_opam_files true) + > (package + > (name foo) + > (available (= :os linux))) + > EOF + + $ dune build @check + File "dune-project", line 5, characters 1-26: + 5 | (available (= :os linux))) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + Error: 'available' is only available since version 3.11 of the dune language. + Please update your dune-project file to have (lang dune 3.11). + [1] + + $ cat > dune-project << EOF + > (lang dune 3.11) + > (generate_opam_files true) + > (package + > (name foo) + > (available (and (<> :os linux) (or (= :arch x86_64) (= :arch arm64))))) + > EOF + + $ dune build @check + + $ cat foo.opam + # This file is generated by dune, edit dune-project instead + opam-version: "2.0" + depends: [ + "dune" {>= "3.11"} + "odoc" {with-doc} + ] + available: os != "linux" & (arch = "x86_64" | arch = "arm64") + build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] + ]