Conditional Compilation in Dune Based on Modes

This example demonstrates how to do conditional compilation in Dune based on compilation modes (bytes, native, js) without using virtual libraries.

Approach 1: Using select in Library Dependencies

The select form allows you to choose different implementations based on available libraries:

(library
 (name mylib)
 (libraries
  (select platform.ml from
   (js_of_ocaml -> platform.js.ml)
   (-> platform.native.ml))))

This will:

  • Use platform.js.ml when js_of_ocaml is available
  • Fall back to platform.native.ml otherwise

Approach 2: Using Conditional Rules

You can use rules with enabled_if to generate different files based on conditions:

(rule
 (target config.ml)
 (enabled_if (= %{profile} release))
 (action (copy config.release.ml %{target})))

(rule
 (target config.ml)
 (enabled_if (<> %{profile} release))
 (action (copy config.dev.ml %{target})))

Approach 3: Mode-specific Modules

For different compilation modes, you can create separate source files:

src/
├── common.ml      # Shared code
├── backend_native.ml
├── backend_bytecode.ml
└── backend_js.ml

Then use select or rules to pick the right implementation.

Approach 4: Using Dune Contexts (Dune 3.16+)

For more complex scenarios, especially with Melange/JS compilation:

; In dune-workspace
(context
 (default))

(context
 (default
  (name js)))

Then in your library:

(library
 (name mylib)
 (enabled_if (= %{context_name} default)))

(library
 (name mylib)
 (modes melange)
 (enabled_if (= %{context_name} js)))

Building

For native:

dune build

For JavaScript (with js_of_ocaml):

dune build @install --profile js

For specific contexts:

dune build --context js

Performance Note

Unlike virtual libraries, these approaches have no runtime performance penalty since the selection happens at build time.