locswijch

The uncomfortably obvious dependency bridge

Problem

dune pkg stores built packages in _build/, invisible to the standard opam switch mechanism that tools like ocamllsp, merlin, and utop rely on. And dune.lock/ plus the built artifacts live only in the project tree — there is no switch-level record to recover them from.

Solution

locswijch creates an opam switch that serves dual purpose:

  1. Working opam switch — tools like ocamllsp, merlin, and utop can find packages via the standard opam switch mechanism.
  2. Backup of dune.lock/ and _build/.pkg/ — survives dune clean (and even deletion of dune.lock/) and can reconstruct both from switch metadata.

Hard links (same filesystem) make both sync and restore zero-cost in disk space. dune clean just decrements link counts; files survive in the switch. A design heresy, the human brain politely refises to process the full trauma of its functionality.

Note: instant rebuilds are dune's job, not locswijch's

Dune decides whether to re-run a rule from _build/.db, which dune clean deletes — restored target trees alone are never trusted, so a rebuild after restore re-executes every package rule. The fix is dune's own shared cache: run builds with DUNE_CACHE=enabled. The default cache mode (enabled-except-user-rules) excludes lock-dir package actions; with DUNE_CACHE=enabled a post-clean rebuild hard-links everything back from ~/.cache/dune/db in about a second. Use restore to recover the dune.lock/ + _build/.pkg/ state itself; use the dune cache for speed.

Commands

locswijch sync

Run after dune build. Hard-links package artifacts from _build/_private/default/.pkg/*/target/ into an opam switch prefix. Stores reconstruction metadata (cookie files, digest directory names, per-package .install manifests).

locswijch sync [--switch NAME] [--project DIR]

locswijch restore

Run after dune clean (or after losing dune.lock/). Recreates dune.lock/ and _build/_private/default/.pkg/ from the switch via hard-links. Note that the next dune build does not trust the restored targets by themselves (see above) — pair with DUNE_CACHE=enabled for fast rebuilds.

locswijch restore [--switch NAME] [--project DIR]

locswijch migrate

Generate dune.lock/ from an existing opam switch. This is a one-way translation of opam package metadata into dune's .pkg format. A full dune build is required afterward (the source artifacts must be fetched and compiled by dune), followed by locswijch sync to augment the switch as backup.

locswijch migrate [--switch NAME] [--project DIR]

Typical workflows

New project using dune pkg

export DUNE_CACHE=enabled   # let dune cache package builds too

# Initial setup
dune pkg lock
dune build
locswijch sync

# After dune clean
dune clean
locswijch restore       # recover dune.lock/ + _build/.pkg/
dune build              # ~1s — packages hard-linked from the dune cache

Migrating from opam to dune pkg

locswijch migrate --switch default
# Edit dune-project to declare dependencies
dune build              # full rebuild (unavoidable)
locswijch sync          # switch now serves as backup

Day-to-day development

dune build              # builds project + any new deps
locswijch sync          # keep switch in sync (fast, idempotent)

Options

How it works

Sync

  1. Enumerates _build/_private/default/.pkg/<name>.<ver>-<digest>/ directories, keeping only the digest dirs the current dune.lock builds (asked via dune pkg print-digest, never recomputed). Stale digest dirs — accumulated across migrate iterations — are deleted from both _build/.pkg/ and the switch store; if the dune in PATH lacks print-digest, pruning is skipped with a warning and everything is synced as before.
  2. For each package with a target/ subdirectory:

    • Hard-links files into the switch prefix (lib/, bin/, etc.).
    • Generates an .install manifest listing which files belong to this package.
    • Copies the binary cookie file to .opam-switch/locswijch/.
  3. Generates opam metadata: switch-config, switch-state, per-package .opam files, environment.
  4. Registers the switch in ~/.opam/config.

Restore

  1. Reads stored digest directory names from .opam-switch/locswijch/.
  2. For each package, recreates the _build/.pkg/<digest>/target/ tree by hard-linking files back from the switch, using .install manifests to attribute files to packages.
  3. Copies cookie files back.

Cross-device fallback

If _build and ~/.opam are on different filesystems, hard links are impossible. The tool detects this and falls back to copying with a warning.

Building

opam install cmdliner opam-file-format
dune build

Testing

dune runtest

Runs parser unit tests plus a self-contained end-to-end smoke test: a tiny two-package fixture closure (generated in a temp dir, no compiler, local sources) goes through the full migrate → build → sync → clean → restore → rebuild cycle, and the post-restore rebuild must beat a time threshold that a real package rebuild cannot meet. Requires opam and dune on PATH.

Limitations

AI disclosure

In the interest of transparency, and in keeping with the transparency principles of the EU Artificial Intelligence Act (Regulation (EU) 2024/1689):

License

Released under the MIT License. © 2026 Cuihtlauac Alvarado.