Abusing OPAM to help construct MacPorts Portfiles

I maintain a bunch of OCaml packages for the MacPorts ecosystem. (In fact, I seem to be the only person working on MacPorts who does OCaml ecosystem packaging.) For those that don’t know, MacPorts is a third party package manager for macOS, much like Brew (for those that know that), or like the package managers in various Linux releases (like “dpkg” or “rpm”).

It would be nice to just tell people “go and use OPAM for everything” and not even maintain any OCaml packages for MacPorts, but there are people who want to install end user software constructed with OCaml who don’t even know it’s built with OCaml and don’t want to learn how to use OPAM. It’s also often quite handy to bootstrap your OCaml environment by building a system OCaml binary and opam binary (which is what I do) from whence opam can bootstrap.

So I’m stuck maintaining some set of packages for MacPorts that can build various OCaml libraries and tools, much as someone maintaining such tools for Debian or Ubuntu or Fedora would be. I want to make the process less painful. I’m soliciting ideas.

To explain the overall problem: in MacPorts, each package is defined by a file called a “Portfile”, which is sort of like an opam file (though much richer in some ways and much poorer in others). Each Portfile is written (mostly) by hand, and specifies all the usual things, like how to fetch the sources for a package, what the cryptographic hash of the package tarfile is, how build a package, what other packages this package depends on, etc. (The Portfile syntax has the odd feature that it’s really Tcl, and thus is Turing equivalent, which often comes in handy.)

The MacPorts build system expects to take the information in the Portfile, build a given package, and then install it into a “destroot”, a fake install location, from whence it gets turned into a binary package and then the binary package gets installed for real into the final location for the user to use.

Constructing each and every Portfile for the large number of OCaml libraries and tools is tedious, and figuring out for every package how to build that particular package’s binaries is tedious.

What I’d like, ideally, is a way to leverage the opam ecosystem to minimize the work. The most obvious way would be to get opam to tell me things like “this is the set of dependencies this package has” or “this is the command for building this package” or “this is the command for installing this package”. I can then use those either to manually construct a portfile, or to create some sort of automatic opam file to Portfile tool.

However, opam doesn’t seem to have the right hooks for this. Or if it does, I can’t figure out what they would be.

Anyone have ideas on what I might be able to do here?

1 Like

You could use opam show -f <field> with the field name you need, e.g. (the colons after the field names are required, if you’re using the same field names from the .opam file format):

$ opam show jbuilder -f depends:
"ocaml" {>= "4.02.3"}
$ opam show jbuilder -f build:
["ocaml" "configure.ml" "--libdir" lib]
["ocaml" "bootstrap.ml"]
["./boot.exe" "--subst"] {pinned}
["./boot.exe" "-j" jobs]

You might have to do some parsing from there to get it into a format suitable for Portfiles (never used MacPorts so I don’t know how it works).

I could also just parse the opam file to get much of the same data, since it is basically the same minus the leading field name with a colon, but the results are pretty messy, and don’t include things like variable expansions etc. There also seems to be some stuff that’s often implicit.

Oh, and also, most opam files don’t seem to describe “install” procedures, and I need to know what to install and how.

When an opam file doesn’t explicitly state the installation step(s), the matching package should generate during build or include in the source tree a foo.install file which defines what should be installed and where.

Does anyone know what do people maintaining deb and rpm type packages do?

Another option: if I could run opam on the current directory, as in if I could do…

opam build --prefix <someprefix> .
and
opam install --prefix <someprefix> .

…I could use opam as if it were a funny make and invoke it from the Portfile.

Yes, I know, there is no opam build command (which would only do the build phase, not the install), and no --prefix argument to opam, I’d need to hack those into existence. The question is, how hard would it be to do that? I’d also likely want an opam test command (and yes, I know, there’s -t but I need it not to run the build phase.)

I think, in fact, that option (making some changes to OPAM to let third party packagers leverage it) might be the right approach. I have created a ticket at: https://github.com/ocaml/opam/issues/3321