[ANN] The OBazl Toolsuite 3.0.0.beta.1

The OBazl Toolsuite 3.0.0.beta.1 is now available.

The OBazl Toolsuite is a collection of rules & tools that support OCaml development using Bazel. To get started:

$ git clone https://github.com/obazl/demo_hello.git
$ cd demo_hello
$ bazel run bin:greetings
$ bazel test test
$ bazel run @utop
etc.

See The OBazl Book for more guidance.

Tested on MacOS and Linux (Ubuntu).

This version contains many improvements:

  • Improved toolchain support. Select a compiler by passing e.g. --tc=ocamlc.
  • Seamless opam dependencies. The previous version required a preprocessing step (running bazel run @coswitch); this is no longer necessary.
  • Fine-grained dependencies. Depend directly on any module, whether it is in a library or not, and whether it is namespaced (``wrapped’') or not.
  • Context-sensitive archiving. Archives are for distribution; internal dependencies do not need them. The ocaml_library rule will only construct an archive on demand. By default, an internal dependency on an ocaml_library target will not request archiving. This can be overridden.
  • Several examples of OBazl extensions: rules_ppx, rules_cppo, rules_ctypes, rules_menhir. These demonstrate the relative ease with which tools can be integrated into the Bazel environment.
  • A new tool, bazel run @obazl//new that generates a project from a template.
  • Direct support for the tools in the standard SDK (ocamldebug, ocamlobjinfo, etc.) and for a subset of the OCaml Platform tools. For example:
    • $ bazel run @opam -- list
    • $ bazel run @ocaml
    • $ bazel run @utop
    • $ bazel run @dbg --@dbg//pgm=src:greetings

OBazl ensures that these commands will be invoked under the
correct switch, with correct paths (CAML_LD_LIBRARY_PATH etc.), insulated from environment variables.

Other tools are invoked by passing an option to an ordinary build command. For example:

  • $ bazel build lib/hello:Hello --config=modinfo # runs ocamlobjinfo on the .cmo/.cmx output
  • $ bazel build lib/hello:Hello --config=siginfo # runs ocamlobjinfo on the .cmi output
  • $ bazel build lib/hello:libFoo --config=archinfo # runs ocamlobjinfo on the .cma/.cmxa output
  • $ bazel build lib/hello:Hello --config=gensig # runs ocamlopt -i on the .ml file to generate inteface code.

The documentation at The OBazl Book has been updated. It remains far from complete but it should be useful. In particular the OBazl Guide and the rules_ocaml Reference Manual.

What’s missing?

  • Support for opam publishing. I have successfully published an OBazl (Bazel) project to an opam switch, and used it in a dune-only project, but the code is still under development so I don’t have a demo.
  • Support for odoc, ocamlformat, and linting. Currently under development.
  • Windows support. The code is designed for portability but it will probably be a while before I can get to Windows.
  • Automatic generation of BUILD.bazel files. I have a tool for this but it is outdated. Bringing it up-to-date is a high priority.

Support:

Cheers!

Gregg

6 Likes

P.S. fwiw, I, who hate all build tools with a white-hot passion, have come to actually enjoy Bazel coding. May Gob have mercy on my soul.

This is great, thank you for working on this.

I have some multi-language projects that I’m using Bazel for and I’m finding it’s less terrible than I’d expected. In learning Bazel I’m hoping to just learn one new build system to cover C/C++/Java.

Does this new version depend on Bazel 8 or could it be made to work with 7.4?

Thanks for all the work!

I see that in most BUILD.bazel files the dependencies between modules are “hardcoded”. Is there a way to integrate with ocamldep or codept?

Sorry, Bazel 8 and up. Making it work with previous versions would have been a pain, so I dropped it, on grounds that Bazel users really should migrate to 8. Sorry about that.

That would be the job of the BUILD-file generator that I’m working on. So it’s a bit like Ninja: you can write the build files by hand but it would generally be better to have a tool generate them.

1 Like

I use nix for ocaml packages and development which completely bypasses opam. Is opam switch a hard requirement for rules_ocaml?

The short answer is Yes and No.

No, because the rules depend on a toolchain interface, and its up to the user to provide an implementation at build-time. That’s what tools_opam does:

use_repo(opam, "opam.ocamlsdk")
register_toolchains("@opam.ocamlsdk//toolchain/selectors/local:all")
register_toolchains("@opam.ocamlsdk//toolchain/profiles:all")

To use some non-opam toolchain one would write some starlark code.

Yes, in that I’ve only implemented the toolchain for opam. If you were using a findlib installation without opam, it would be relatively easy to write a tools_findlib package that would work with rules_ocaml.

Unfortunately I know almost nothing about nix. But I know that Tweag has opam-nix; is that what you’re using? If so, then you are in fact using opam, indirectly. It should be possible to write a toolchain adapter that would work with rules_ocaml. If you can provide a minimum working example, a simple “hello world” project that uses at least one external dependency, then I’ll take a look at it and see what we can do to accomodate nix.

If your system really does "completely bypasses opam’', then I’d need to know more about it in order to estimate how much work it would be to integrate it.

HTH,

Gregg

PS. Toolchain adapters are pretty simple. You can inspect the tools_opam adapters if you’ve cloned the demo_hello example:

bazel cquery @opam.ocamlsdk//toolchain/adapters/local:all --output=build

The default ocamlopt.opt adapter looks like this:

ocaml_toolchain_adapter(
  name = "ocamlopt.opt",
  host = "sys",
  target = "sys",
  default_runtime = "@opam.ocamlsdk//runtime:libasmrun.a",
  std_runtime = "@opam.ocamlsdk//runtime:libasmrun.a",
  dbg_runtime = "@opam.ocamlsdk//runtime:libasmrund.a",
  instrumented_runtime = "@opam.ocamlsdk//runtime:libasmruni.a",
  pic_runtime = "@opam.ocamlsdk//runtime:libasmrun_pic.a",
  shared_runtime = "@opam.ocamlsdk//runtime:libasmrun_shared.so",
  repl = "@opam.ocamlsdk//bin:ocaml",
  stublibs = ["@opam.ocamlsdk//stublibs/lib:lib", "@@tools_opam++opam+opam.stublibs//lib:lib"],
  stublibs_path = ["@opam.ocamlsdk//stublibs/lib:path", "@@tools_opam++opam+opam.stublibs//lib:path"],
  compiler = "@opam.ocamlsdk//bin:ocamlopt.opt",
  sigcompiler = "@opam.ocamlsdk//bin:ocamlc.opt",
  version = "@opam.ocamlsdk//version:version",
  profiling_compiler = "@opam.ocamlsdk//bin:ocamloptp",
  ocamllex = "@opam.ocamlsdk//bin:ocamllex.opt",
  ocamlyacc = "@opam.ocamlsdk//bin:ocamlyacc",
  objinfo = "@opam.ocamlsdk//bin:ocamlobjinfo.opt",
  ocamlcmt = "@opam.ocamlsdk//bin:ocamlcmt",
  ocamldoc = "@opam.ocamlsdk//bin:ocamldoc.opt",
  linkmode = "dynamic",
)

PS. That’s a feature, not a bug. Bazel requires that all inputs and outputs be explicitly registered, in order to guarantee replicable builds. In principle it might be possible to support globbing of all source files in a directory (which is basically what Dune does), but at a price. That would substantially complicate the build logic, since it would require an additional step to analyze the dependencies. But even worse (IMO) it would compromise separate compilation. With fine-grained dependencies you can optimize your build in ways that would not be possible with globbing. It would also compromise querying possibilities. With explicit fine-grained deps you can run fine-grained queries - for example, to discover which .ml files lack a corresponding .mli file (or vice-versa), etc.

Having said that, it might be possible to support globbing like that. It would be a nice enhancement, but the implementation would be fairly complicated, I suspect.

I am using ocaml with nix only. So no opam-nix. Most of the packages - both ocaml and system - that I need are already in nixpkgs so opam really is redundant when I use nix.