Building an OCaml cross compiler with OCaml 5.3

A cross compiler is a compiler that runs on some host machine, for instance one running Linux on a 64-bit ARM processor, and generates code for a different target machine, for instance one running Windows with a 64-bit x86 processor.
Building OCaml cross compilers used to be quite tricky and hackish but many incremental changes to the build system over the last years have improved radically the situation. So much so that, with the most recent changes (1, 2) in the development branch of the compiler, it should be possible to build many cross compilers without extra changes :crossed_fingers:

This is all well and good, you might say, but you would rather play with the brand new 5.3 instead of a development branch! So I’ve backported the necessary changes to 5.3.

How to build and use a OCaml 5.3 cross compiler

To make it easy to test, I’ve written an example OPAM file that can be customised to suit your goal. It takes the example of building a cross compiler to 64-bit x86 Windows MinGW, in particular because that always reveals unexpected issues :slight_smile:

  1. Start by creating a 5.3.0 OPAM switch if you haven’t already.

  2. Choose the target you want to create a cross compiler for and find its target triplet. The GCC C cross compilers and the GNU cross binutils use target triplets as prefixes for the commands, so an easy way to find your triplet is to install the tools. So:

  3. Install the C cross compiler and toolchain for your target. Many Linux distributions package some cross compilers. For instance, the CI tests for cross compilers installs on Ubuntu:

    • the gcc-mingw-w64-x86-64 package (which depends on the matching binutils) to cross compile to 64-bit x86 Windows MinGW; in that case, that target machine is identified by the x86_64-w64-mingw32 triplet, so it calls configure with the argument --target=x86_64-w64-mingw32,
    • the gcc-aarch64-linux-gnu package to cross compile to 64-bit arm Linux; in that case, the target machine is identified by the aarch64-linux-gnu triplet.
  4. Create a new OPAM package interactively for instance by choosing the name of the package (ocaml-xyz or even ocaml-cross-xyz are good choices I’d say; my example uses ocaml-cross-windows, for instance) and run:

    opam pin add --edit ocaml-cross-xyz -
    

    This will open an editor so that you can fill in the instructions on how to build your cross compiler. Use my example to get you started. In particular you’ll want to configure the --target parameter with the triplet for your target (that could be the only change!). If your toolchain and C compiler use that triplet as a prefix for all the commands, configure will find them automatically. Otherwise you’ll need to explicitly set them, by adding arguments such as CC=... to configure. The CI tests for cross compilers contains such an example to cross compile to Android where CC, AR, PARTIALLD, RANLIB and STRIP are explicitly set… In other words, I suggest to experiment first with an example with automatic configuration!

You should now have a cross compiler! Let’s use it on a simple sanity check test.ml:

(* Is the proper (target) OS identified? *)
let _ =
  Printf.printf "Version: %s\nOS: %s\nUnix: %b\nWin: %b\nCygwin: %b\n"
    Sys.ocaml_version Sys.os_type Sys.unix Sys.win32 Sys.cygwin

(* Do the compiler libs work? *)
(* The interface for [Arch] is not the same across processor architectures, the
   following assumes your target is 64-bit x86 *)
let _ =
  Printf.printf "allow_unaligned_access = %b\n" Arch.allow_unaligned_access;
  Printf.printf "win64 = %b\n" Arch.win64

The package ocaml-cross-xyz will install an ocamlfind toolchain called xyz. So we can compile test.ml thus:

ocamlfind -toolchain xyz opt -package compiler-libs.optcomp -linkpkg test.ml -verbose

where -verbose let you check what is actually being run. If your target is Windows MinGW (so cross compiling from some unix), you probably need an extra step before this compilation can go through: the tool flexlink.exe which is used to link the final Windows binary has been built as part of the package but ocamlopt will expect to find a command flexlink (note in particular the absence of .exe) so I suggest to ln -s the flexlink binary somewhere in your PATH. For instance, it could be:

ln -s "$(opam show --list-files ocaml-cross-xyz | grep flexlink.opt.exe)" ~/bin/flexlink

and then you will be able to run the ocamlfind -toolchain ... command to compile your program.

Gotchas and details

  1. Beware that having a flexlink command in PATH breaks OCaml (5.3 and before)’s configure if you’re not on Windows; this will be fixed in 5.4.

  2. The example OPAM package contains SHA256 sums for .patch files generated on the fly from the corresponding commits but they might change without notice (to add an extra digit to the SHA1 they contain, for instance). If you notice that, ping me so that I can update the SHA256 sums in the gist.

  3. The example OPAM package pulls the official OCaml 5.3.0 archive along with two patches:

    • the first one is a large commit that squashes all the commits that I backported from upstream,
    • the second one is a small commit that adds the generation of the ocamlfind toolchain configuration.

    You can find the detailed backport on my 5.3+ocross branch and its comparison with the official release. The squashed commit lives on its own branch.

25 Likes

Indeed, for example, my OCaml cross compiler only needs a 7line patch

1 Like

Am I correct that repositories like GitHub - ocaml-cross/opam-cross-windows: An OCaml cross-toolchain for Windows and several useful libraries will still be necessary for package ecosystem cross compilation purposes, but that these changes will make adding/maintaining the compiler packages in those repos easier?

Yes, that’s right - the change has effectively “upstreamed” the compiler part of maintaining cross-compilation (with immediate benefit - the Windows change I made in Set `FLEXDIR` when bootstrapping flexlink by dra27 · Pull Request #13723 · ocaml/ocaml · GitHub, for example, now immediately includes the required updates for Windows cross-compilation, which are even then tested in CI)

1 Like

I’m really glad to see this: I noticed Makefile.cross in the repository but I wasn’t sure if it was being updated for general cross compiling.