Expr c b a
is a computation that produces a value of type Action a
and can
read parameters of the current build Target c b
, but what does that mean
exactly? Here's its definition from hadrian/src/Hadrian/Expression.hs
:
newtype Expr c b a = Expr (ReaderT (Target c b) Action a)
deriving (Applicative, Functor, Monad)
So Expr c b a
is a newtype
wrapper around a ReaderT (Target c b) Action a
.
In practice within Hadrian c
is always Context
and b
is always Builder
.
The extra parameterisation is there so that hopefully one day the general
functionality of Hadrian (eg. compiling a Haskell library) will be available
to Shake users via a library.
A type synonym from hadrian/src/Expression/Type.hs
is often used to avoid
writing Context
and Builder
everywhere:
type Expr a = H.Expr Context Builder a
Where H.Expr
is the Expr c b a
defined above. The following references to
Expr
will generally refer to this type synonym unless there is extra
parameterisation.
Let's break down the type a bit, working from the outside in, left to right.
Put simply, ReaderT (Target c b) Action a
adds a read-only environment
Target c b
(in the case of Hadrian: Target Context Builder
) to values of
type Action a
. It's the equivalent of threading through a Target c b
parameter to all our functions, but we only have to worry about it when we need
it, using ask :: Monad m => ReaderT r m r
(where r
is Target c b
and m
is Action
in this case) or other functions based on it. ReaderT
and ask
are defined in Control.Monad.Trans.Reader
.
So, instead of:
foo :: Target Context Builder -> Action ()
foo target = do
liftIO $ putStrLn "Some message"
bar target
bar :: Target Context Builder -> Action ()
bar target' = do
liftIO $ putStrLn "Some other message"
baz target'
baz :: Target Context Builder -> Action ()
baz target'' = do
liftIO $ putStrLn "Yet another message"
liftIO $ print target
We can write:
foo :: ReaderT (Target Context Builder) Action ()
foo = do
liftIO $ putStrLn "Some message"
bar
bar :: ReaderT (Target Context Builder) Action ()
bar = do
liftIO $ putStrLn "Some other message"
baz
baz :: ReaderT (Target Context Builder) Action ()
baz = do
liftIO $ putStrLn "Yet another message"
target <- ask
liftIO $ print target
And to make those into Hadrian Expressions all we have to do is change the type and add the constructor:
foo :: Expr ()
foo = Expr $ do
liftIO $ putStrLn "Some message"
bar
bar :: Expr ()
bar = Expr $ do
liftIO $ putStrLn "Some other message"
baz
baz :: Expr ()
baz = Expr $ do
liftIO $ putStrLn "Yet another message"
target <- ask
liftIO $ print target
From hadrian/src/Hadrian/Target.hs
:
Each invocation of a builder is fully described by a
Target
, which comprises a build context (type variablec
), a builder (type variableb
), a list of input files and a list of output files. For example:preludeTarget = Target (GHC.Context) (GHC.Builder) { context = Context Stage1 base profiling , builder = Ghc Stage1 , inputs = ["libraries/base/Prelude.hs"] , outputs = ["build/stage1/libraries/base/Prelude.p_o"] }
The data type is as follows and is fairly self-explanatory:
data Target c b = Target
{ context :: c -- ^ Current build context
, builder :: b -- ^ Builder to be invoked
, inputs :: [FilePath] -- ^ Input files for the builder
, outputs :: [FilePath] -- ^ Files to be produced
} deriving (Eq, Generic, Show)
So we have some inputs
to our target, some outputs
that it will produce, a
context for the build (in Hadrian: Context
), and the builder (in Hadrian:
Builder
).
From hadrian/src/Context/Type.hs
:
data Context = Context
{ stage :: Stage -- ^ Currently build Stage
, package :: Package -- ^ Currently build Package
, way :: Way -- ^ Currently build Way (usually 'vanilla')
} deriving (Eq, Generic, Show)
So Context is a data type that stores a Stage, Package, and a Way, i.e. the
context for some particular Target
.
From hadrian/src/Stage.hs
:
data Stage = Stage0 | Stage1 | Stage2 | Stage3
deriving (Show, Eq, Ord, Enum, Generic, Bounded)
From hadrian/src/Hadrian/Package.hs
:
data Package = Package {
-- | The package type. 'Library' and 'Program' packages are supported.
pkgType :: PackageType,
-- | The package name. We assume that all packages have different names,
-- hence two packages with the same name are considered equal.
pkgName :: PackageName,
-- | The path to the package source code relative to the root of the build
-- system. For example, @libraries/Cabal/Cabal@ and @ghc@ are paths to the
-- @Cabal@ and @ghc-bin@ packages in GHC.
pkgPath :: FilePath
} deriving (Eq, Generic, Ord, Show)
PackageType
is simply defined as:
data PackageType = Library | Program deriving (Eq, Generic, Ord, Show)
This doesn't quite reflect how Cabal packages are actually structured, as discussed in snowleopard/hadrian#12, but Hadrian can still function treating packages as either libraries or programs.
Both PackageName
and FilePath
are just type synonyms of String
.
From hadrian/src/Way/Type.hs
:
newtype Way = Way IntSet
Where Way
is a set of enumerated WayUnit
s wrapped in a newtype
.
WayUnit
is defined as:
data WayUnit = Threaded
| Debug
| Profiling
| Logging
| Dynamic
deriving (Bounded, Enum, Eq, Ord)
There are also some helper functions in this module to abstract away this complexity. For example:
import qualified Data.IntSet as Set
wayFromUnits :: [WayUnit] -> Way
wayFromUnits = Way . Set.fromList . map fromEnum
wayFromUnits
converts the [WayUnit]
into [Int]
using map fromEnum
,
creates an IntSet
from them using Set.fromList
, and then wraps the IntSet
with the Way
constructor. So we can use wayFromUnits
to create a Way
that
builds Hadrian with both multi-threading and profiling by simply writing
wayFromUnits [Threaded, Profiling]
.
We can also check if a Way
contains a particular WayUnit
by using
wayUnit :: WayUnit -> Way -> Bool
. This is useful if we need to do something
when we're building with a particular WayUnit
, but not otherwise.
For example, using getWay :: Expr Context b Way
from hadrian/src/Context.hs
:
foo :: Expr ()
foo = do
w <- getWay
if wayUnit Profiling w
then liftIO $ putStrLn "We're building this target with profiling"
else liftIO $ putStrLn "We're not building this target with profiling"
From hadrian/src/Builder.hs
:
A
Builder
is a (usually external) command invoked in a separate process viacmd
. Here are some examples:
Alex
is a lexical analyser generator that buildsLexer.hs
fromLexer.x
.Ghc
Stage0
is the bootstrapping Haskell compiler used inStage0
.Ghc
StageN
(N > 0) is the GHC built in stage (N - 1) and used inStageN
.The
Cabal
builder is unusual in that it does not correspond to an external program but instead relies on the Cabal library for package configuration.
The data type itself is simply a long set of constructors that may or may not be parameterised:
data Builder = Alex
| Ar ArMode Stage
| Autoreconf FilePath
| DeriveConstants
| Cabal ConfigurationInfo Stage
...
| Ghc GhcMode Stage
... etc.
deriving (Eq, Generic, Show)
Action
comes from Shake, the library underlying Hadrian. It can perform IO
using liftIO
and keeps track of the dependencies for a rule. For more
information on Action
, see the Shake docs:
https://hackage.haskell.org/package/shake-0.18.3/docs/Development-Shake.html
One useful kind of Hadrian expression is Predicate
, which is just a type
synonym for Expr Bool
. These expressions can read from the Target
and
possibly perform IO
or any other Action
to return a Bool
.
A particularly useful operator for using Predicate
s is ?
. Its real type and
implementation can be found in hadrian/src/Hadrian/Expression.hs
, but for the
sake of illustrating how it works in most cases, imagine it's defined like
this:
(?) :: Monoid a => Predicate -> Expr a -> Expr a
predicate ? expr = do
bool <- predicate
if bool then expr else return mempty
If the Predicate
returns True
, we return the Expr
we give it, otherwise
we return mempty
(which is why we need the Monoid
type constraint). In fact
thanks to some added type class complexity in the real definition, we can
give ?
a Bool
instead of a Predicate
and it works the same way.
To show how we might use Predicate
s and ?
in practice, say we want to
compile all the Haskell modules in compiler/
with -O0
during stage 0. We can
do that by going to UserSettings.hs
(see
the user settings docs) and changing userArgs
to:
userArgs :: Args
userArgs = package compiler ? builder (Ghc CompileHs stage0) ? arg "-O0"
Args
is just a type synonym for Expr [String]
and arg
just lifts a
String
into an Args
.
package :: Package -> Predicate
from hadrian/src/Expression.hs
takes a
Package
and returns a Predicate
that will return True
if the current
Target
is part of that package and False
otherwise. In this case we give
it compiler
which is defined in hadrian/src/Packages.hs
along with many
other convenient Package
definitions.
builder
comes from hadrian/src/Expression.hs
:
This type class allows the user to construct both precise builder predicates, such as
builder (Ghc CompileHs Stage1)
, as well as predicates covering a set of similar builders. For example,builder (Ghc CompileHs)
matches any stage, andbuilder Ghc
matches any stage and any GHC mode.
class BuilderPredicate a where
-- | Is a particular builder being used?
builder :: a -> Predicate
Other useful Predicate
functions can be found in hadrian/src/Expression.hs
and hadrian/src/Hadrian/Expression.hs
.