Cross Platform (MacOSX/*nix) static linking

lwt
jbuilder
ssl
libev
esy

#1

Note that this is a related to Statically Link

So we started a greenfield project at work using OCaml/Reason (we are mainly a Scala based team) and we have successfully deployed however we noticed an issue that we need some help in.

The issue comes across making executable that can run without any runtime C dependencies, we need this due to our CDP build system. Since OCaml produces native bytecode, you basically have to do static linking for the platform. To be clear, the libraries which contain C dependencies which we are trying to static link are ocaml-ssl (https://github.com/savonet/ocaml-ssl) which binds to the openssh library and also libev which is used by conf-libev.

There is a guide on how to do this here http://rgrinberg.com/posts/static-binaries-tutorial/ however the main issue is that global static linking (i.e. using (flags (-ccopt -static))) doesn’t work on MacOS (note that MacOS perfectly supports static linking specific dependency, just not every dependency in your project). This leaves to solutions for us. Most of our developers use machines that have MacOS for local development, however in production we deploy docker containers which run linux.

  1. The ideal (or perfect) solution is to only statically link the libev and openssh library on every platform. Unfortunately I haven’t managed to figure out how to do this using -ccopt flags. These flags are directly passed into ld however with a lot of searching I haven’t actually managed to figure out how to only statically link openssl/libev (I know there is a -l flag that can be passed into ld to do this, but I didn’t manage to get it to work in an agnostic way. Furthermore it has the known issue that it will always pick up the dynamic .dynlib/.so libraries over the static .a files, so you need a manual prepossessing step to deal with this.
  2. The less ideal solution is to allow you to build your project with an optional flag (i.e. something like --static-linux) so that by default it uses dynamic libraries but in our build systems on linux we can just build with --static-linux). The issue is I haven’t managed to figure this out with esy/dune (esy just uses dune under the hood). Firstly someone on discord recommended me to use the env stanza to conditionally add the (flags (-ccopt -static)) flags and then use --profile in dune however the issue with this is that esy uses the -p (i.e. package flag) to build dune projects which can’t be mixed with --profile. I also had issues just getting the env command to work when running dune directly, i.e. if you have a trivial dune file as shown
(library
  (name lib)
  (public_name hello-ocaml)
(libraries lambda-term lwt lwt_ssl))

Where would you put the env stanza, i.e. would you put it here

(env
 (static-linux (flags (:standard -ccopt -static))))
(library
  (name lib)
  (public_name hello-ocaml)
(libraries lambda-term lwt lwt_ssl))

Or here

(library
  (name lib)
  (env
    (static-linux (flags (:standard -ccopt -static))))
  (public_name hello-ocaml)
(libraries lambda-term lwt lwt_ssl))

Note that in this case I still have the issue that I cant do something like dune build --profile=static-linux -p hello because the flags don’t mix.

Anyone have any recommendation on what to do here?


#2

I don’t have a good answer to your problem. But two remarks:

  • if in prod you have only docker containers running linux, why do you need static linking on macos? You should be able to build the binary for production in a container too. Or I am missing something?
  • esy uses what you tell it to use. It doesn’t do any magic. You can control what is in the build field of your package.json. You are not forced to use the -p flag. You are not forced to put a dune command in this field. It could be a script running a configure part at first checking for the environment and deciding what are the good compilation flags to use. The -p flag is not magic, it is just --root . --only-packages PACKAGE --promote ignore --no-config --profile release. You can use all those and replace the --profile part. Also if all you dependencies are using dune, you can vendor them and set some global flags for everything using the env stanza in dune-workspace.

#3

You aren’t missing anything, this is precisely what I wan’t to do (I am just figuring out how to do this).

This is the precisely the info I needed, will try this out now, thanks!


#4

I don’t think this is the case if you use Docker. If you run the build with static link flags in a container, the linked libraries will be of the container and not the host MacOS. Any C libraries installed (for example from running opam depext inside the container) would be available for static linking AFAIK.

Unless, of course, if you are planning to bypass Docker on your dev env, which would defeat one of the purposes of having Docker to prevent the phrase “it works on my machine”. I believe it would be better if your devs also build and run the binary from a Docker container locally to make it (almost) identical with your prod environment.