---
# System prepended metadata

title: Supporting header installation

---

# Supporting header installation

> Supporting luv and similar packages.

## Context

Some opam packages ship C headers (`.h` files). Two common use cases are:

- a library with a C part in its API (e.g. `ctypes`)
- an OCaml library acting as a wrapper for a C library, but which embeds the source code for this library instead of relying on depexts (ex: `luv` which embeds `libuv`).

Then, some other packages that depend on these can use these headers while building their C stubs.

A common convention is to install these `.h` files in the package's `lib` directory and to refer to them as e.g. `%{lib:ctypes:ctypes_cstubs_internals.h}` or to the directory as `%{lib:ctypes:.}`.

## Problem

This works when the package is installed, but this breaks in other cases. The reason is that `%{lib:...}` refers to the installed path (more or less the output of `opam var pkg:lib`). The problematic cases include:

- using `opam-monorepo` (the sources are unpacked and not installed)
- developing several packages in a single repository (it might be necessary to pin or install first one library)
- even when developing a single package this can cause issues, since the "installed" (pinned) version might drift from the version under development.

What dune is lacking is a mechanism to refer to these headers that would work in the source tree as well.

## Solution

The solution proposed here consists in adding a first class notion of installed headers attached to libraries: a library can have a tree of headers attached to it. This generalizes the notion of `(install_c_headers)`.

At install time, this directory is installed by opam (with preserved structure). This directory can be accessed by dependencies: if the library is found installed in opam, the installed directory is used, otherwise the directory in the workspace is used.

## Implementation

### Syntax

- the existing `(install_c_headers)` field is now recorded in the lib db
- `(foreign_stubs (include))` gets extended with a new `(headers <lib>)` constructor.
- a new pform `${headers:<lib>}` is added
- (optional) `(install_c_headers)` gets a new `(dir <path>)` constructor to install a directory of headers, with preserved structure

### Semantics

On the "provider" side, `(install_c_headers)` are now recorded in the lib db, either as plain headers (individual `.h` files) or as structured directories.
Install rules are updated so that both kinds of headers are can be turned into an implicit `(install)` stanza.

On the "user" side, `(headers <lib>)` gets evaluated to the corresponding directories: the directories where the files are installed, or the right directories in case the provider is found in the workspace. In case of a plain header in a workspace, the source directory is passed as `-I` so there might be overapproximation if this mechanism is not sandboxed (if `x.h` and `y.h` are present in the same directory, `y.h` might be available to the C compiler even if not explicitly depended on).
The `%{headers:<lib>}` pform is expanded using the same semantics. This is useful for when C code is compiled using lower level stanzas.

### Open questions / remarks on implementation

- when there are several headers directories for a given library, it's not clear how to expand `%{headers:<lib>}`. A list of directories is not useful, since it is usually used with `-I`. A possible solution could be to expand with the `-I` part.
- should we make the mechanism more automatic? for example, foreign stubs built for a library that depends on a library could be built with its headers added.
- what should be the installed path in opam? several packages use the `%{lib:<lib>:.}` location, even though it's not the "right" one. A specific `include` section in opam could be useful.
- could we rely on some of the work on `sites` to support this?