Rebuilding a "build-info" module every time an exe is produced

If you use Jane’s Core.Command for command line parsing (and if not, why aren’t you?), it can take arguments like -version and -build-info which are super useful if you want to bake in important build information.

Aside: why does anyone want this? One excellent release engineer use-case is to figure out which binaries are more than, say, 6 months out of date and schedule them for a refresh. Another one is if you want to hotfix a bug in a binary, but not introduce all of the changes made in the main repo, you can git checkout the version baked into some ad-hoc binary that nobody is quite sure when it was released, and then roll a new release of that which only contains your fix.

Anyway, to get this, you have to have your build system generate some info that you then pass into Core.Command.

I’m having a bit of trouble doing this with dune. I can follow these directions and get pretty close to what I want: https://github.com/ocaml/dune/tree/master/example/sample-projects/with-configure-step

I can stick a dune stanza like this into my dune file:

(rule
  (target build_info_gen.ml)
  (action (run ocaml %{dep:gen_build_info.ml})))m

which runs an script called gen_build_info.ml

beaver$ cat gen_build_info.ml 
#load "unix.cma"

let sh_line s =
  let ic = Unix.open_process_in s in
  let line = input_line ic in
  close_in ic;
  line
;;

let () =
  let oc = open_out_bin "build_info_gen.ml" in
  Printf.fprintf oc {|
let git_revision = "%s" ;;
let build_host = "%s" ;;
let build_user = "%s" ;;
let build_time = "%s" ;;
let ocamlopt_version = "%s" ;;
|} (sh_line "git describe --always --dirty")
   (sh_line "hostname")
   (sh_line "whoami")
   (sh_line "date")
   (sh_line "ocamlopt --version");
  close_out oc
;;

What that will do is generate a module called build_info_gen.ml, which I can use in a module called build_info.ml with sexp to make auto-generation easy.

$ cat build_info.ml 
open! Core

type t =
  { git_revision : string
  ; build_user : string
  ; build_host : string
  ; build_time : string
  ; ocamlopt_version : string }
  [@@deriving sexp]

let t =
  { git_revision = Build_info_gen.git_revision
  ; build_user = Build_info_gen.build_user
  ; build_host = Build_info_gen.build_host
  ; build_time = Build_info_gen.build_time
  ; ocamlopt_version = Build_info_gen.ocamlopt_version }

let version = t.git_revision
let build_info = sexp_of_t t |> Sexp.to_string_hum

Which I can feed into Command in mymain.ml and get cool stuff to happen:

open! Core

(*...*)

let () =
  Command.run 
    ~version:Build_info.version 
    ~build_info:Build_info.build_info cmd 

Cool stuff:

beaver$ mymain.exe -build-info | sexp pp
((git_revision     095e463-dirty)
 (build_user       mbac)
 (build_host       beaver)
 (build_time       "Fri Jan  3 10:10:44 PST 2020")
 (ocamlopt_version 4.08.1))

(You can get the sexp tool with opam install sexp if you don’t already have it)

This almost works perfectly. What I’m having trouble doing is convincing dune it should regenerate build_info_gen.ml every time it tries to build an executable, not just the first time I build the executable.

Any pointers on this?

1 Like

Have you considered using the dune-build-info library for this? It maybe doesn’t expose as much transient build info like user host and time as you want, but for version and versions of dependencies it works well in my experience. An example, that is integrated with Command, is here which uses this dune file. Otherwise, you might have luck using dune’s universe dependency in a build rule, which tells dune to rerun that rule if anything has changed.

1 Like

I did look at dune-build-info but it’s not enough info.

Yup! It does seem like specifying (deps (universe)) as for my rule solves the problem. Repeating the whole thing:

(rule
  (deps (universe))
  (target build_info_gen.ml)
  (action (run ocaml %{dep:../scripts/gen_build_info.ml})))

When I build and run twice with no changes in between:

mbac@beaver$ dune build ./mymain.exe
mbac@beaver$ ./mymain.exe -build-info | sexp pp
((git_revision     095e463-dirty)
 (build_user       mbac)
 (build_host       beaver)
 (build_time       "Sun Jan  5 13:38:54 PST 2020")
 (ocamlopt_version 4.08.1))
mbac@beaver$ dune build ./mymain.exe
mbac@beaver$ ./mymain.exe -build-info | sexp pp
((git_revision     095e463-dirty)
 (build_user       mbac)
 (build_host       beaver)
 (build_time       "Sun Jan  5 13:39:03 PST 2020")
 (ocamlopt_version 4.08.1))

Note the build time baked into the .exe changes.

w00t! Thanks!