aos/doc/003-hake/Hake.tex
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

787 lines
33 KiB
TeX

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Copyright (c) 2011, ETH Zurich.
% All rights reserved.
%
% This file is distributed under the terms in the attached LICENSE file.
% If you do not find this file, copies can be found by writing to:
% ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\documentclass[a4paper,twoside]{report} % for a report (default)
\usepackage{bftn} % You need this
\title{Hake} % title of report
\author{Timothy Roscoe} % author
\tnnumber{003} % give the number of the tech report
\tnkey{Hake: the Barrelfish build system} % Short title, will appear in footer
% \date{Month Year} % Not needed - will be taken from version history
\begin{document}
\maketitle
%
% Include version history first
%
\begin{versionhistory}
\vhEntry{1.0}{3.06.2010}{TR}{Initial version}
\vhEntry{1.1}{11.04.2010}{TR}{Support for out-of-tree builds}
\vhEntry{1.2}{03.07.2015}{TR}{Added platform and boot constructs}
\vhEntry{1.3}{15.04.2017}{GZ}{Support for Modules}
\vhEntry{1.4}{14.12.2018}{SG}{Support for library OS selection on a per application/library basis}
\end{versionhistory}
% \intro{Abstract} % Insert abstract here
% \intro{Acknowledgements} % Uncomment (if needed) for acknowledgements
\tableofcontents % Uncomment (if needed) for final draft
% \listoffigures % Uncomment (if needed) for final draft
% \listoftables % Uncomment (if needed) for final draft
\chapter{Introduction}
Hake is how we build Barrelfish.
Hake isn't designed to operate outside Barrelfish, so this document
will assume you're trying to build Barrelfish.
\section{Quick start}
Suppose you have a fresh Barrelfish source tree in:
\texttt{/home/barrelfish/src}
To build a tree, create a directory for it, \texttt{cd} to that
directory, and run the Hake bootstrap script, and then Make:
\begin{verbatim}
$ cd /home/barrelfish/src
$ mkdir ../build
$ cd ../build
$ ../src/hake/hake.sh -s ../src -a x86_64
...
\end{verbatim}
Type \texttt{make help-platforms} for a list of platforms you can now
build for, and \texttt{make help-boot} for a list of options for
booting Barrelfish under simulation if you don't want to use real
hardware.
You can supply multiple \texttt{-a} options to build for multiple
architectures at the same time.
Edit the file \texttt{hake/Config.hs} in your build directory and
rebuild Hake to reconfigure your build tree.
That's about it.
\section{How to think about hake}
Hake builds a single, very large \texttt{Makefile} which
\texttt{make} can then use to build any part of Barrelfish.
Hake is essentially a Haskell embedded domain-specific language,
except that it is also evaluated dyanically (using the
\texttt{System.Eval.Haskell} package) and written by scattering code
around the source tree.
Hake consists of the main hake program (which itself contains
considerable information on how to build code), together with a set of
Hakefiles spread throughout the source tree.
Each Hakefile should be thought of as containing a Haskell expression
which evaluates to a set of rules for Make. The expression will be
evaluated in an environment which includes the path to the directory
where the Hakefile is located, plus a complete list of all files in
the source tree.
\section{When hake runs}
When you run Hake in a Barrelfish source tree, it does the following things:
\begin{enumerate}
\item Hake builds a list of (almost) every file and directory in the
source tree. The list of files Hake ignores is currently hardcoded
into Hake, but basically it skips editor temporary files, version
control directories, and products of a previous build process.
\item From this, Hake extracts a list of all Hakefiles in the tree.
\item Hake reads every Hakefile. Each Hakefile contains a single
Haskell expression which itself evaluates to a set of Make rules.
\item Hake constructs a single very large Haskell expression out of
all these Hakefiles. Each Hakefile is evaluated in an environment
which includes the pathname of the Hakefile itself (to resolve
relative names), and the entire list is evaluated in an environment
which includes the list of files in the whole tree (to allow
wildcards).
\item This large expression is then evaluated. The result is a single
tree of Hake rule representations (see
Chapter~\ref{sec:reprules}).
\item The rule tree is traversed to derive a list of every directory
in the build tree.
\item Finally, a single Makefile is generated which contains rules to
build every target in the build tree, for every architecture
(including the host-based build tools themsevles), and also create
every directory in the build tree.
\end{enumerate}
This single Makefile is large, but is also quite simple: it contains
no use of Make variables or generic Make rules, instead it simply
includes explicit rules to build every file required for Barrelfish.
The Makefile also includes comments to help you locate the make rules
generated from any particular Hakefile.
\section{Motivation and Design Principles}
\paragraph{Hake should be a full programming language.} The lesson
from countless built systems is that if one starts without a full
programming language built in, one ends up implementing a bad one
(CMake being only one example). It's much easier to bite the bullet
and admit that we need a complete language, and plenty are available
for this.
\paragraph{Hake should be a functional language.} \texttt{make} is a
canonical example of a successful declarative language: Hake should
not try and replicate what make does well.
\paragraph{Hake should generate one Makefile.} One Makefile is easier
to debug: all the information is available in the same file. There is
no need to hunt through 5 levels of include files. The only thing
Hake-generated Makefiles include are generated C dependency lists, and
a single, top-level file giving symbolic targets. The Makefile
generated by Hake also makes minimal, and highly stylized, use of make
variables: wherever possible, any variable substitution is done in Haskell
before the Makefile is generated.
\paragraph{Hake is for building Barrelfish.} We make no claims as to
Hake's suitability for any project other than Barrelfish, and indeed
the current implementation is pretty tied to the Barrelfish tree.
This has helped to keep the system focussed and tractable. One
non-goal of Hake, for example, is to support portability across host
machines (as CMake tries to do).
\chapter{Simple Hakefiles}
Hake can in principle build anything, but there are two simple use
cases for Hake: building Barrelfish applications (user-space
binaries), and building Barrelfish libraries. Here's how, at time of
writing, the Barrelfish PCI driver is specified. This is the entire
Hakefile:
\begin{verbatim}
[ build application {
target = "pci",
cFiles = [ "pcimain.c", "pci.c", "pci_service.c",
"ioapic.c", "acpi.c", "ht_config.c",
"acpica_osglue.c", "interrupts.c",
"pci_confspace.c", "pcie_confspace.c",
"video.c", "buttons.c", "acpi_ec.c" ],
flounderBindings = [ "pci" ],
flounderDefs = [ "monitor" ],
mackerelDevices = [ "pci_hdr0", "pci_hdr1",
"lpc_ioapic", "ht_config",
"lpc_bridge", "acpi_ec" ],
addIncludes = [ "acpica/include" ],
addCFlags = [ "-Wno-redundant-decls" ],
addLibraries = [ "mm", "acpi", "skb", "pci" ],
libraryOs = Config.libbarrelfish_pmap_array,
architectures = [ "x86_64", "x86_32" ]
}
]
\end{verbatim}
The outermost square brackets are a Haskell list expression - each
Hakefile should be such a list (the exact type will be explained
later). This list has a single element, the instruction to built an
application (you can have more of these, separated by commas).
The \texttt{build application} specifies a number of arguments, all of
which are optional. These are actually Haskell record field
specifiers, and \texttt{application} returns a complete default set.
\texttt{build} then generates the Make rules to build the
application.
The complete list of possible arguments for applications (or
libraries) can be found by looking at \texttt{Args.hs}. The ones used
here are:
\begin{description}
\item[target]: the name of the binary to build.
\item[cFiles]: list of names of C source files. You need to include
``\texttt{.c}''.
\item[flounderBindings]: Flounder interfaces for which to compile
the stub files.
\item[flounderDefs]: Flounder interfaces to use from a library
\item[mackerelDevices]: list of Mackerel device specs this application uses or depends on.
\item[addIncludes]: additional include paths for header files.
\item[addLibraries]: additional libraries to link against.
\item[addModules]: additional libraries to link against. In contrast to
\texttt{addLibraries}, the linker makes sure to include all symbols by passing
the \texttt{--whole-archive} option. This is useful if you want to have a more
dynamic discovery of code (e.g., device drivers modules).
\item[libraryOs]: Select the library OS against which to link this particular
application. The field is of type \verb|Maybe Args|, and the valid options are
defined in \verb|Config.hs|.
\end{description}
Note that filenames are relative to the current source
directory. Those with a leading '/' are interpreted relative to the
root of the tree (not the root file system).
Libraries are similar. Here's the Hakefile for the X86 emulator
library:
\begin{verbatim}
[ build library {
target = "x86emu",
cFiles = [ "debug.c", "decode.c", "fpu.c", "ops2.c",
"ops.c", "prim_ops.c", "sys.c"],
addCFlags = ["-Wno-shadow" ]
}
]
\end{verbatim}
Finally, there are two other kinds of high-level construct that Hake
provides. One is a \texttt{platform} -- a collection of related files
to build to support a given hardware configuration. Here is the
(pretty minimal) specification for the ARMv5 test platform - just the
CPU driver and its bootable image:
\begin{verbatim}
[ platform "ARMv7" [ "armv7" ]
([ ("armv7", "/sbin/" ++ f) | f <- [ "cpu", "cpu.bin" ]])
"Very basic ARMv7 configuration for testing",
]
\end{verbatim}
The first argument is the name of the platform (``\texttt{ARMv7}''),
the second is a set of architectures which must be built (and
configured) for this platform. The third argument is a list of
pathnames in the build tree of files which must be built, and the
final argument is a description to be printed by \texttt{make
help-platforms}.
The final high-level construct is a ``\texttt{boot}'' - a make target
that boots Barrelfish in some kind of emulator. Examples can be found
in \texttt{/platforms/Hakefile}.
This should be all you need to know to write simple Hakefile{s} for
the Barrelfish, and indeed to understand most of the Hakefile{s} in
the Barrelfish tree.
Doing (or understanding) more fancy things in the Hakefile requires
more knowledge of how Hake internally generates and represents Make
rules, described later.
\chapter{Hake from the bottom up}
The core of Hake consists of the code to walk the source tree, a
minimal set of data types used to represent Make rules, and codes to
render these data types into a Makefile.
\section{The Hake name space for files}
Unlike most build systems, Hake uses a 3-dimensional name space for
files.
The first component is called the ``tree''. Whenever Hake runs, it
deals with three ``trees'':
\begin{enumerate}
\item The ``source tree'' (written as \texttt{SrcTree}) is the fle
system directory tree containing the source code for the programs
and libraries currently being built. When building the core OS,
this is the main OS source tree. When building an external
application or library, this is the directory tree containing the
application or library's source code.
\item The ``build tree'' (written as \texttt{BuildTree}) is where the
intermediate and final results of the compilation end up. This is
typically the current working directory when Hake was run.
\item The ``install tree'' (written as \texttt{InstallTree}) is the
directory tree containing a core Barrelfish OS build tree. When
building the OS, the install tree and the build tree are the same,
but when building an external application or library, the install
tree is a pre-built Barrelfish tree and the build tree is where the
new application or library is built.
\end{enumerate}
The second component is called the ``architecture'' (for want
of a better name), and corresponds to building the same code for
different target architectures (\texttt{x86\_64}, \texttt{arm}, etc.)
Architectures themselves form a flat namespace.
Some ``architectures'' are special when building the core Barrelfish OS:
\begin{description}
\item[src] refers to files which are always present in the source
tree. Hake should not be used to build anything in the \texttt{src}
architecture, and anything in any other architecture must be generated
at build time.
\item[hake] is used by Hake as part of the bootstrapping process.
\item[root] refers to files relative to the top of the build
tree, and should be used with caution.
\item[tools] is used to build other build process tools (Mackerel,
Fugu, Flounder, etc.)
\item[docs] is used to build documentation (Technical Notes),
including this document.
\end{description}
The final component is called the ``path'', and corresponds roughly
to the pathname of the file from the root of the designated tree.
In the source for hake itself,``path'' usually refers to this path,
and file ``location'' or just ``loc'' refers to the 3-dimensional file
reference.
Here are some examples of hake file locations:
\begin{tabular}{crll} Tree & Architecture & Path & Description \\ \hline
\texttt{SrcTree} & \texttt{src} & \texttt{/tools/hake/Main.hs} & Part of the source code for Hake
itself \\
\texttt{InstallTree} & \texttt{tools} & \texttt{/tools/flounder/flounder} & The (built) binary for the
flounder compiler \\
\texttt{InstallTree} & \texttt{x86\_64} & \texttt{/lib/libbarrelfish.a} & The Barrelfish
library \\
\texttt{InstallTree} & \texttt{src} & \texttt{/include/stdio.h} & C header file \\
\texttt{BuildTree} & \texttt{x86\_64} & \texttt{/include/asmoffsets.h} & Generated C header
file \\
\texttt{SrcTree} & \texttt{src} & \texttt{/devices/xapic.dev} & Mackerel source file \\
\texttt{BuildTree} & \texttt{x86\_64} &
\texttt{/include/dev/xapic\_dev.h} & Generated header file from Mackerel \\
\end{tabular}
When referring to files in Hake, files whose paths are ``relative''
(i.e.\ do not start with a leading ``/'') are considered relative to
the path of their Hakefile, and are converted into
``absolute'' paths from the top of their tree when they appear.
This is more intuitive than it sounds. For example, a file referred
to as \texttt{(SrcTree,"src","e1000.c")} in
\texttt{drivers/e1000/Hakefile} will appear in the resulting Makefile
as {drivers/e1000/e1000.c}.
The Hake namespace is mapped onto the file system as follows: all
files with architecture \texttt{src} are relative to the top of the
source tree, whereas a file in a different architecture \texttt{foo}
is relative to directory \texttt{foo/} in the build or install tree.
Thus, \texttt{(BuildTree,"x86\_64","e1000.o")} in
\texttt{drivers/e1000/Hakefile} will appear in the resulting Makefile
as {./x86\_64/drivers/e1000/e1000.o}.
Hake will generate all Makefile rules necessary to create any
directories in the build tree that it needs - it's perfectly possible
(and sometimes useful) with Hake to type ``\texttt{rm -rf ./*;
make}'' and have everything work.
\section{Representing rules}\label{sec:reprules}
Each Hakefile is an expression that must evaluate to a list of
\texttt{HRule}s. The declaration of \texttt{HRule} is:
\begin{verbatim}
data HRule = Rule [ RuleToken ]
| Include RuleToken
| Error String
| Phony String Bool [ RuleToken ]
| Rules [ HRule ]
deriving (Show,Typeable)
\end{verbatim}
The \texttt{Include} constructor creates an ``include'' directive in a
Makefile. In theory, there should be no need for developers to use
this; it is only used currently to include automatically-generated
dependency files for C and assembly source.
The \texttt{Rules} constructor allows a tree of rules to be
constructed. This is purely a convenience: any time that one can
return a single rule, one can also return a list of rules. This makes
it easier to write functions which return rules, which is the basis of
Hake.
The \texttt{Error} constructor is used to signal errors, but in
practice is rarely used.
The \texttt{Phony} constructor is used to create rules whose target is
not actually a file. This should hardly ever be used in practice:
right now, it is only used for defining ``platforms'' and ``boots''
(see below).
An actual basic Makefile rule is constructed by \texttt{Rule} as a
list of \texttt{RuleToken}s. The declaration of \texttt{RuleToken}
is:
\begin{verbatim}
data TreeRef = SrcTree | BuildTree | InstallTree
deriving (Show,Eq)
data RuleToken = In TreeRef String String -- Input to the computation
| Dep TreeRef String String -- Extra (implicit) dependency
| NoDep TreeRef String String -- File that's not a dependency
| PreDep TreeRef String String -- One-time dependency
| Out String String -- Output of the computation
| Target String String -- Target that's not involved
| Str String -- String with trailing " "
| NStr String -- Just a string
| ErrorMsg String -- Error message: $(error x)
| NL -- New line
| Abs RuleRoken RuleToken -- Absolute path rule token
deriving (Show,Eq,Ord)
\end{verbatim}
Each rule token can either be a string of some form, or a reference to
a file. Note that for some file references, the tree is implicit:
\texttt{Out} and \texttt{Target} files are always in the
\texttt{BuildTree}.
Rules in Hake differ from plain Makefile rules in that
they only consist of rule bodies (i.e., exactly what needs to be
done), and the targets and dependencies are inferred (so they only
need to be written once). An example may make this clear. Here is a
function which returns list of \texttt{RuleToken}s for maintaining a
Unix library:
\begin{verbatim}
archive :: Options -> [String] -> String -> [ RuleToken ]
archive opts objs libpath =
[ Str "ar cr ", Out arch libpath ]
++
[ In BuildTree arch o | o <- objs ]
++
[ NL, Str "ranlib ", Out arch libpath ]
\end{verbatim}
The arguments to this function include a set of ``options'', which are
used extensively inside Hake to pass around values like C flags,
include paths, link options, etc., together with a set of object file
paths and the path of a library file to build. The architecture
``\texttt{arch}'' is defined elsewhere (this example is from the file
with rules specific to \texttt{x86\_64}, so within the scope it is
defined globally)
The library is referred to as an \texttt{Out} token, since it is a target
of the rule, whereas the object files are referred to by \texttt{In}
tokens, since they are prerequisites. Both are in the \texttt{arch}
architecture, since they have presumably been built by other rules.
This function is called from another, \texttt{arch}-independent
function called ``\texttt{archiveLibrary}'', which dispatches based on
the architectures that need to be built for a given library.
Hence, if a Hakefile at ``\texttt{drivers/e1000/Hakefile}'' contained
the expression:
\begin{verbatim}
archiveLibrary "x86_64" "e1000drv" [ "e1000.o", "e1000srv.o"]
\end{verbatim}
-- the resulting Makefile would contain:
\begin{verbatim}
./x86_64/drivers/e1000/libe1000drv.a: \
./x86_64/drivers/e1000/e1000.o \
./x86_64/drivers/e1000/e1000srv.o
ar cr ./x86_64/drivers/e1000/libe1000drv.a \
./x86_64/drivers/e1000/e1000.o \
./x86_64/drivers/e1000/e1000srv.o
ranlib ./x86_64/drivers/e1000/libe1000drv.a
\end{verbatim}
The precise definitions of each token are as follows:
\begin{description}
\item[In] tokens are file references which are dependent inputs for a
Make rule. In other words, they refer to files which will appear
both in the rule body and the list of dependencies (the right hand
side) in the rule
head. \textbf{In} file references can be in any architecture.
\item[Dep] tokens are file references to implicit dependencies. In
Make terms, these are file names which appear in the list of
dependencies in rule head, but don't explicitly appear in the rule
body.
\item[PreDep] tokens are like \textbf{Dep} tokens, but appear in the
rule head following a \textbf{$|$} character. GNU Make will require
these dependencies to be built only if they do not already exist -
it does not check for modification times. In Barrelfish, such
dependencies are used for files such as \texttt{errno.h} which must
be generated first in order to calculate C dependencies, but which
ultimately not all C files depend upon. Any true dependency of a C
file on \texttt{errno.h} will be specified by the generated depend
files, and thus override the \textbf{PreDep} declaration.
\item[NoDep] tokens are file references that are not dependencies at
all. The file name only appears in the rule body, never in the
head. For example, \textbf{NoDep} references are used for
directories for include files.
\item[Out] tokens are file references to output files from a rule,
which are mentioned in the rule body. This is the common case for
most files generated by Make rules.
\item[Target] tokens are file references that are implicit outputs of
the rule, but do not appear in the rule body. In Make terms they
appear only in the left-hand side of the rule head, and not in the
body.
\item[Str] tokens are simply strings. They will be followed in the
Makefile by a space character, which is usually what you want.
\item[NStr] tokens are like \textbf{Str}, but not followed by a
space. This is useful for situations like the \texttt{-I} flag to
the C compiler, which takes a directory name (specified by a
\textbf{NoDep} token) without any intervening whitespace.
\item[ErrorMsg] tokens are a way to incorporate error conditions into
the Makefile - they are translated into the GNU make construct
\texttt{\$(error \textit{x})}.
\item[NL] tokens are simply newlines in the rule.
\item[Abs] tokens convert a \texttt{RuleToken} into an absolute path.
This is used internally when a command is executed inside a subdirectory
in the build tree and needs access to other resources in the tree.
\end{description}
In practice, a Hakefile rarely has to resort to explicit
\texttt{RuleToken}s, but instead calls functions inside Hake to return
\texttt{HRule}s.
\section{Higher rule abstractions}
The guts of Hake is mostly contained in the file \texttt{RuleDefs.hs},
which provides a big lattice of functions to automate generating
rules for commonly used patterns. If you want to do more complex
things than simply ``\texttt{build application}'' or ``\texttt{build
library}'', it's a good idea to understand how these features use
the definitions in \texttt{RuleDefs.hs}.
\subsection{Library OS selection}
It is worth describing the mechanism that drives the selection of a library OS
in both \texttt{build application} and \texttt{build library} in a bit of
detail here, as it touches both those macros.
The way library OS selection works is that the \texttt{application} and
\texttt{library} macros read certain fields of \verb|Args.libraryOs|.
Currently the fields that are used are \verb|Args.target|, to figure out the
name of the libraryOs archive to include when linking, and
\verb|Args.omitCFlags|, \verb|Args.omitCxxFlags|, \verb|Args.addCFlags|, and
\verb|Args.addCxxFlags| to provide extra compiler flags.
If a Hake rule does not set the field \verb|Args.libraryOs|, then we fall back
to \verb|Config.libbarrelfish|, which can be used to select which library OS
to link against by default.
To reduce the number of types and leave the library OS selection mechanism
open to future library OSes that need to provide more extra arguments to the
compiler, we want to use another \verb|Args| struct to store the library OS
configuration.
The field \verb|Args.libraryOs| is of type \verb|Maybe Args| because we run
into a circular definition when trying to define \verb|Args.defaultArgs|
otherwise.
All the library OS configurations must be defined in \texttt{Config.hs}, so
that they are visible while the Hakefiles are evaluated.
A libray OS itself is just a regular ``\texttt{build library}``, examples of
which can be found for the variants of the default library OS in the
``libbarrelfish`` Hakefile in \texttt{/lib/barrelfish}.
A library OS should be using its own library OS configuration as
\verb|Args.libraryOs|, and hake provides \verb|Args.makeTarget| to extract the
target name from a \verb|Maybe Args| so library OSes are encouraged to use
this function to extract their target from the library OS configuration.
\section{Target architectures}
Most of the flexibility required of Hake in building for multiple
architectures is simply coded into the Haskell source of the program.
For every target architecture (at time of writing, only
\texttt{x86\_64}), there is a file (\texttt{X64\_64.hs}) which
contains the definitions required to build the system for that
target. Adding a new target architecture for Barrelfish involves
writing a new one of these files (e.g. \texttt{ARMv8.hs}, or
\texttt{X86\_64.hs}, etc.) and modifying the code in
\texttt{RuleDefs.hs} to dispatch to the correct module.
\section{Host architectures}
Hake at present supports only a single host architecture: the
toolchain to build Barrelfish is specified once in the target
architecture files (see above).
To add support for multiple host build environments, one way to slice the
problem is for the target architecture modules to import different
host architecture modules and decide which one to call to get tool and
path definitions at runtime.
\section{Configuration}
The file \texttt{hake/Config.hs} in the build directory contains all
the configuration variables (at time of writing) used for Barrelfish.
Unlike in Make or CMake, these are Haskell values of arbitrary type,
since they are evaluated entirely within Hake.
To reconfigure a build tree, therefore, one modifies this file,
and rebuilds Hake and the top-level Makefile.
The \texttt{rehake} target performs this task.
\chapter{Bootstrapping and Configuring Hake}
Hake is bootstrapped using a shell script found in the Barrelfish
source tree in \texttt{hake/hake.sh}. This script is the place to
start configuring a new core Barrelfish OS build tree, and must be
run in the root of the new build directory.
\texttt{hake.sh} takes the following command-line options:
\begin{description}
\item[-s,--source-dir:] This option is mandatory and specifies the
path to the Barrelfish source directory tree.
\item[-i,--install-dir:] This option specifies a path to an
alternative install directory, and defaults to \texttt{`pwd`}.
\item[-a,--architecture:] This option can be given multiple times and
specifies the list of architectures to build Barrelfish for. Run
the script with the \texttt{-h} option to get the default list of
architectures.
\item[-h,--help:] Prints a usage message.
\item[-n,--no-hake:] This option simply rebuilds Hake, but does not
run it to generate a Makefile. It can be handy for debugging Hake
itself.
\item[-t,--toolchain <arch> <toolchain>:] Use \texttt{toolchain} to build
\texttt{arch}.
\end{description}
After parsing and checking arguments, \texttt{hake.sh} next creates a new
configuration file \texttt{hake/Config.hs} in the build tree. The
configuration options in this file are defaults: it is a copy of the
template \texttt{hake/Config.hs.template} in the source tree.
If this file already exists in the build tree, however, it is left
unchanged, which means that any user modifications to this file
persist across multiple bootstrapping runs of \texttt{hake.sh}.
If you really want to reconfigure a build tree from scratch, you
should therefore remove everything in the build tree, including this
file.
After this, Hake itself is recompiled in the build tree (including the
new \texttt{Config.hs} file), and then run with default options (most
of which will be picked up from \texttt{Config.hs}).
\chapter{Debugging Hakefiles}
At least three things can go wrong when you modify or write a
Hakefile.
\section{The Hakefile has a compile error}
If you make a mistake in a Hakefile, the most likely output you will
see is a funny-looking Haskell compile error, e.g.:
\begin{verbatim}
../barrelfish.oothake/usr/pci/Hakefile:13:0:
Couldn't match expected type `t -> [HRule]'
against inferred type `[a]'
In the expression:
[build
(application
{target = "pci", flounderBindings = ["pci"],
flounderDefs = ["monitor"],
mackerelDevices = ["pci_hdr0", "pci_hdr1", ....],
\ldots
\ldots
<command line>: module is not loaded: `Hakefiles' (Hakefiles.hs)
\end{verbatim}
Ignoring the last line for the moment, if you know enough Haskell this
should tell you exactly what is wrong with some Hakefile. However,
even if you don't know enough Haskell, it does say which Hakefile is
at fault and whereabouts in the offending Hakefile the problem is (in
this case line 13 of \texttt{usr/pci/Hakefile}).
Also, the file that Hake tried to compile will be left for you in
\texttt{Hakefiles.hs}. If you look at this, you'll see it's
constructed out of individual Hakefile{s} together with a preamble
giving details of the files in the tree.
\section{The Makefile has an error}
Hake generates a single large Makefile at the top of the tree.
While it's huge (often several 100,000 lines), it's actually very easy to
understand
since (a) it only refers to files, (b) it contains comments saying
where each bit comes from, and (c) it barely uses any
Make variables at all.
It is hard to persuade Hake to generate an invalid Makefile,
but it's possible. If so, it may still be due to an error in some
Hakefile, in which case look at the file comments preceding the line
where Make thinks the error is to find out which Hakefile to look
at.
The most common problem is actually due to out of date dependencies.
Hake does its best to calculate dependencies properly, but sometimes
(such as when Hakefiles themselves change) they get confused. In this
case, the first thing to try to is completely remove the build tree
and try again. As you get more of a feel for the system it's possible
to more surgically remove bits of the tree (the Makefile knows how to
recreate any part of the build tree).
\section{The Makefile works, but the build fails}
In this case, you've written valid Hake rules, but they don't do what
you want them to. In this case as well, looking at the generated Makefile
can often help work out what went wrong.
\chapter{Command-line arguments}
The Hake binary built in a Barrelfish tree can be found in
\texttt{/hake/hake}, and takes the following command-line arguments:
\begin{description}
\item[--source-dir:] this option is mandatory and specifies the root
of the source tree.
\item[--output-filename:] this option specifies the name of the output
Makefile, and defaults to \texttt{Makefile}
\item[--quiet:] this option turns off some information and warning
messages as Hake runs.
\item[--verbose:] this option increases the verbosity level of Hake's
information messages.
\item[--install-dir:] this option specifies the install tree. It
defaults to the build tree (the current working directory where Hake runs).
\item[--bfsource-dir:] this option specifies the location of Barrefish headers.
\item[--architecture:] this option can be specified multiple times and
gives an architecture for Hake to build. It overrides the default
list of architectures to build that was set when Hake was
configured. At time of writing, supported architectures include
\texttt{x86\_64}, \texttt{armv7}, \texttt{armv8},
and \texttt{k1om}.
\item[--ghc-libdir:] Library directory for GHC.
\end{description}
\section{Building an external application or library}
Building an application or library \emph{outside} the main Barrelfish
tree involves invoking Hake directly (rather than bootstrapping with
\texttt{hake.sh}), and requires to you have a pre-built Barrelfish
tree with at least as many architectures built as you would like to
build the application or library for.
For example, suppose \texttt{/projects/barrelfish/install} contains a
core Barrelfish tree built for all supported architectures, and the
user's home directory contains a small source tree
\verb!~/quake3! containing an application to be built for
\texttt{x86\_64} only. As long as this source tree has a correct
Hakefile (or Hakefiles), the following should build the application:
\begin{verbatim}
(Barrelfish) $ make install_headers
$ mkdir quake_build
$ cd quake_build
$ /projects/barrelfish/install/hake/hake \
--source-dir ~/quake \
--install-dir /projects/barrelfish/install \
--bfsource-dir /project/barrelfish/install \
--architecture x86_64
$ make -j 16
\end{verbatim}
\chapter{Wishlist}
Hake is missing many desirable features. Hopefully, this list will
reduce in size over time. Here are a few:
\begin{itemize}
\item Support for multiple host build environments (such as Cygwin).
\item The bootstrapping process for Hake, while short, is a little
unsatisfactory.
\end{itemize}
\end{document}