How to generate different code based on an environmental variable at build time

I would like to read a OCaml variable, based on an environmental variable that was passed at build time.

Currently I am doing the following:
env_gen.ml

let () =
  let env = Sys.getenv_opt "MY_ENV" |> Option.value ~default:"default" in
  let oc = open_out "env_generated.ml" in
  Printf.fprintf oc "let env = %S\n" env;
  close_out oc

in a dune file:

(rule
 (targets env_generated.ml)
 (deps env_gen.ml)
 (action (run ocaml env_gen.ml)))

This correctly gets MY_ENV:

MY_ENV=foo dune build --profile release

env_generator.ml will have let env = "foo"

However, if I run the build without cleaning up first:

dune build
MY_ENV=foo dune build --profile release

env_generator.ml ends up stale and will have let env = "default"

Is there a better built-in method to generate that file?
Is there a way to avoid it being cached and requiring a _build directory cleanup before running MY_ENV .. to avoid stale values?

Add to the deps list (env_var MY_ENV), then dune will know to rebuild when it changes.
https://dune.readthedocs.io/en/latest/concepts/dependency-spec.html

3 Likes

perfect, thank you, I missed that option.

Is there a more “standard” way of embedding in code a build-time environmental variable?

I make no claims about being standard, there is this example ppx rewriter that injects the compilation-time value of an environment variable. You would still want to declare the dependency for dune though.

(action (write-file foo.ml "let env = \"%{env:MY_ENV=default}\"")) may work if you don’t need special characters to be quoted (especially MY_ENV can’t contain ")

2 Likes

For completeness I’ll mention that on a library level, you need to add (preprocessor_deps (env_var MY_ENV)), to use ppx_get_env, or in general to get convenient build-time-configurable PPX.