[ANN] opam2nix (v1)

Anouncing opam2nix (v1)

opam2nix generates nix expressions from the opam OCaml package repository. It works similarly to bundix, node2nix, etc:

You run an (impure) command to resolve all transitive dependency versions using the current opam repository, generating a .nix file that locks down the exact package sources and versions. Then this file can be imported to provide buildInputs for building your ocaml project in nix.

What is nix and why would I care? Well, that’s a long story but the headline benefits of nix are:

  • reproducible builds (if it builds for me, it builds for you)
  • stateless (you don’t set up switches and then install packages, each expression specifies everything it needs, and anything you don’t have is fetched/built on demand)
  • language agnostic (takes care of non-ocaml dependencies)

It’s sadly not a shallow learning curve, but those benefits are hard to find elsewhere, so I obviously think it’s worthwhile. So if you use nix (or would like to), please give it a try and provide feedback. I’ll (slowly) start working on upstreaming it into nixpkgs.

15 Likes

This is exciting! I’m keen to try this for reproducible builds in particular.

Did you encounter any hardships with wrapping opam for this, or interpreting depexts? Please do file upstream issues if future versions can be simplified for this usecase.

Thanks, will do. Overall I was super happy with how reusable opam’s internals were, it took a while to get familiar with some of the more complex parts (formulas are way more powerful than I knew from using opam), but it could have been much harder.

The main difficulty I found was that anything tied to OpamState had to be reimplemented, because that assumes certain things which aren’t true in opam2nix (e.g. there’s no one lib directory, there’s one per dependency). I’ll detail the exact issues in github though :+1:

feedback reported: https://github.com/ocaml/opam/issues/4030

Guix is more suitable for reproducible and bootstrappable builds because of their efforts with creating binary “seeds”

Guix does have a more pure bootstrap story, true. But both should be equivalent in terms of reproducibility. If you use the default binary cache, you’re sidestepping the bootstrap process anyway.

2 Likes

@timbertson how ready it is for something more complex than examples? Would it work with BAP? For example, see the opam files of the latest 2.1 version: https://github.com/ocaml/opam-repository/pull/16548/files

Very nice! However, the solving step can be quite slow. For example (deliberately choosing a hard-to-solve-for package :wink: ):

$ ./result/bin/opam2nix resolve --ocaml-version 4.07.0 datakit-ci
+ ./result/bin/opam2nix resolve --ocaml-version 4.07.0 datakit-ci
Fetching...
Importing opam-repository 8d9cac4f0a35a5da2366efb9aacc8a176df03592 into nix store...
Loading repository...
Loaded 14257 packages
Solving...
[ERROR] Sorry, resolution of the request timed out.
        Try to specify a simpler request, use a different solver, or increase the allowed time by setting
        OPAMSOLVERTIMEOUT to a bigger value (currently, it is set to 60.0 seconds).
Fatal error: exception OpamStd.OpamSys.Exit(60)

Since you’re just picking versions to run a single program (not trying to maintain an opam switch), you’ll probably find it faster to use the opam-0install solver library instead, e.g.

$ opam-0install ocaml.4.07.0 datakit-ci
[NOTE] Opam library initialised in 0.22 s
asetmap.0.8.1 asn1-combinators.0.2.2 astring.0.8.3 atd.2.2.1 atdgen.2.2.1 atdgen-runtime.2.2.1 base.v0.13.2 base-bigarray.base base-bytes.base base-threads.base base-unix.base base64.3.4.0 bigarray-compat.1.0.0 biniou.1.2.1 calendar.2.04 camomile.1.0.2 charInfo_width.1.1.0 cmdliner.1.0.4 cohttp.2.5.1 cohttp-lwt.2.1.3 cohttp-lwt-unix.2.1.3 conduit.1.5.0 conduit-lwt.1.5.0 conduit-lwt-unix.1.5.0 conf-gmp.1 conf-m4.1 conf-perl.1 conf-pkg-config.1.2 cppo.1.6.6 cpuid.0.1.2 crunch.3.2.0 cstruct.5.1.1 cstruct-lwt.5.1.1 cstruct-sexp.5.1.1 datakit-ci.1.0.0 datakit-client.1.0.0 datakit-client-9p.1.0.0 datakit-client-git.1.0.0 datakit-github.1.0.0 decompress.0.7 dispatch.0.4.1 domain-name.0.3.0 dune.2.6.0 dune-configurator.2.6.0 dune-private-libs.2.6.0 easy-format.1.3.2 fieldslib.v0.13.0 fmt.0.8.8 git.1.11.5 git-http.1.11.4 git-unix.1.11.5 github.4.3.0 github-unix.4.3.0 hex.1.4.0 io-page.2.3.0 io-page-unix.2.3.0 ipaddr.4.0.0 ipaddr-sexp.4.0.0 irmin.1.4.0 irmin-git.1.3.0 irmin-watcher.0.3.0 jbuilder.1.0+beta20.2 jsonm.1.0.1 lambda-term.3.1.0 logs.0.7.0 lwt.5.3.0 lwt_log.1.1.1 lwt_ppx.2.0.1 lwt_react.1.1.3 macaddr.4.0.0 magic-mime.1.1.2 menhir.20200525 menhirLib.20200525 menhirSdk.20200525 mew.0.1.0 mew_vi.0.5.0 mirage-channel.3.2.0 mirage-channel-lwt.3.2.0 mirage-clock.2.0.0 mirage-crypto.0.6.2 mirage-device.1.2.0 mirage-flow.1.6.0 mirage-flow-lwt.1.6.0 mirage-no-solo5.1 mirage-no-xen.1 mmap.1.1.0 mstruct.1.4.0 mtime.1.2.0 multipart-form-data.0.2.0 nocrypto.0.5.4-2 num.1.3 ocaml.4.07.0 ocaml-base-compiler.4.07.0 ocaml-compiler-libs.v0.12.1 ocaml-config.1 ocaml-migrate-parsetree.1.7.3 ocaml-syntax-shims.1.0.0 ocamlbuild.0.14.0 ocamlfind.1.8.1 ocamlgraph.1.8.8 ocb-stubblr.0.1.1-1 ocplib-endian.1.1 parsexp.v0.13.0 pbkdf.1.1.0 ppx_cstruct.5.1.1 ppx_derivers.1.2.1 ppx_deriving.4.5 ppx_fields_conv.v0.13.0 ppx_sexp_conv.v0.13.0 ppx_tools.5.1+4.06.0 ppx_tools_versioned.5.4.0 ppxfind.1.4 ppxlib.0.13.0 prometheus.0.7 prometheus-app.0.7 protocol-9p.2.0.1 protocol-9p-unix.2.0.1 ptime.0.8.5 re.1.9.0 react.1.2.1 redis.0.4 redis-lwt.0.4 result.1.4 rresult.0.6.0 seq.base session.0.4.1 session-cohttp.0.4.1 session-redis-lwt.0.4.1 session-webmachine.0.4.1 sexplib.v0.13.0 sexplib0.v0.13.0 stdio.v0.13.0 stdlib-shims.0.1.0 stringext.1.6.0 tls.0.10.2 topkg.1.0.1 trie.1.0.0 tyxml.4.4.0 uchar.0.0.2 uri.3.1.0 uri-sexp.3.1.0 uuidm.0.9.7 uutf.1.0.2 webmachine.0.5.0 win-error.1.0 x509.0.6.3 yojson.1.7.0 zarith.1.9.1 zed.3.1.0
[NOTE] Solve took 0.29 s
1 Like

In general it seems pretty solid. It reuses as much of opam as possible, and in particular with most packages tending towards jbuilder everything works well with the findlib-style of installation rather than hardcoding paths. I use OCaml just for personal projects though, so I haven’t stress tested it on larger software.

I tried out BAP, and ran into an unsupported feature: it doesn’t support git archives. It looks like bap-emacs-mode uses this: https://github.com/ocaml/opam-repository/blob/master/packages/bap-emacs-mode/bap-emacs-mode.0.1/opam (it doesn’t look like it pins a particular tag/commit, which is a bit dicey).

Supporting git is certainly possible (nothing too tricky, just a little tedious to prefetch it then map that into nix’ fetchGit expressions), honestly I’ve just never encountered a (non-pinned) opam package distributed via git before.

1 Like

Thanks, that looks very nice! I’ve definitely had a bunch of frustration with solve times, particularly when I didn’t get the external solver stuff working right.

You have some parity tests in there, are divergences from opam considered bugs or does it have known different results? Any edge cases where it makes a bad choice?

You have some parity tests in there, are divergences from opam considered bugs or does it have known different results?

It’s a bug if it finds a solution that opam rejects, or if it finds a solution that is strictly worse than another valid solution (i.e. some package’s version is worse and none is better).

But it can find a different solution to opam. Here’s a screenshot from the tests showing it making some different choices:

Opam will try to minimise a cost function across all packages, whereas 0install gives preference to the package you asked to install. In most cases there’s no trade-off needed and they both give the same solution.

Any edge cases where it makes a bad choice?

The newest version of jbuilder (jbuilder.transition) conflicts with dune 2, so you can either have the “best” jbuilder or the best dune. 0install will prioritise jbuilder if that comes first in the optimisation order, whereas opam tends to favour dune 2 in that case. But the package you’re trying to install is always the first priority, so it will never choose an older version of that just to get a better jbuilder.

2 Likes