Bazel build instructions
July 10, 2020 · View on GitHub
The Semantic project supports builds with the Bazel build system. This is unconventional—most Haskell projects either use Cabal or Stack. However, for a project of Semantic's size, Bazel has many advantages. Some reasons you might want to use Bazel:
- Bazel uses content-addressed hashing and reproducible builds to provide sophisticated caching. Situations where Cabal invalidates caches can result in cascading build requirements, causing many rebuilds of the language syntax packages.
- Bazel's tooling is (on Emacs with lsp-mode and lsp-haskell) more reliable.
- Bazel gets Haskell dependencies from Stackage LTS versions, so we avoid the rebuilds associated with living on the latest Hackage snapshot.
How do I get started?
Assuming you're on macOS, run the script located at script/bootstrap-bazel. This uses Homebrew to install Bazel and creates the .bazel-cache directory.
The first time you run bazel build, it'll take some time, as Bazel will compile all of Stackage. Fear not: you won't have to do this again.
cabal → bazel cheatsheet
| Operation | cabal | bazel |
|---|---|---|
| Build all | cabal build all | bazel build //... |
Build TARGET library | cabal build TARGET:lib | bazel build //TARGET |
| Build semantic executable | cabal build semantic:exe:semantic | bazel build //semantic:exe |
| Build/run executable | cabal run semantic -- ARGS | bazel run //semantic:exe -- ARGS |
| Load REPL component | script/ghci and :load | bazel build //TARGET@repl |
| Run tests | cabal test all | bazel test //... |
| Build with optimizations | cabal build --flags="+release" | bazel build -c opt //... |
| Run all languages' AST tests | ETOOLONGTOWRITE | bazel test --test_tag_filters=language-test //... |
Adding a new dependency
Here's a breakdown of how to add a new package.
- Make sure it's present in Stackage LTS 13.15. If not, add the package (versioned exactly) to the
stack-snapshot.yamlfile. - Make sure it's linked into the
WORKSPACEfile, in thestack_snapshotcall. - Make sure it's present in your target's
depsfield.
If this seems complicated, don't worry: most of the time you'll be able to skip this first point, and you'll often be able to skip the second.
Things to know
- Don't generally run
bazel clean. Since Bazel builds are reproducible, there's very little reason to clean, unless somehow your whole cache got irrevocably corrupted. - You can load a REPL for any target by appending
@repl. (with the exception of the language packages, due to this). - Some packages come with GHC and are not loaded from Stackage. These include
base,containers, and others. To depend on those packages, you use//:base,//:containers, etc. They are specified in theBAZEL.buildat the project root. You probably won't need to add any more. - Getting weird errors from the C compiler? Try setting
export BAZEL_USE_CPP_ONLY_TOOLCHAIN=1in your.profileor whatnot.
Quick reference links
- Bazel manual: https://docs.bazel.build/versions/3.3.0/bazel-overview.html
rules_haskellmanual: https://rules-haskell.readthedocs.iorules_haskellAPI docs: https://api.haskell.build
Conventions
We give library targets the same name as their subproject. Test targets are called test, and executable targets are exe.
The default .bazelrc file imports a .bazelrc.local file if it's present; use that for any Bazel customizations you want.
The variables that the scripts under build/ export are SCREAMING_SNAKE_CASE. The functions are snake_case.
Shared variables
GHC_FLAGS: the standard set of Cabal flags that all targets should use.EXECUTABLE_FLAGS: ditto, but with executable-specific flags.
Custom rules
We have two common custom rules, defined in build/common.bzl. The first, tree_sitter_node_types_release, uses the http_archive rule to download a specified tree-sitter grammar's node-types.json file. These calls declare new top-level targets, so they're only present in the top-level WORKSPACE file. The second, semantic_language_library, takes care of the boilerplate associated with declaring a target for a semantic-LANG language package (as these packages' contents are identical, their target declarations are almost identical).
For the purposes of setting up the examples upon which the parse-examples test depends, we have code in build/example_repos.bzl which defines them, checks them out, and computes the set of target names. You shouldn't need to change or modify this, unless you're adding new repos.
Protips
bazel build --output_filter=REGEXPdoes what it says on the tin.