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.
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.
| 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 //... |
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.
- 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.
- 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
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.
GHC_FLAGS: the standard set of Cabal flags that all targets should use.EXECUTABLE_FLAGS: ditto, but with executable-specific flags.
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.
bazel build --output_filter=REGEXPdoes what it says on the tin.