787 lines
33 KiB
TeX
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}
|