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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
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
^^^^^^^^^^^^^^^^^^^^^
Let’s 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
|