I’ll add to that Bazel’s notion of toolchain, which I think is probably unique. It’s pretty slick, and particularly apropos for OCaml. Obviously you may have multiple toolchains if you’re doing cross-platform development, but Ocaml comes with no fewer than eight toolchains right out of the box: ocamlc, ocamlopt, ocamlcp, ocamloptp, in .byte and .opt versions. Then we have flamba and who knows what else.
So how should a build language express toolchain control? Most have a build “mode” flag for bytecode v. native; I don’t know what they do about the profiling compilers. But flags don’t scale - what happens when you start adding true cross-compiling OCaml toolchains? And what about building the standard toolchain with different C compilers? (FYI Bazel toolchains for LLVM and Zig are available and I’ve used them to build the OCaml C kernel. Took about 10 minutes to get them working.)
With Bazel you can register all the toolchains you want - with selection characteristics like build/target host platform - and at build time it will select the one that best fits the “profile” of your build. You set buildhost and targethost platforms at the command line and Bazel takes care of the rest. You can also use user-defined CLI flags to control tc selection. For example you might configure things so that using Bazel’s built-in --compilation_mode=opt
switch serves to select the flambda toolchain.
I’ve used toolchains instead of a ‘compile mode’ flag to support native v. bytecode compiles. Think of bytecode as what runs on a ‘vm’ platform, and native code as what runs on a ‘sys’ platform. Then our 4 basic compilers are vm>vm, sys>sys, vm>sys, and sys>vm. The default is sys>sys. If you want to emit bytecode, you pass --platforms=@ocaml//host/target:vm
on the command line (this can be abbreviated). The beauty of this is that the build rules don’t have to worry about it - Bazel will select the right toolchain. So long as they all have the same tool interface, the rules don’t care. There are cases where the build rule needs to know if the target is the vm (e.g. in order to emit the right commands to configure a runtime) but they don’t need to know anything about the toolchain.
A very nifty illustration of how cool this is, is that any build tools that your project needs (e.g. some kind of preprocessing, or in fact menhir, ocamllex, etc.) are automatically switched to the appropriate toolchain, one that targets the build host, which is what you want. Also, with js_of_ocaml, you can use the sys>sys toolchain to build jsoo itself, but the rules that use it to transpile will automatically switch the toolchain so that the input to the jsoo compile is built with a vm-targeted OCaml toolchain.
It’s very OCaml-like in a way. Toolchains provide a ToolchainInfo struct that contains the tools, which the rules then use. That struct is essentially a toolchain interface. Bazel figures out at build time which implementation (specific toolchain) should be bound to that interface, so to speak.
Izzat cool or what?
Gregg