Development environment and workflow Last updated Feb 11, 2025

Currently, released versions of OBazl rules and tools are registered in the OBazl registry. Eventually they will be migrated to the Bazel Central Registry, but in the meantime, to use them you must put the following in a bazelrc file:

common --registry=https://raw.githubusercontent.com/obazl/registry/main/
common --registry=https://bcr.bazel.build

However, code under development is not published to any registry and so cannot be accessed via the bzlmod package management system. But OBazl is open-source and development is public, so you are welcome to fork a repository and offer contributions or just eavesdrop.

To use unreleased versions of a module (whether rules_ocaml or one of the other OBazl modules) you must use one of the following methods:

  1. Create a local registry with source.json files of type git_repository

  2. Use the git_override method in your MODULE.bazel file, specifying the branch you want; or

  3. Fork/clone the repo to your local drive, and then use either

    • local_path_override in your MODULE.bazel file, or

    • --override_module option on the command line or in a bazelrc file.

Even if you use one of these methods, you must nonetheless always use the --registry directives as shown above. This is because the module may itself depend on resources only accessible in those registries.

I develop primarily on a Mac (arm) and test on Linux (Ubuntu, x86).

The next section describes the configuration I use for development using these techniques.

Local registry

This is the easiest and most flexible method.

Summary: create a local registry , whose records will point either to local copies of repos or to commits in a git service. Then use --registry=…​ to use this local registry, and you will pick any changes to local repos automatically.

With this method you avoid cluttering your MODULE.bazel files with override code that will have to be deleted eventually anyway, and you don’t need to keep track of which modules have overrides and which don’t.

Another advantage of this approach is that it facilitates sharing a configuration across hosts, so that modules under development can be tested without publishing to a registry. Each developer on each host maintains a copy of the local registry, and can update the source.json files as needed.

Registries are very simple. A local registry looks like this:

$HOME/obazl/registry
.
├── bazel_registry.json           (1)
├── modules
│ ├── module_a
│ ├── module_b
.
.
.
1 Contents: { "module_base_path": "../../" }

An individual module record looks like this:

$HOME/obazl/registry/modules
module_a
└── 1.0.0.dev
    ├── MODULE.bazel -> /path/to/local/module_a/MODULE.bazel
    └── source.json

Pretty simple. The source.json file will have one of the following two forms:

local source
{ "type": "local_path",
  "path": "relpath/to/module_a" }       (1)
1 path will be interpreted relative to the module_base_path set in the bazel_registry.json file as noted above.
remote source
{
    "type":   "git_repository",
    "remote": "git@github.com:<user>/module_a.git",
    "commit": "bb43b97e95b7e02b120c43373e52e3e86d065230",
}

Use a local source record until your code is stable enough for testing or sharing on other platforms. Then create and push a commit, and switch the registry record to use the remote source format. Repeat, updating the commit field of the registry record, until ready to publish.

With this method you do not need to cut a release or prerelease archive, or publish to a shared bzlmod registry, in order to share code under development. Instead you “distribute” a local registry (which can itself be put under version control).

POLICY always use "dev ids" for code under development. OBazl convention is to use <version>.dev for both the module Id and the branch name. Such Ids are never used for published versions, so it is easy to keep track of what has been published (e.g. when viewing the output of ‘bazel mod graph`).

Using dev versions of the repos

I use the following branches:

  • <version>.dev is where active development takes place. Unstable.

For example, to depend directly on the 5.0.0.dev branch of the rules_ocaml repo:

MODULE.bazel
bazel_dep(name = "rules_ocaml", version = "5.0.0")
git_override(module_name = "rules_ocaml",
             branch = "5.0.0.dev",                                     (1)
             remote = "https://github.com/obazl/rules_ocaml.git")
1 You can depend on whichever branch you prefer.

The override tells Bazel to use the head commit of the named branch. Since that may change, if you use this method, you will need to run bazel clean --expunge in order to pick up any new commits on the branch you are using.

Alternatively, you can fork the rules_ocaml repo and clone it to your local machine (for example, to /home/<uid>/obazl/rules_ocaml), and then use either local_path_override in your MODULE.bazel or --override_module in .bazelrc.

For example:

MODULE.bazel
bazel_dep(name = "rules_ocaml", version = "5.0.0")
local_path_override(module_name = "rules_ocaml", path="/home/<uid>/obazl/rules_ocaml")

or you can omit the local_path_override directive and instead do this:

.bazelrc
common --override_module=rules_ocaml=/home/<uid>/obazl/rules_ocaml

If you use this method, you will of course need to sync your fork with the upstream repo in order to pick up any new commits.

Be sure to check out the branch of interest after cloning! When you use overrides like this, version compatibility checks are disabled - you get whatever you ask for. This is especially important if you are working with multiple modules with interdependencies. For example, tools_opam and rules_ocaml - the former depends on the latter. If you override both of them to point to local copies, it’s up to you to make sure the local copies are correctly configured.

bazelrc

See Write bazelrc configuration files for more information.

You home bazelrc file is the place to specify configurations you use across multiple Bazel projects. Mine contains:

$HOME/.bazelrc
common --symlink_prefix=.bazel/
common --color=yes
common:show --subcommands=True
common:show --verbose_failures
common:showpp --subcommands=pretty_print
common:showpp --verbose_failures
test:showt --test_output=all
test:showt --keep_going

common:q --noshow_progress
common:q --noshow_loading_progress
common:q --show_result=99
## ui events: fatal, error, warning, info, progress, debug,
## start, finish, subcommand, stdout, stderr, pass, fail,
## timeout, cancelled or depchecker
common:q --ui_event_filters=-info
common:q --ui_event_filters=-progress
common:q --ui_event_filters=-subcommand
common:q --ui_event_filters=-fail
# common:q --ui_event_filters=-error
# common:q --ui_event_filters=-stdout
# common:q --ui_event_filters=-stderr

The show* configuration groups (see --config) make it easy to toggle verbosity for particular build commands, e.g.

$ bazel build mwe/hello:Hello --config=showpp

By default, bazel test will only print the pass/fail result of tests, and will stop at the first failure. Passing --config=showt tells bazel to print a little more information for each test, including anything written to stdout, and to keep going even when tests fail.

The q (quiet) config group suppresses all Bazel messages except those written to stdout and stderr.

For example, if you run $ bazel build mwe/... (NB: three dots) then Bazel will build all the targets in the mwe package, but it will only print summary information such as

INFO: Analyzed 55 targets (0 packages loaded, 0 targets configured).
INFO: Found 55 targets...
INFO: Elapsed time: 0.072s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action

Passing --config=q sets --show_result=99, which tells Bazel to print information about output files. So $ bazel build mwe/... --config=q will print information about each target:

$ bazel build mwe/... --config=q
Target //mwe/hello_library:hello.exe up-to-date:
  .bazel/bin/mwe/hello_library/hello.exe
Target //mwe/hello_ns_archive:test up-to-date:
  .bazel/bin/mwe/hello_ns_archive/test
Target //mwe/hello_ns_library:libHello up-to-date:
  .bazel/out/darwin_arm64-fastbuild-ST-e73fda9ac620/bin/mwe/hello_ns_library/__obazl/LibHello__Easy.cmx
  .bazel/out/darwin_arm64-fastbuild-ST-e73fda9ac620/bin/mwe/hello_ns_library/__obazl/LibHello__Simple.cmx
Target //mwe/hello_library:Easy up-to-date:
  .bazel/bin/mwe/hello_library/__obazl/Easy.cmx
...