about summary refs log tree commit diff
path: root/third_party/bazel/rules_haskell/docs/why-bazel.rst
blob: 2ad4bc598be30846f062c3e4cbef9e55df29573a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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