feat(third_party/bazel): Check in rules_haskell from Tweag

This commit is contained in:
Vincent Ambo 2019-07-04 11:18:12 +01:00
parent 2eb1dc26e4
commit f723b8b878
479 changed files with 51484 additions and 0 deletions

View file

@ -0,0 +1 @@
_build

View file

@ -0,0 +1,46 @@
load("@io_bazel_skydoc//skylark:skylark.bzl", "skylark_doc")
genrule(
name = "guide_html",
srcs = ["conf.py"] + glob(["*.rst"]),
outs = ["guide_html.zip"],
cmd = """
set -euo pipefail
# Nixpkgs_rules are pointing to every bins individually. Here
# we are extracting the /bin dir path to append it to the $$PATH.
CWD=`pwd`
sphinxBinDir=$${CWD}/$$(echo $(locations @sphinx//:bin) | cut -d ' ' -f 1 | xargs dirname)
dotBinDir=$${CWD}/$$(echo $(locations @graphviz//:bin) | cut -d ' ' -f 1 | xargs dirname)
zipBinDir=$${CWD}/$$(echo $(locations @zip//:bin) | cut -d ' ' -f 1 | xargs dirname)
PATH=$${PATH}:$${sphinxBinDir}:$${dotBinDir}:$${zipBinDir}
sourcedir=$$(dirname $(location conf.py))
builddir=$$(mktemp -d rules_haskell_docs.XXXX)
sphinx-build -M html $$sourcedir $$builddir -W -N -q
(cd $$builddir/html && zip -q -r $$CWD/$@ .)
rm -rf $$builddir
""",
tools = [
"@graphviz//:bin",
"@sphinx//:bin",
"@zip//:bin",
],
)
skylark_doc(
name = "api_html",
srcs = [
# The order of these files defines the order in which the corresponding
# sections are presented in the docs.
"//haskell:haskell.bzl",
"//haskell:haddock.bzl",
"//haskell:lint.bzl",
"//haskell:toolchain.bzl",
"//haskell:protobuf.bzl",
"//haskell:cc.bzl",
"//haskell:repositories.bzl",
"//haskell:ghc_bindist.bzl",
"//haskell:nixpkgs.bzl",
],
format = "html",
)

View file

@ -0,0 +1,41 @@
project = 'rules_haskell'
copyright = '2018, The rules_haskell authors'
source_suffix = '.rst'
extensions = [
'sphinx.ext.graphviz',
'sphinx.ext.todo',
]
master_doc = 'index'
language = None
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
pygments_style = 'sphinx'
html_theme = 'alabaster'
html_theme_options = {
'show_powered_by': False,
'github_user': 'tweag',
'github_repo': 'rules_haskell',
'github_banner': True,
'github_type': "star",
'show_related': False,
'note_bg': '#FFF59C',
}
html_show_sphinx = False
todo_include_todos = True
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass).
latex_documents = [
(master_doc, 'rules_haskell.tex', 'rules\\_haskell Documentation',
'Tweag I/O', 'manual'),
]

View file

@ -0,0 +1,283 @@
.. _use-cases:
Common Haskell Build Use Cases
==============================
Picking a compiler
------------------
Unlike Bazel's native C++ rules, rules_haskell does not auto-detect
a Haskell compiler toolchain from the environment. This is by design.
We require that you declare a compiler to use in your ``WORKSPACE``
file.
There are two common sources for a compiler. One is to use the
official binary distributions from `haskell.org`_. This is done using
the `ghc_bindist`_ rule.
The compiler can also be pulled from Nixpkgs_, a set of package
definitions for the `Nix package manager`_. Pulling the compiler from
Nixpkgs makes the build more hermetic, because the transitive closure
of the compiler and all its dependencies is precisely defined in the
``WORKSPACE`` file. Use `rules_nixpkgs`_ to do so (where ``X.Y.Z``
stands for any recent release)::
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_tweag_rules_nixpkgs",
strip_prefix = "rules_nixpkgs-X.Y.Z",
urls = ["https://github.com/tweag/rules_nixpkgs/archive/vX.Y.Z.tar.gz"],
)
load(
"@io_tweag_rules_nixpkgs//nixpkgs:nixpkgs.bzl",
"nixpkgs_git_repository",
"nixpkgs_package"
)
nixpkgs_git_repository(
name = "nixpkgs",
revision = "18.09", # Any tag or commit hash
)
nixpkgs_package(
name = "ghc",
repositories = { "nixpkgs": "@nixpkgs//:default.nix" }
attribute_path = "haskell.compiler.ghc843", # Any compiler version
build_file = "@io_tweag_rules_haskell//haskell:ghc.BUILD",
)
register_toolchains("//:ghc")
This workspace description specifies which Nixpkgs version to use,
then exposes a Nixpkgs package containing the GHC compiler. The
description assumes that there exists a ``BUILD`` file at the root of
the repository that includes the following::
haskell_toolchain(
name = "ghc",
# Versions here and in WORKSPACE must match.
version = "8.4.3",
# Use binaries from @ghc//:bin to define //:ghc toolchain.
tools = ["@ghc//:bin"],
)
.. _Bazel+Nix blog post: https://www.tweag.io/posts/2018-03-15-bazel-nix.html
.. _Nix package manager: https://nixos.org/nix
.. _Nixpkgs: https://nixos.org/nixpkgs/manual/
.. _ghc_bindist: http://api.haskell.build/haskell/ghc_bindist.html#ghc_bindist
.. _haskell.org: https://haskell.org
.. _haskell_binary: http://api.haskell.build/haskell/haskell.html#haskell_binary
.. _haskell_library: http://api.haskell.build/haskell/haskell.html#haskell_library
.. _rules_nixpkgs: https://github.com/tweag/rules_nixpkgs
Loading targets in a REPL
-------------------------
Rebuilds are currently not incremental *within* a binary or library
target (rebuilds are incremental across targets of course). Any change
in any source file will trigger a rebuild of all source files listed
in a target. In Bazel, it is conventional to decompose libraries into
small units. In this way, libraries require less work to rebuild.
Still, for interactive development full incrementality and fast
recompilation times are crucial for a good developer experience. We
recommend making all development REPL-driven for fast feedback when
source files change.
Every `haskell_binary`_ and every `haskell_library`_ target has an
optional executable output that can be run to drop you into an
interactive session. If the target's name is ``foo``, then the REPL
output is called ``foo@repl``.
Consider the following binary target::
haskell_binary(
name = "hello",
srcs = ["Main.hs", "Other.hs"],
deps = ["//lib:some_lib"],
)
The target above also implicitly defines ``hello@repl``. You can call
the REPL like this (requires Bazel 0.15 or later)::
$ bazel run //:hello@repl
This works for any ``haskell_binary`` or ``haskell_library`` target.
Modules of all libraries will be loaded in interpreted mode and can be
reloaded using the ``:r`` GHCi command when source files change.
Building code with Hackage dependencies (using Nix)
---------------------------------------------------
Each Haskell library or binary needs a simple build description to
tell Bazel what source files to use and what the dependencies are, if
any. Packages on Hackage don't usually ship with `BUILD.bazel` files.
So if your code depends on them, you either need to write a build
description for each package, generate one (see next section), or
decide not to use Bazel to build packages published on Hackage. This
section documents one way to do the latter.
Nix is a package manager. The set of package definitions is called
Nixpkgs. This repository contains definitions for most actively
maintained Cabal packages published on Hackage. Where these packages
depend on system libraries like zlib, ncurses or libpng, Nixpkgs also
contains package descriptions for those, and declares those as
dependencies of the Cabal packages. Since these definitions already
exist, we can reuse them instead of rewriting these definitions as
build definitions in Bazel. See the `Bazel+Nix blog post`_ for a more
detailed rationale.
To use Nixpkgs in Bazel, we need `rules_nixpkgs`_. See `Picking
a compiler`_ for how to import Nixpkgs rules into your workspace and
how to use a compiler from Nixpkgs. To use Cabal packages from
Nixpkgs, replace the compiler definition with the following::
nixpkgs_package(
name = "ghc",
repositories = { "nixpkgs": "@nixpkgs//:default.nix" },
nix_file = "//:ghc.nix",
build_file = "@io_tweag_rules_haskell//haskell:ghc.BUILD",
)
This definition assumes a ``ghc.nix`` file at the root of the
repository. In this file, you can use the Nix expression language to
construct a compiler with all the packages you depend on in scope::
with (import <nixpkgs> {});
haskellPackages.ghcWithPackages (p: with p; [
containers
lens
text
])
Each package mentioned in ``ghc.nix`` can then be imported using
`haskell_toolchain_library`_ in ``BUILD`` files.
.. _haskell_toolchain_library: http://api.haskell.build/haskell/haskell.html#haskell_toolchain_library
Building code with Hackage dependencies (using Hazel)
-----------------------------------------------------
.. todo::
Explain how to use Hazel instead of Nix
Generating API documentation
----------------------------
The `haskell_doc`_ rule can be used to build API documentation for
a given library (using Haddock). Building a target called
``//my/pkg:mylib_docs`` would make the documentation available at
``bazel-bin/my/pkg/mylib_docs/index/index.html``.
Alternatively, you can use the
``@io_tweag_rules_haskell//haskell:haskell.bzl%haskell_doc_aspect``
aspect to ask Bazel from the command-line to build documentation for
any given target (or indeed all targets), like in the following:
.. code-block:: console
$ bazel build //my/pkg:mylib \
--aspects @io_tweag_rules_haskell//haskell:haskell.bzl%haskell_doc_aspect
.. _haskell_doc: http://api.haskell.build/haskell/haddock.html#haskell_doc
Linting your code
-----------------
The `haskell_lint`_ rule does not build code but runs the GHC
typechecker on all listed dependencies. Warnings are treated as
errors.
Alternatively, you can directly check a target using
.. code-block:: console
$ bazel build //my/haskell:target \
--aspects @io_tweag_rules_haskell//haskell:haskell.bzl%haskell_lint_aspect
.. _haskell_lint: http://api.haskell.build/haskell/lint.html#haskell_lint
Checking code coverage
----------------------
"Code coverage" is the name given to metrics that describe how much source
code is covered by a given test suite. A specific code coverage metric
implemented here is expression coverage, or the number of expressions in
the source code that are explored when the tests are run.
Haskell's ``ghc`` compiler has built-in support for code coverage analysis,
through the hpc_ tool. The Haskell rules allow the use of this tool to analyse
``haskell_library`` coverage by ``haskell_test`` rules. To do so, you have a
few options. You can add
``expected_covered_expressions_percentage=<some integer between 0 and 100>`` to
the attributes of a ``haskell_test``, and if the expression coverage percentage
is lower than this amount, the test will fail. Alternatively, you can add
``expected_uncovered_expression_count=<some integer greater or equal to 0>`` to
the attributes of a ``haskell_test``, and instead the test will fail if the
number of uncovered expressions is greater than this amount. Finally, you could
do both at once, and have both of these checks analyzed by the coverage runner.
To see the coverage details of the test suite regardless of if the test passes
or fails, add ``--test_output=all`` as a flag when invoking the test, and there
will be a report in the test output. You will only see the report if you
required a certain level of expression coverage in the rule attributes.
For example, your BUILD file might look like this: ::
haskell_library(
name = "lib",
srcs = ["Lib.hs"],
deps = [
"//tests/hackage:base",
],
)
haskell_test(
name = "test",
srcs = ["Main.hs"],
deps = [
":lib",
"//tests/hackage:base",
],
expected_covered_expressions_percentage = 80,
expected_uncovered_expression_count = 10,
)
And if you ran ``bazel coverage //somepackage:test --test_output=all``, you
might see a result like this: ::
INFO: From Testing //somepackage:test:
==================== Test output for //somepackage:test:
Overall report
100% expressions used (9/9)
100% boolean coverage (0/0)
100% guards (0/0)
100% 'if' conditions (0/0)
100% qualifiers (0/0)
100% alternatives used (0/0)
100% local declarations used (0/0)
100% top-level declarations used (3/3)
=============================================================================
Here, the test passes because it actually has 100% expression coverage and 0
uncovered expressions, which is even better than we expected on both counts.
There is an optional ``haskell_test`` attribute called
``strict_coverage_analysis``, which is a boolean that changes the coverage
analysis such that even having better coverage than expected fails the test.
This can be used to enforce that developers must upgrade the expected test
coverage when they improve it. On the other hand, it requires changing the
expected coverage for almost any change.
There a couple of notes regarding the coverage analysis functionality:
- Coverage analysis currently is scoped to all source files and all
locally-built Haskell dependencies (both direct and transitive) for a given
test rule.
- Coverage-enabled build and execution for ``haskell_test`` targets may take
longer than regular. However, this has not effected regular ``run`` /
``build`` / ``test`` performance.
.. _hpc: <http://hackage.haskell.org/package/hpc>

View file

@ -0,0 +1,364 @@
.. _guide:
Introduction to Bazel: Building a Haskell project
=================================================
In this tutorial, you'll learn the basics of building Haskell
applications with Bazel. You will set up your workspace and build
a simple Haskell project that illustrates key Bazel concepts, such as
targets and ``BUILD.bazel`` files. After completing this tutorial, take
a look at :ref:`Common Haskell build use cases <use-cases>` for
information on more advanced concepts such as writing and running
Haskell tests.
What you'll learn
-----------------
In this tutorial you'll learn how to:
* build a target,
* visualize the project's dependencies,
* split the project into multiple targets and packages,
* control target visibility across packages,
* reference targets through labels.
Before you begin
----------------
To prepare for the tutorial, first `install Bazel`_ if you don't have
it installed already. Then, retrieve the ``rules_haskell`` GitHub
repository::
git clone https://github.com/tweag/rules_haskell/
The sample project for this tutorial is in the ``tutorial``
directory and is structured as follows::
rules_haskell
└── tutorial
├── WORKSPACE
├── main
│ ├── BUILD.bazel
│ └── Main.hs
└── lib
├── BUILD.bazel
└── Bool.hs
The first thing to do is to::
$ cd tutorial
Build with Bazel
----------------
Set up the workspace
^^^^^^^^^^^^^^^^^^^^
Before you can build a project, you need to set up its workspace.
A workspace is a directory that holds your project's source files and
Bazel's build outputs. It also contains files that Bazel recognizes as
special:
* the ``WORKSPACE`` file, which identifies the directory and its
contents as a Bazel workspace and lives at the root of the project's
directory structure,
* one or more ``BUILD.bazel`` files, which tell Bazel how to build different
parts of the project. (A directory within the workspace that
contains a ``BUILD.bazel`` file is a *package*. You will learn about
packages later in this tutorial.)
To designate a directory as a Bazel workspace, create an empty file
named ``WORKSPACE`` in that directory.
When Bazel builds the project, all inputs and dependencies must be in
the same workspace. Files residing in different workspaces are
independent of one another unless linked, which is beyond the scope of
this tutorial.
Understand the BUILD file
^^^^^^^^^^^^^^^^^^^^^^^^^
It is recommended to use a ``.bazel`` extension for each ``BUILD`` file to
avoid clashing with files or folders already using that name.
A ``BUILD.bazel`` file contains several different types of instructions for
Bazel. The most important type is the *build rule*, which tells Bazel
how to build the desired outputs, such as executable binaries or
libraries. Each instance of a build rule in the ``BUILD.bazel`` file is
called a *target* and points to a specific set of source files and
dependencies. A target can also point to other targets.
Take a look at the ``BUILD.bazel`` file in the ``tutorial/lib`` directory::
haskell_library(
name = "booleans",
srcs = ["Bool.hs"],
)
In our example, the ``booleans`` target instantiates the
`haskell_library`_ rule. The rule tells Bazel to build a reusable
(statically or dynamically linked) library from the ``Bool.hs`` source
file with no dependencies.
The attributes in the target explicitly state its dependencies and
options. While the ``name`` attribute is mandatory, many are optional.
For example, in the ``booleans`` target, ``name`` is self-explanatory,
and ``srcs`` specifies the source file(s) from which Bazel builds the
target.
Build the project
^^^^^^^^^^^^^^^^^
Let's build your sample project. Run the following command::
$ bazel build //lib:booleans
Notice the target label - the ``//lib:`` part is the location of our
``BUILD.bazel`` file relative to the root of the workspace, and ``booleans``
is what we named that target in the ``BUILD.bazel`` file. (You will learn
about target labels in more detail at the end of this tutorial.)
Bazel produces output similar to the following::
INFO: Found 1 target...
Target //lib:booleans up-to-date:
bazel-bin/lib/libZSbooleans/libZSbooleans.conf
bazel-bin/lib/libZSbooleans/package.cache
INFO: Elapsed time: 2.288s, Critical Path: 0.68s
Congratulations, you just built your first Bazel target! Bazel places
build outputs in the ``bazel-bin`` directory at the root of the
workspace. Browse through its contents to get an idea for Bazel's
output structure.
Review the dependency graph
^^^^^^^^^^^^^^^^^^^^^^^^^^^
A successful build has all of its dependencies explicitly stated in
the ``BUILD.bazel`` file. Bazel uses those statements to create the
project's dependency graph, which enables accurate incremental builds.
Let's visualize our sample project's dependencies. First, generate
a text representation of the dependency graph (run the command at the
workspace root)::
bazel query --nohost_deps --noimplicit_deps \
'deps(//lib:booleans)' --output graph
The above command tells Bazel to look for all dependencies for the
target ``//lib:booleans`` (excluding host and implicit dependencies)
and format the output as a graph.
Then, paste the text into GraphViz_.
On Ubuntu, you can view the graph locally by installing GraphViz and the xdot
Dot Viewer::
sudo apt update && sudo apt install graphviz xdot
Then you can generate and view the graph by piping the text output above
straight to xdot::
xdot <(bazel query --nohost_deps --noimplicit_deps \
'deps(//lib:booleans)' --output graph)
As you can see, the first stage of the sample project has a single
target that builds a single source file with no additional
dependencies:
.. digraph:: booleans
node [shape=box];
"//lib:booleans"
"//lib:booleans" -> "//lib:Bool.hs"
"//lib:Bool.hs"
Now that you have set up your workspace, built your project, and
examined its dependencies, let's add some complexity.
Refine your Bazel build
-----------------------
While a single target is sufficient for small projects, you may want
to split larger projects into multiple targets and packages to allow
for fast incremental builds (that is, only rebuild what's changed) and
to speed up your builds by building multiple parts of a project at
once.
Specify multiple build targets
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Let's split our sample project build into two targets. Take a look at
the ``BUILD.bazel`` files in the ``tutorial/lib`` and ``tutorial/main``
directories. The contents of both files could have been kept in
a single ``BUILD.bazel`` as follows::
haskell_library(
name = "booleans",
srcs = ["Bool.hs"],
)
haskell_toolchain_library(name = "base")
haskell_binary(
name = "demorgan",
srcs = ["Main.hs"],
compiler_flags = ["-threaded"],
deps = [":base", ":booleans"],
)
With this single ``BUILD.bazel`` file, Bazel first builds the ``booleans``
library (using the `haskell_library`_ rule), then the ``demorgan``
binary (which as an example uses the ``booleans`` library to check one
of the De Morgan laws). The ``deps`` attribute in the ``demorgan``
target tells Bazel that the ``:booleans`` library is required to build
the ``demorgan`` binary. The binary also requires the ``base``
built-in library that ships with GHC, to perform I/O among other
things. Libraries like ``base``, ``bytestring`` and others that ship
with GHC are special in that they are prebuilt outside of Bazel. To
import them as regular targets, we use the `haskell_toolchain_library`_ rule.
Let's build this new version of our project::
$ bazel build //main:demorgan
Bazel produces output similar to the following::
INFO: Found 1 target...
Target //main:demorgan up-to-date:
bazel-bin/main/demorgan
INFO: Elapsed time: 2.728s, Critical Path: 1.23s
Now test your freshly built binary::
$ bazel-bin/main/demorgan
Or alternatively::
$ bazel run //main:demorgan
If you now modify ``Bool.hs`` and rebuild the project, Bazel will
usually only recompile that file.
Looking at the dependency graph:
.. digraph:: demorgan
node [shape=box];
"//main:demorgan"
"//main:demorgan" -> "//main:base\n//main:Main.hs"
"//main:demorgan" -> "//lib:booleans"
"//lib:booleans"
"//lib:booleans" -> "//lib:Bool.hs"
"//lib:Bool.hs"
"//main:base\n//main:Main.hs"
You have now built the project with two targets. The ``demorgan``
target builds one source file and depends on one other target
(``//lib:booleans``), which builds one additional source file.
Use multiple packages
^^^^^^^^^^^^^^^^^^^^^
Lets now split the project into multiple packages.
Notice that we actually have two sub-directories, and each contains
a ``BUILD.bazel`` file. Therefore, to Bazel, the workspace contains two
packages, ``lib`` and ``main``.
Take a look at the ``lib/BUILD.bazel`` file::
haskell_library(
name = "booleans",
srcs = ["Bool.hs"],
visibility = ["//main:__pkg__"],
)
And at the ``main/BUILD.bazel`` file::
haskell_toolchain_library(name = "base")
haskell_binary(
name = "demorgan",
srcs = ["Main.hs"],
compiler_flags = ["-threaded"],
deps = [":base", "//lib:booleans"],
)
As you can see, the ``demorgan`` target in the ``main`` package
depends on the ``booleans`` target in the ``lib`` package (hence the
target label ``//lib:booleans``) - Bazel knows this through the
``deps`` attribute.
Notice that for the build to succeed, we make the ``//lib:booleans``
target in ``lib/BUILD.bazel`` explicitly visible to targets in
``main/BUILD.bazel`` using the ``visibility`` attribute. This is because by
default targets are only visible to other targets in the same
``BUILD.bazel`` file. (Bazel uses target visibility to prevent issues such
as libraries containing implementation details leaking into public
APIs.)
You have built the project as two packages with three targets and
understand the dependencies between them.
Use labels to reference targets
-------------------------------
In ``BUILD.bazel`` files and at the command line, Bazel uses *labels* to
reference targets - for example, ``//main:demorgan`` or
``//lib:booleans``. Their syntax is::
//path/to/package:target-name
If the target is a rule target, then ``path/to/package`` is the path
to the directory containing the ``BUILD.bazel`` file, and ``target-name`` is
what you named the target in the ``BUILD.bazel`` file (the ``name``
attribute). If the target is a file target, then ``path/to/package``
is the path to the root of the package, and ``target-name`` is the
name of the target file, including its full path.
When referencing targets within the same package, you can skip the
package path and just use ``//:target-name``. When referencing targets
within the same ``BUILD.bazel`` file, you can even skip the ``//`` workspace
root identifier and just use ``:target-name``.
Further reading
---------------
Congratulations! You now know the basics of building a Haskell project
with Bazel. Next, read up on the most common :ref:`Common Haskell
build use cases <use-cases>`. Then, check out the following:
* `External Dependencies`_ to learn more about working with local and
remote repositories.
* The `Build Encyclopedia`_ to learn more about Bazel.
* The `C++ build tutorial`_ to get started with building C++
applications with Bazel.
* The `Java build tutorial`_ to get started with building Java
applications with Bazel.
* The `Android application tutorial`_ to get started with building
mobile applications for Android with Bazel.
* The `iOS application tutorial`_ to get started with building mobile
applications for iOS with Bazel.
Happy building!
.. note:: This tutorial is adapted from the Bazel `C++ build tutorial`_.
.. _install Bazel: https://docs.bazel.build/versions/master/install.html
.. _haskell_binary: http://api.haskell.build/haskell/haskell.html#haskell_binary
.. _haskell_toolchain_library: http://api.haskell.build/haskell/haskell.html#haskell_toolchain_library
.. _haskell_library: http://api.haskell.build/haskell/haskell.html#haskell_library
.. _graphviz: https://www.graphviz.org/
.. _external dependencies: https://docs.bazel.build/versions/master/external.html
.. _build encyclopedia: https://docs.bazel.build/versions/master/be/overview.html
.. _C++ build tutorial: https://docs.bazel.build/versions/master/tutorial/cpp.html
.. _Java build tutorial: https://docs.bazel.build/versions/master/tutorial/java.html
.. _Android application tutorial: https://docs.bazel.build/versions/master/tutorial/android-app.html
.. _iOS application tutorial: https://docs.bazel.build/versions/master/tutorial/ios-app.html

View file

@ -0,0 +1,23 @@
.. meta::
:description: User guide for building Haskell code with Bazel.
Build Haskell Using Bazel
=========================
Bazel_ is a tool for automating the *building* and the *testing* of
software. Follow :ref:`this guide <guide>` to get started building
small Haskell projects using Bazel. For a deeper dive and solutions to
more advanced use cases, see :ref:`Common Haskell Build Use Cases
<use-cases>`. Refer to the `Bazel documentation`_ for more about
Bazel.
.. toctree::
:maxdepth: 2
:caption: Contents:
why-bazel
haskell
haskell-use-cases
.. _Bazel: https://bazel.build
.. _Bazel documentation: https://docs.bazel.build/versions/master/getting-started.html

View file

@ -0,0 +1,102 @@
.. _why-bazel:
Is Bazel right for me?
======================
Nearly as many build tools exist as there are programming languages
out there. C++ has Autotools_/Make_, CMake_ and many others. Java has
Ant_, Maven_, Gradle_ and several more. Haskell has Cabal_, Stack_,
Shake_ and several more. Each of these originated in a given language
community but are in some cases generic enough to support building any
language. Are any of them the right choice for your use case? Should
you be combining several systems? That's what this document should
help you answer.
Rule of thumb
-------------
If a combination of the following apply, then you're better off using
Cabal_ or Stack_:
* your project is an independently publishable single library, or
small set of libraries;
* your project is open source code and has at most small static
assets (hence publishable on Hackage);
* your project is nearly entirely Haskell code with perhaps a little
bit of C;
* your project has many dependencies on other packages found on
Hackage but few if any system dependencies (like zlib, libpng etc);
Bazel works well for the following use cases:
* projects that cannot be hosted on Hackage (games with large static
assets, proprietary code etc);
* projects with a very large amount of code hosted in a single
repository;
* projects in which you or your team are writing code in two or more
languages (e.g. Haskell/PureScript, or Haskell/Java, or
Haskell/C++/FORTRAN);
Rationale
---------
For all the benefits it can bring, Bazel also has an upfront cost.
Don't pay that cost if the benefits don't justify it.
If you don't have much code to build, any build tool will do. Build
issues like lack of complete reproducibility are comparatively easier
to debug, and working around build system bugs by wiping the entire
build cache first is entirely viable in this particular case. So might
as well use low-powered Haskell-native build tools that ship with GHC.
You won't *need* sandboxed build actions to guarantee build system
correctness, completely hermetic builds for good reproducibility,
build caching, test result caching or distributed builds for faster
build and test times. Those features start to matter for larger
projects, and become essential for very large monorepos_.
Why exactly do these features matter?
* **Hermetic builds** are builds that do not take any part of the
host's system configuration (set of installed system libraries and
their versions, content of ``/etc``, OS version, etc) as an input.
If all build actions are deterministic, hermeticity guarantees that
builds are reproducible anywhere, anytime. More developers on
a project means more subtly different system configurations to cope
with. The more system configurations, the more likely that the build
will fail in one of these configurations but not in others... Unless
the build is completely hermetic.
* **Sandboxing build actions** guarantees that all inputs to all build
actions are properly declared. This helps prevent build system
correctness bugs, which are surprisingly and exceedingly common in
most non-sandboxing build systems, especially as the build system
becomes more complex. When a build system *might* be incorrect,
users regularly have to wipe the entire build cache to work around
issues. As the codebase becomes very large, rebuilding from scratch
can cost a lot of CPU time.
* **Distributed build caches** make building the code from a fresh
checkout trivially fast. Continuous integration populates the build
cache at every branch push, so that building all artifacts from
fresh checkouts seldom needs to actually build anything at all
locally. In the common case, builds become network-bound instead of
CPU-bound.
* **Distributed build action execution** mean that average build times
can stay constant even as the codebase grows, because you can
seamlessly distribute the build on more machines.
* **Test result caching** is the key to keeping continuous
integration times very low. Only those tests that depend on code
that was modified need be rerun.
On their own hermetic and sandboxed builds can already save quite
a few headaches. But crucially, without them one can't even hope to
have any of the other features that follow them above.
.. _Autotools: https://en.wikipedia.org/wiki/GNU_Build_System
.. _Make: https://en.wikipedia.org/wiki/Make_(software)
.. _CMake: https://cmake.org/
.. _Ant: https://ant.apache.org/
.. _Maven: https://maven.apache.org/index.html
.. _Gradle: https://gradle.org/
.. _Cabal: https://www.haskell.org/cabal/
.. _Stack: http://haskellstack.org/
.. _Shake: https://shakebuild.com/
.. _monorepos: https://en.wikipedia.org/wiki/Monorepo