Skip to content
Riot Docs

Search is only available in production builds. Try building and previewing the site to test it out locally.

Install Riot GitHub

RFD0017 - OCaml Cross-Compilation Snapshot

  • Feature Name: ocaml_cross_compilation_snapshot
  • Start Date: 2026-03-23
  • Status: implemented

This RFD documents the current cross-compilation state carried by Riot’s vendored OCaml fork under vendor/ocaml.

The current state is centered on one large fork commit, ef81d5fd5 feat(cross): add relocatable cross-compilation tooling, plus the supporting documents in:

  • vendor/ocaml/LINEAR.md
  • vendor/ocaml/CROSS_COMPILE_GUIDE.md
  • vendor/ocaml/CROSS_MSVC.md
  • vendor/ocaml/RELOCATABLE.md
  • vendor/ocaml/SUMMARY.md

At a high level, the fork currently provides:

  • relocatable OCaml compiler installs
  • scripted native and cross builds under vendor/ocaml/cross/
  • Linux glibc and musl target support
  • MinGW Windows target scripts
  • an exploratory MSVC design document rather than an implemented MSVC pipeline

At a high level, Riot/Riot currently provides:

  • workspace-level toolchain.targets configuration in ocaml-toolchain.toml
  • riot build --target ... target selection and single-target execution
  • host and target-specific toolchain installation under ~/.riot/toolchains/
  • build-context propagation of cross-compilation target metadata into the planner and executor
  • partial consumption of cross target metadata, with stronger support for target-specific OCaml toolchains than for fully configured C toolchain plumbing

This RFD is a snapshot, not a proposal. It exists to make the current shape of the fork explicit before Riot integrates it more deeply into its own bootstrap, toolchain, and CI flows.

Riot now vendors its OCaml fork under vendor/ocaml, and that fork carries a substantial cross-compilation system that is larger than a normal “patch a few compiler files” story.

Right now the relevant information is split across:

  • fork-local implementation scripts in vendor/ocaml/cross/
  • multiple design and summary documents in the fork root
  • one large fork commit that mixes code, scripts, packaging, and CI
  • Riot-side bootstrap and toolchain code that is beginning to point at the vendored compiler tree

That makes it hard to answer questions like:

  • what is actually implemented today
  • which target combinations are scripted today
  • whether Windows support is real or exploratory
  • where relocatability comes from
  • how much of this is in the compiler fork versus Riot’s own workflows

This RFD exists to capture the current state before more integration work lands.

The current cross-compilation story has two layers:

  1. a vendored OCaml fork in vendor/ocaml
  2. Riot-side tooling that can eventually consume compiler installs produced by that fork

The fork is where almost all of the implemented behavior lives today.

The current fork should be understood as:

  • an OCaml source tree with a relocatable install model
  • a cross/ directory full of build and packaging scripts
  • a matrix of native and cross target configuration files
  • optional CI definitions that were moved out of active .github/workflows in the fork

It should not be understood as:

  • a fully integrated Riot build flow yet
  • a fully general target-agnostic cross-compilation framework
  • an implemented MSVC pipeline

The fork is designed to produce compiler installs that can be moved anywhere on disk as long as they preserve the usual layout:

bin/
lib/ocaml/

The important property is:

  • the compiler and runtime should resolve ../lib/ocaml relative to the executable location instead of relying only on a fixed absolute install path

That is what makes tarball distribution practical for Riot’s toolchain story.

The main user-facing scripts live under vendor/ocaml/cross/:

  • build-native.sh
  • build-cross.sh
  • build.sh
  • package.sh
  • download-toolchains.sh
  • test-musl.sh
  • test-relocatable.sh

The target definitions live in vendor/ocaml/cross/targets/.

Today the scripted targets include:

  • native macOS ARM64
  • native Linux x86_64 glibc
  • native Linux ARM64 glibc
  • macOS ARM64 to Linux x86_64 glibc
  • macOS ARM64 to Linux ARM64 glibc
  • macOS ARM64 to Linux x86_64 musl
  • macOS ARM64 to Linux ARM64 musl
  • Linux x86_64 glibc to Linux ARM64 glibc
  • Linux ARM64 glibc to Linux x86_64 glibc
  • Linux x86_64 glibc to Linux x86_64 musl
  • Linux ARM64 glibc to Linux ARM64 musl
  • macOS ARM64 to Windows x86_64 MinGW
  • Linux x86_64 glibc to Windows x86_64 MinGW

That means the current implemented Windows story is MinGW-based. CROSS_MSVC.md is an exploration of how an MSVC-targeting path could work, but it is not the same as saying Riot currently ships MSVC cross-compilers.

The Riot repository is beginning to grow a self-contained compiler story around this vendored fork, but the fork is still the authoritative place for the cross-compilation implementation itself.

In current terms:

  • vendor/ocaml is the compiler source and scripted build surface
  • Riot bootstrap/toolchain code can prefer that vendored source for host toolchains
  • Riot does not yet have a fully settled end-to-end workflow that exercises all of the fork’s target combinations from this repository alone

That distinction matters: the compiler fork already contains a lot of behavior, but Riot has not yet fully absorbed it into its own steady-state workflows.

On the Riot side, the current cross-compilation entrypoint is riot build with target selection.

The current user-facing pieces are:

  • ocaml-toolchain.toml can declare toolchain.targets = [...]
  • riot build --target <pattern> can resolve a configured target
  • riot build --all-targets exists at the CLI surface, but the build command still rejects multiple matched targets and asks the user to pick one
  • riot toolchain can inspect and install configured toolchains

The important current behavior is:

  • host builds default to the current host triple
  • a single explicit target triple can be selected for a build invocation
  • the local session/server stack passes that target through to the build context
  • the planner uses that build context for target-specific profile overrides and sysroot-aware C compile/link flags

The important current limitation is:

  • Riot currently supports one build target per build invocation, even though the configuration format and some CLI surface area already anticipate multiple configured targets

1. The cross-compilation implementation currently lives in one fork commit

Section titled “1. The cross-compilation implementation currently lives in one fork commit”

The current cross-compilation system in the vendored compiler is primarily the result of one fork commit:

  • vendor/ocaml@ef81d5fd5

That commit introduces:

  • cross build scripts
  • packaging and test helpers
  • target definitions
  • relocatability documentation
  • Windows exploration documentation
  • vendored FlexDLL support under cross/flexdll
  • disabled fork-local GitHub workflow definitions under .github/workflows-disabled

So the current implementation should be treated as a fork-local subsystem, not as a small patch series sprinkled through upstream OCaml.

2. Relocatability is implemented by source changes, not only scripts

Section titled “2. Relocatability is implemented by source changes, not only scripts”

The fork documentation is slightly inconsistent about how small the core compiler change is.

cross/README.md emphasizes a one-line Makefile change, but the current source tree and the other fork documents show a broader implementation that currently touches at least these files:

  • Makefile
  • utils/config.generated.ml.in
  • utils/config.common.ml.in
  • runtime/startup_byt.c
  • configure.ac

The durable current story is:

  • the install layout is made relocatable by compiler/runtime path-resolution changes in the OCaml source tree
  • the cross/ scripts are built on top of that relocatable behavior

The current source confirms:

  • utils/config.common.ml.in uses Sys.executable_name
  • runtime/startup_byt.c uses caml_executable_name()
  • both compiler/runtime config paths now work with relative stdlib locations

So the current fork should be described as “relocatable compiler/runtime path resolution is implemented in source”, not merely “packaging scripts arrange portable directories”.

The target definitions currently present under vendor/ocaml/cross/targets/ are:

  • aarch64-apple-darwin.sh
  • x86_64-unknown-linux-gnu.sh
  • aarch64-unknown-linux-gnu.sh
  • aarch64-apple-darwin-x-aarch64-unknown-linux-gnu.sh
  • aarch64-apple-darwin-x-x86_64-unknown-linux-gnu.sh
  • aarch64-apple-darwin-x-aarch64-unknown-linux-musl.sh
  • aarch64-apple-darwin-x-x86_64-unknown-linux-musl.sh
  • x86_64-unknown-linux-gnu-x-aarch64-unknown-linux-gnu.sh
  • aarch64-unknown-linux-gnu-x-x86_64-unknown-linux-gnu.sh
  • x86_64-unknown-linux-gnu-x-x86_64-unknown-linux-musl.sh
  • aarch64-unknown-linux-gnu-x-aarch64-unknown-linux-musl.sh
  • aarch64-apple-darwin-x-x86_64-w64-mingw32.sh
  • x86_64-unknown-linux-gnu-x-x86_64-w64-mingw32.sh

That means:

  • Linux glibc is implemented
  • Linux musl is implemented
  • MinGW Windows targets are scripted
  • MSVC is not represented by target scripts here

vendor/ocaml/CROSS_MSVC.md is a design exploration.

It describes:

  • why MSVC would be desirable
  • why MinGW is easier today
  • why Windows SDK and cl.exe make Unix-hosted MSVC cross-compilation hard
  • potential approaches involving Clang/LLVM, Wine, or SDK extraction

But the current fork snapshot does not include:

  • active MSVC target scripts in cross/targets/
  • an implemented MSVC build pipeline
  • validated MSVC compiler artifacts

So the correct snapshot wording is:

  • MinGW Windows targeting is partially scripted
  • MSVC cross-compilation remains a design investigation

5. Riot has a real cross-compilation surface, but it is still partial

Section titled “5. Riot has a real cross-compilation surface, but it is still partial”

The Riot repository already contains a real target-aware toolchain and build surface.

The current pieces are:

  • packages/riot-model/src/toolchain_config.ml
  • packages/riot-toolchain/src/riot_toolchain.ml
  • packages/riot-toolchain/src/cross_compiling_toolchain.ml
  • packages/riot-cli/src/build.ml
  • packages/riot-build/src/build_server.ml
  • packages/riot-model/src/target.ml
  • packages/riot-model/src/build_ctx.ml
  • packages/riot-planner/src/action_graph.ml

ocaml-toolchain.toml can currently declare:

  • toolchain.version
  • toolchain.source
  • toolchain.targets

The targets list is the current Riot-side source of truth for which target triples the workspace intends to support.

If targets is empty, Riot currently behaves as host-only by default.

riot build currently exposes:

  • -x, --target
  • --all-targets

But the current command implementation only executes one target per invocation. If target resolution matches more than one configured target, the CLI prints the matched list and exits instead of building them all.

So the durable snapshot is:

  • target selection is implemented
  • multi-target execution is not implemented yet

Riot_toolchain.init_for_target currently behaves like this:

  • if target = host, it prefers a native host toolchain
  • if ./vendor/ocaml/compiler exists, Riot can symlink that as the local host toolchain
  • otherwise Riot downloads a prebuilt host toolchain tarball from cdn.ocaml.ai
  • if target != host, Riot downloads a prebuilt cross toolchain tarball from cdn.ocaml.ai

This means Riot currently distinguishes two very different stories:

  • vendored compiler source is currently part of the native host bootstrap path
  • cross-target toolchains are currently treated as prebuilt downloadable artifacts, not as compiler installs built on demand from vendor/ocaml

That is an important current boundary.

Riot now also carries a local helper script, scripts/toolchain/ocaml.sh, for manually building, packaging, and uploading a selected subset of those prebuilt tarballs to the configured CDN bucket while the automated publishing workflow remains disabled.

When riot build selects a non-host target:

  • packages/riot-build/src/build_server.ml parses the target triple
  • it constructs Riot_model.Target.Cross
  • it asks CrossCompilingToolchain.detect to discover:
    • sysroot
    • bin_dir
    • bin_prefix
  • it stores that information in the build context
  • it initializes a target-specific OCaml toolchain for that build

So Riot does have a real target-aware build context, not merely a target label passed to logging.

5.5 What the planner and executor consume today

Section titled “5.5 What the planner and executor consume today”

The current planner/executor stack uses target information in a narrower way than the model suggests.

Today the clearest implemented uses are:

  • selecting the OCaml toolchain binaries for the target
  • looking up target-specific profile overrides by target platform name
  • adding --sysroot=... to C compilation actions when a sysroot was detected
  • adding --sysroot=... and target-derived linker flags to executable linking

The current weaker area is:

  • Riot records bin_dir and bin_prefix for cross C toolchains, but the action graph/executor path is still much more clearly wired around sysroot and profile/linker flags than around a fully explicit prefixed C toolchain command path

So the current Riot snapshot should be described as:

  • target-aware OCaml toolchain selection: implemented
  • target-aware build context propagation: implemented
  • sysroot-aware C compile/link flag injection: implemented
  • full end-to-end prefixed cross C toolchain plumbing: not yet clearly complete

6. Riot’s current cross-compilation story is a hybrid

Section titled “6. Riot’s current cross-compilation story is a hybrid”

Putting the fork and Riot together, the current state is:

  • the vendored compiler fork owns the heavy cross-compilation implementation
  • Riot can already select targets and request target-specific OCaml toolchains
  • Riot can already thread target metadata into build planning
  • Riot still relies on prebuilt cross toolchain artifacts for actual target toolchain installation
  • Riot does not yet build cross toolchains from vendor/ocaml as part of the normal riot build story

That means Riot’s current cross-compilation story should be understood as a hybrid:

  • source-driven for the vendored native compiler/bootstrap path
  • artifact-driven for current target toolchain consumption
  • not yet a fully unified “Riot builds all host and cross compilers from source on demand” system

7. Fork-local CI exists, but is intentionally disabled in the fork

Section titled “7. Fork-local CI exists, but is intentionally disabled in the fork”

The cross-compilation commit moves several workflow files out of the active .github/workflows directory into .github/workflows-disabled/.

That means the current fork carries workflow definitions and matrix ideas, but does not currently rely on the fork repository itself to execute them.

This is important for Riot because it implies:

  • Riot can study and reuse the intended CI matrix
  • Riot should not assume the fork is already self-testing every target in its own GitHub Actions configuration

At the moment, Riot should treat the vendored compiler fork as:

  • the source of truth for compiler-side cross-compilation scripts and patches
  • not yet the final source of truth for Riot’s end-to-end workflow contracts

The integration questions that remain outside this snapshot include:

  • how Riot bootstrap should prefer vendored source over downloaded tarballs
  • how Riot CI should build, cache, and publish cross compilers
  • whether Riot keeps using prebuilt toolchain tarballs, local vendored builds, or both
  • how MinGW support should appear in Riot user-facing commands
  • whether MSVC becomes a real supported target or remains documentation only

The current state has several rough edges:

  • the implementation is concentrated in a large fork commit rather than a smaller patch stack
  • the fork docs are internally inconsistent in a few places about how minimal the relocatability change really is
  • Linux and Windows cross-target support live together in the fork, but Riot has not yet turned that into one coherent local workflow here
  • MSVC documentation exists next to MinGW implementation, which can easily overstate the actual supported surface if read too quickly

This snapshot does not choose a future integration strategy. It only records the current state faithfully.

The main alternative would be to skip documenting the fork until Riot’s own bootstrap and CI integration are complete. That would make the eventual design look cleaner, but it would hide the substantial cross-compilation work that already exists and make follow-up decisions harder to reason about.

The primary prior art for this snapshot is the vendored OCaml fork itself:

  • vendor/ocaml/LINEAR.md
  • vendor/ocaml/CROSS_COMPILE_GUIDE.md
  • vendor/ocaml/CROSS_MSVC.md
  • vendor/ocaml/RELOCATABLE.md
  • vendor/ocaml/SUMMARY.md
  • vendor/ocaml/cross/README.md
  • vendor/ocaml@ef81d5fd5

Within Riot, the closest related document is:

  • RFD0009-riot-toolchain-system-snapshot.md

That RFD explains how Riot chooses and runs toolchains today, while this RFD captures the current compiler-fork side of the cross-compilation story.

  • Should Riot treat vendor/ocaml as the primary source build path for toolchains, or only as a development/debugging path behind prebuilt tarballs?
  • Should Riot absorb the fork’s disabled workflow matrix into its own CI, or keep the compiler build/publish pipeline elsewhere?
  • Should MinGW targets become first-class Riot targets, or are they only intermediate experiments on the path to another Windows story?
  • Does Riot want to carry an actual MSVC implementation, or only keep the exploration document around for future reference?