Search code examples
ocamlnixnixos

How can I pass include path flags to ocamlc/ocamlopt in my dune project


I'm using NixOS and compiling the cohttp server example using dune. The example is notable in that it links to two C-libraries: openssl and libev.

initial attempt

Here's my shell.nix:

with import <nixpkgs> { };

let spec = {
  buildInputs = with ocamlPackages; [
    ocaml
    findlib
    dune

    # ocaml libs (and external library deps)
    cohttp-lwt-unix openssl libev
  ]);
};
in runCommand "dummy" spec ""

Here's my dune file:

(executable
 (name server_example)
 (libraries cohttp-lwt-unix))

And the output of dune build server_example.exe

...
/nix/store/3xwc1ip20b0p68sxqbjjll0va4pv5hbv-binutils-2.30/bin/ld: cannot find -lssl
/nix/store/3xwc1ip20b0p68sxqbjjll0va4pv5hbv-binutils-2.30/bin/ld: cannot find -lcrypto
/nix/store/3xwc1ip20b0p68sxqbjjll0va4pv5hbv-binutils-2.30/bin/ld: cannot find -lev

Ok, not terribly surprising since these are in non-standard locations in NixOS. I need to add the relevant paths to the ocamlopt commandline invoked by dune, e.g.: -I ${openssl.out}/lib -I ${libev}/lib.

Now, openssl includes pkg-config files, but adding pkg-config to my shell.nix has no apparent effect.

second attempt, using configurator

I used configurator to create a program to add flags from an environment variable to the build flags of my dune executable.

shell.nix

with import <nixpkgs> { };

let spec = {
  buildInputs = with ocamlPackages; [
    ocaml
    findlib
    dune
    configurator

    # ocaml libs (and external library deps)
    cohttp-lwt-unix openssl libev
  ]);

  shellHook = ''
    export OCAML_FLAGS="-I ${openssl.out}/lib -I ${libev}/lib"
  '';
};
in runCommand "dummy" spec ""

dune

(executable
 (name server_example)
 (flags (:standard (:include flags.sexp)))
 (libraries cohttp-lwt-unix))

(rule
 (targets flags.sexp)
 (deps (:discover config/discover.exe))
 (action (run %{discover})))

config/dune

(executable
 (name discover)
 (libraries dune.configurator))

config/discover.ml

open Sys
module C = Configurator.V1

let () =
  C.main ~name:"getflags" (fun _c ->
      let libs =
        match getenv_opt "OCAML_FLAGS" with
        | None -> []
        | Some flags -> C.Flags.extract_blank_separated_words flags
      in

      C.Flags.write_sexp "flags.sexp" libs)

Compilation now succeeds, but this approach of writing a custom program to take an environment variable and put it into the flags argument seems clunky.

Is there a standard way to accomplish this in dune (adding paths to ocamlopt commandline with -I)?

If not, is there an easier way to read an environment variable from within dune files?


Solution

  • Use stdenv.mkDerivation or (as suggested by Robert Hensing above) ocamlPackages.buildDunePackage instead of runCommand. The latter results in an environment that does not include openssl and libev in the NIX_CFLAGS_COMPILE or NIX_LDFLAGS environment variables. Using mkDerivation does result in those variables being populated.

    Once those variables are in place, compilation via dune succeeds, and the executable is linked to libssl and libev.

    Furthermore, explicit inclusion of libev and openssl is unnecessary since they are declared as propagated build inputs of through cohttp-lwt-unix.

    shell.nix:

    with import <nixpkgs> { };
    
    with ocamlPackages;
    
    buildDunePackage {
      pname = "dummy";
      version = "0";
      buildInputs = [
        cohttp-lwt-unix
      ];
    }