Install dependency if a package has a specific version in opam

458 Views Asked by At

I want to specify that my opam package depends on camlp-streams if the ocaml package version is higher than 4.14.0. In my opam file I put the following line:

depends: [
  "ocaml" {>= "4.07.0"}
  "dune" {build & >= "2.8.0"}
  "camlp-streams" {>= "5.0" & "ocaml" >= "4.14.0" }
]

But even if ocaml version is lower than 4.14.0, when I opam install --deps-only package.opam it will install camlp-streams.

Is there a way to tell opam to not install a package when another package is present with a specific version? Since I don't know how to tell opam to show me how it resolved my constraints, it's a bit hard to debug my opam file.

2

There are 2 best solutions below

3
On BEST ANSWER

The "ocaml" >= "4.14.0" expression just compares the string "ocaml" with the string "4.14.0" using the Debian Version Ordering. Since "ocaml" is greater than "4.14.0", it always evaluates to true, therefore the dependency is always installed.

The depends field specifies the dependencies using the filtered package formulas. A filtered package formula allows us to specify both filters and package version constraints. The formula is evaluated in two stages. In the first stage, all filters in the formula are evaluated and those packages whose filter expressions were evaluated to true are removed from the list. After the first stage, the filter expressions are removed from the formula and the resulting package formula that contains only the version constraints is passed to the constraint solver.

For example,

depends: [
  "linux-support-package" {>= "4.0.0" & os = "linux"}
]

Will install the linux-support-package only on the Linux operating systems. In the first stage if the global variable os is equal to linux the filter will evaluate to true and yield the final package formula {>= "4.0.0" & os = "linux"}.

Note that the variables in the filters are not quoted and only global and switch variables (see opam var for the list of such) could be used in the filter. Unfortunately, there's no global or switch variable for the OCaml version as it is considered just a package. Besides, if it were allowed to refer to a package in the filter expression of the depends field, then the correct syntax would be ocaml.version >= "4.14.0".

Now, when we're equipped with this knowledge, what options do we have? The first option, referenced in your own answer would be using a disjunction of mutually exclusive formulas. It will work but with a few caveats. The first is that if your disjunction is not exclusive, i.e., there's some set of packages that satisfies both clauses then the solver will pick an arbitrary set in a non-deterministic way, which is definitely not what you want! When the formula grows it becomes very hard to maintain the exclusive invariant. Another caveat is that the set of the dependencies of your package is no longer a static property but a function of the switch state. This will make maintaining and debugging your package a harder task. Finally, having disjunctions and conjunctions in your package formula (not in the version constraint) puts a lot of stress on the constraint solver, which eventually will lead to performance issues or even timeouts.

The conventional solution, but with a different semantics would be using the depopts field. It still doesn't allow you to parameterize over the OCaml version, but you can do this using a filter in your configure script, e.g.,

build: [
  [
    "./configure"
    "--%{camlp-streams:installed}%-streams" {ocaml:version >= "4.14.0"}
  ]
  [make]
]


depopts: [
  "camlp-streams" {>= "5.0"}
]

Now, when you install your package (let's name it foo), camlp-streams will never be explicitly installed. But if you specify camlp-streams explicitly, e.g.,

opam install foo camlp-streams

or if camlp-streams is already installed, then your configure script during the build will be called with --enable-camlp-streams only if the version of OCaml is greater than 4.14.0. In addition, the camlp-streams will be built before your package and if the camlp-streams package is installed after your package, then your package will be rebuilt.

This is the default approach for specifying the optional dependencies that you can find in the opam-repository. It is also a good habit to advertise your optional packages that enhance your main package in the post-install message.

In a rare case when your package is really required if and only if some other package is present, then you can either rely on the disjunction (from your answer) or specify such a dependency via virtual packages. In fact, one package with two alternatives, version 1 that doesn't introduce the dependency on camlp-streams,

opam-version: "2.0"
name: "conf-foo-camlp-streams"
version: "1"

conflicts: [
  "ocaml" {>= "4.14.0"}
]

and version 2,

opam-version: "2.0"
name: "conf-foo-camlp-streams"
version: "2"

depends: [
  "camlp-streams" {>= "5.0"}
]

conflicts: [
  "ocaml" {< "4.14.0"}
]

and specify conf-foo-camlp-streams (substitute the foo tag with the name of your package everywhere) as the dependency of your package without constraining its version. Now the solver would easily select the proper alternative based on the conflicts with the OCaml version and install camlp-streams only if the second alternative was chosen. This is close to the disjunction solution but more verbose. On the other hand, it should put less stress on the solver.

0
On

I think I found a working solution:

depends: [
  ( "ocaml" {>= "4.07.0" & < "4.14.0" } | ( "ocaml" { >= "4.14.0" } & "camlp-streams" ))
  "dune" {build & >= "2.8.0"}
]

This allows me to specify that I depend on the ocaml package only if its version is less than 4.14.0 and on the ocaml and camlp-streams if ocaml version is more than 4.14.0