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.yaml
file. - Make sure it's linked into the
WORKSPACE
file, in thestack_snapshot
call. - Make sure it's present in your target's
deps
field.
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.build
at 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=1
in your.profile
or whatnot.
- Bazel manual: https://docs.bazel.build/versions/3.3.0/bazel-overview.html
rules_haskell
manual: https://rules-haskell.readthedocs.iorules_haskell
API 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=REGEXP
does what it says on the tin.