Status: It is currently severely untested but the aim is to be able to cross-compile to Linux from Windows/Mac/Linux for aarch64 and x86_64 CPU architectures simply by adding an opam repository, and without the need for nix.
Why: The novel aspect is zig, which allows you to cross-compile C code without needing to install or set up a cross-compilation sysroot i.e. glibc, gcc, binutils, kernel headers etc. as zig packages much of the needed headers and symbol information internally.
Next steps: start importing packages (including those with native binaries) into the opam repository overlay, validate them in CI/CD
This approach has led me down some rabbit-holes with a bunch of learning - some interesting points:
zig uses clang internally, so its effectively testing clang compatibility with OCamlās autoconf + Makefile assumptions about the C compiler
targeting windows isnāt possible with this setup at this time, because flexdll hardcodes mingw binary names (e.g. x86_64-w64-mingw32-gcc) in its Makefile and the flexlink binary (it assumes these exist because targeting mingw32 is always a cross-compilation, even on Windows). It also depends on binutilsā windres, which zig does not provide a wrapper for.
targeting macos x is untested
as you can see in the CI/CD scripts, setting the ZIG cache directory environment variables is crucial for MacOS because of opamās sandboxing (zig builds its cache in the userās home directory, which is outside the default sandbox)
although ocamlfind and dune have some cross-compilation support with ātoolchainsā, there are gaps and undocumented assumption
opam doesnāt really support cross-compilation environments well - packages often donāt require much change, but you do need to create a <package>-cross-<cross-name> version of every single package - this could be a lot more straightforward and less work with a more cohesive platform strategy for cross-compilation
Alternatives: Iām aware of alternatives in the ecosystem (and indeed have benefitted from):
ocaml nix overlays - these offer a far better tested and reproducible cross-compilation environment, mostly for systems that can run nix
opam-cross-windows - lots of little nuggets of build-time information found in here
Iām stuck in a frenzy of teaching at the moment due to it being the height of the academic term here, but I just wanted to stop by and say āwow!ā.
I hadnāt realised that Zig supports cross-compilation out of the box, but Iāve been very impressed by using Ghostty recently that is written in it, and your work looks like a very promising direction of travel. I canāt wait to try this outā¦
zig supports cross compilation of not just itself, but C, out of the box, with binutils/gcc compatible wrappers for aspp, ar, ranlib, gcc, etc. targeting a number of platforms. Itās quite incredible the backwards compatibility with C they built targeting numerous architectures and OSs they did
Ok, this gets very close, but the zig rc wrapper takes command line switches to be compatible with msvcās rc tool instead of binutilsā windres, which means that itās not a simple drop-in replacement for windres.
I actually need to be able to override the whole call as this doesnāt work:
(the problem is the -o - to make things even more confusing, zig rc accepts /, - and all other manner of switch prefixes for this command in its latest version, but there is no -o compatibility equivalent to windres. This is probably because theyāre trying to be more windres compatible but havenāt quite got there yet)
Actually I take this back - I noticed I can just set which call to make with your patch. Thanks, that does help!
The problems Iām hitting are now after that, where it tries to build the OCaml parts of flexlink with the bootstrap ocamlrun and ocamlc. I may be able to avoid those by building the flexlink sources separately, instead of relying on the builtin Makefile and building in the OCaml source tree.
Iām not actively investigating this, but Iāll keep it in mind if I decide I want to experiment with windowās targets later.
A couple of very quick notes on the Windows side - flexdll has always been updated āas neededā, so patches to facilitate other builds are very much welcomed (if occasionally only slowly reviewed ). If youāre building via opam, note that flexdll can be pinned - you can either pin a branch directly before building the compiler and those sources will be used instead or you can obviously set a package in your overlay repo to do the same thing and then depend on that for any patched compiler, etc. Iād recommend doing that rather than trying to circumvent the build system, just because itāll be easier to share with others.
Thanks - I meant I need to run the OCaml build of flexdll separately, not necessarily patch the build system of flexdll before running it. OCaml 5.3.0ās Makefile passes OCAMLC as ../boot/ocamlrun ../boot/ocamlc which is not what I want when cross-compiling.
The Makfile.cross in the current main branch has instructions closer to what I need to do - I will attempt those when I get the time.
My focus right now is working out how to generate the <package>-cross-<cross_name> for each package I want to cross-compile. Most of the changes are mechanistic updates to the dune package build name and adding the -x <toolchain> parameter, which I think I can automate
(again, I really wish opam had the concept of a āsubswitchā where you could just run the build and install for each package and its dependencies again, but with the toolchain parameter specifed (or for those that donāt use dune, a {with-toolchain} filter). Once Iāve figured out how to do this en-mass reliably and have a good idea of whatās actually needed, I might write a proposal).
Iāve gotten around that in the past by using opam pre-build-commands to create a dune-workspace file with the right cross-compile flags in each build directory. (EDIT: I think I needed a post-install-commands as well so that the extra cross-compiled output _build/default.TARGET/* gets into the switch.) Much of this would be much easier if dune supported an environment variable (DUNE_CROSSCOMPILE_TARGETS?) instead of just dune build -x ... or a custom dune-workspace. (I was going to do that with a follow-up to dune build -x accepts only one toolchain while dune-workspace accepts many Ā· Issue #10989 Ā· ocaml/dune Ā· GitHub but I have had little spare time to do it.)
This is a really great idea. Cross compilation is a big deal for me, I am learning zig for exactly this reason, but ocaml is such a nice language. Most of my time is spent in the julia world and this is again their big problem. If you can just get a binary that works!
Thatās definitely a sneaky way of getting opam to compile for the host and the target at the same time!
My planned approach was to do something like what opam-cross-windows does, by duplicating packages and rewriting them. I was going to try and script this as much as possible so I could just point at a package I wanted, resolve the dependencies, and rewrite them to an overlay repository for the -cross-<name> I wanted.
Thank you for suggesting zig as a good choice for a C cross compiler!
I just checked that the cross-compiler support that was merged upstream (and that I backported to 5.3 if you want to give it a try on the stable release) is compatible with that and indeed it is! You can get a cross compiler with just:
$ ./configure --target=aarch64-linux-gnu \
'CC=zig cc --target=aarch64-linux-gnu.2.34' \
'PARTIALLD=zig cc --target=aarch64-linux-gnu.2.34 -r' \
'AR=zig ar' STRIP=:
$ make crossopt -j
So I had to experiment with something more tricky, namely a Linux x86_64 to macOS aarch64 cross compiler. Building the cross compiler, using:
$ ./configure --target=aarch64-apple-darwin \
'CC=zig cc --target=aarch64-macos' \
'PARTIALLD=zig cc --target=aarch64-macos -r' \
'AR=zig ar' STRIP=: --disable-shared
worked fine but then compiling a small program fails with a long list of linking errors:
error: symbol _caml_startup.code_end not attached to any (sub)section
note: while parsing /tmp/camlstartup41b4bf.o
error: symbol _camlStd_exit.code_end not attached to any (sub)section
note: while parsing /home/runner/cross/lib/ocaml/std_exit.o
error: symbol _camlExample.code_end not attached to any (sub)section
note: while parsing example.o
[ā¦]
You can see the full CI log for details. Do you have a clue about what is broken there?
(Iāve found this similar error, maybe itās much more general than my simple attempt; so I hope it might be fixed in the next zig release).
Not sure how your stuff works, but fwiw there is bazel support for zig, and you can use bazel to build opam packages. So for example you could probably eliminate the need to install zig locally by making an opam pkg for it. You might find it worth you while to look in to that.
Iām glad to see it works with the new crossopt Makefile - Iāll look at using it once its in an official release.
I havenāt tried a target like MacOS X, but from what I understand, itās diabolical to compile for, as anything useful needs system headers from FoundationKit and AppKit, and Apple protect against their use outside of their SDKs and platform.
That said, I donāt think disabling shared libraries is a good idea for a MacOS X target. Iāve tried with shared libraries enabled, but when linking libasmrun_shared.so:
I needed to remove the passing of the -Wl,-w flag, which doesnāt seem to be supported by zig
After removing that, zig crashes with thread 10405 panic: unexpected augmentation string. This seems to be related to something in runtime/arm64.S
Iām not likely to explore this one any further - there is definitely a limit on zigās behaviour here, or something incompatible that weāre trying to compile. I think this is one to try again in the future.
Iāve been working on a way to automatically import packages from the existing opam-repository into a ācross-compile repositoryā and rewriting them to pass toolchain parameters and the correct package names when building them.
You can see the start of my work in the packman tool Iāve been putting together for this purpose.
It essentially starts with:
the name of a standard opam repository
the name of a ācrossā opam repository that has the OCaml zig-based cross-compiler defined already (in this case, https://github.com/chris-armstrong/opam-cross-lambda)
and takes:
a set of package names
a ātargetā opam repository where the rewritten package definitions are to be saved
the cross toolchain name (e.g. x86_64_al2023)
It then:
uses the opam solver to resolve the packages and their runtime dependencies (test, dev, doc are dropped; depopt should be added although this does not seem to be working)
generate a new package with name <package>-cross-<cross_toolchain>
rewrite the build commands to add the toolchain name and set the package name explicitly (this is started for dune and topkg builders)
I see this whole work as transitory - Iād really love to get something like this working automatically with duneās new package management support - but for me, this is both a demo of what is possible, a way to explore requirements, and a way to learn about dune and opam more deeply.