opam icon indicating copy to clipboard operation
opam copied to clipboard

Mitigate DNF explosion for depends field with disjoint disjunctions

Open dra27 opened this issue 2 months ago • 1 comments

I had a depends field enforcing an invariant for an little-known experimental branch of OCaml:

depends: [
  "conf-autoconf"         {build}
  "conf-which"            {build}
  "ocaml"                 {= "5.2.0" & post}
  "base-unix"             {post}
  "base-bigarray"         {post}
  "base-threads"          {post}
  "base-domains"          {post}
  "base-nnp"              {post}
  "ocaml-options-vanilla" {post}
  ("oxcaml-no-alcotest" {tpost} | "oxcaml-alcotest" {post})
  ("oxcaml-no-backoff" {post} | "oxcaml-backoff" {post})
  ("oxcaml-no-ctypes" {post} | "oxcaml-ctypes" {post})
  ("oxcaml-no-ctypes-foreign" {post} | "oxcaml-ctypes-foreign" {post})
  ("oxcaml-no-dot-merlin-reader" {post} | "oxcaml-dot-merlin-reader" {post})
  ("oxcaml-no-dune" {post} | "oxcaml-dune" {post})
  ("oxcaml-no-gen_js_api" {post} | "oxcaml-gen_js_api" {post})
  ("oxcaml-no-js_of_ocaml" {post} | "oxcaml-js_of_ocaml" {post})
  ("oxcaml-no-js_of_ocaml-compiler" {post} | "oxcaml-js_of_ocaml-compiler" {post})
  ("oxcaml-no-js_of_ocaml-ppx" {post} | "oxcaml-js_of_ocaml-ppx" {post})
  ("oxcaml-no-js_of_ocaml-toplevel" {post} | "oxcaml-js_of_ocaml-toplevel" {post})
  ("oxcaml-no-jsonrpc" {post} | "oxcaml-jsonrpc" {post})
  ("oxcaml-no-lsp" {post} | "oxcaml-lsp" {post})
  ("oxcaml-no-lwt_ppx" {post} | "oxcaml-lwt_ppx" {post})
  ("oxcaml-no-mdx" {post} | "oxcaml-mdx" {post})
  ("oxcaml-no-merlin" {post} | "oxcaml-merlin" {post})
  ("oxcaml-no-merlin-lib" {post} | "oxcaml-merlin-lib" {post})
  ("oxcaml-no-notty-community" {post} | "oxcaml-notty-community" {post})
  ("oxcaml-no-ocaml-compiler-libs" {post} | "oxcaml-ocaml-compiler-libs" {post})
  ("oxcaml-no-ocaml-index" {post} | "oxcaml-ocaml-index" {post})
  ("oxcaml-no-ocaml-lsp-server" {post} | "oxcaml-ocaml-lsp-server" {post})
  ("oxcaml-no-ocamlbuild" {post} | "oxcaml-ocamlbuild" {post})
  ("oxcaml-no-ocamlformat" {post} | "oxcaml-ocamlformat" {post})
  ("oxcaml-no-ocamlformat-lib" {post} | "oxcaml-ocamlformat-lib" {post})
  ("oxcaml-no-ojs" {post} | "oxcaml-ojs" {post})
  ("oxcaml-no-ppxlib" {post} | "oxcaml-ppxlib" {post})
  ("oxcaml-no-ppxlib_ast" {post} | "oxcaml-ppxlib_ast" {post})
  ("oxcaml-no-re" {post} | "oxcaml-re" {post})
  ("oxcaml-no-sedlex" {post} | "oxcaml-sedlex" {post})
  ("oxcaml-no-spawn" {post} | "oxcaml-spawn" {post})
  ("oxcaml-no-topkg" {post} | "oxcaml-topkg" {post})
  ("oxcaml-no-utop" {post} | "oxcaml-utop" {post})
  ("oxcaml-no-uutf" {post} | "oxcaml-uutf" {post})
  ("oxcaml-no-wasm_of_ocaml-compiler" {post} | "oxcaml-wasm_of_ocaml-compiler" {post})
  ("oxcaml-no-zarith" {post} | "oxcaml-zarith" {post})
]

opam switch create oxcaml ocaml-variants.5.2.0+ox completely falls over with this. The problem is the conversion to DNF which takes place in OpamFormula.packages.

While it's possible to re-encode this formula across more packages (which is the workaround I'm using), the conversion to DNF followed by filtering is sub-optimal.

The observation here is that in (X1 | X2) & (Y1 | Y2), there's no need to consider Y1 | Y2 if the atoms of Y1 | Y2 are distinct from the atoms of X1 | X2 combined (hopefully correctly) with the observation that if the entire sub-formula Y1 | Y2 doesn't refer to the specific package being filtered, then we don't need consider it when converting to DNF.

In example above, that means, say, that when considering which versions of the ocaml package to consider, all the disjunctions are eliminated, because the conversion to DNF cannot introduce formula of interest.

dra27 avatar Dec 08 '25 12:12 dra27

(original code is in #2968)

dra27 avatar Dec 08 '25 13:12 dra27