Commit 605bce26 authored by James Foster's avatar James Foster Committed by Marge Bot

Add documentation for Hadrian expressions

This commit adds documentation on Hadrian's 'Expr' type and
references the documentation in hadrian/README.md
parent a33bad2d
...@@ -121,6 +121,11 @@ build to check that the build system is well formed. Note that the Lint check ...@@ -121,6 +121,11 @@ build to check that the build system is well formed. Note that the Lint check
currently fails under certain circumstances, as discussed in currently fails under certain circumstances, as discussed in
[this ticket](https://gitlab.haskell.org/ghc/ghc/issues/15971). [this ticket](https://gitlab.haskell.org/ghc/ghc/issues/15971).
#### Expressions
Hadrian expressions are used extensively for specifying build settings. For an
explanation of how they work, see the [documentation](doc/expressions.md).
#### User settings #### User settings
The Make-based build system uses `mk/build.mk` to specify user build settings. The Make-based build system uses `mk/build.mk` to specify user build settings.
......
# Expressions
`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`:
```haskell
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:
```haskell
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.
## ReaderT
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`](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/Control-Monad-Trans-Reader.html).
So, instead of:
```haskell
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:
```haskell
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:
```haskell
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
```
## Target
From `hadrian/src/Hadrian/Target.hs`:
> Each invocation of a builder is fully described by a `Target`, which
> comprises a build context (type variable `c`), a builder (type variable `b`),
> a list of input files and a list of output files. For example:
>
> ```haskell
> 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:
```haskell
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`).
### Context
From `hadrian/src/Context/Type.hs`:
```haskell
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`.
#### Stage
From `hadrian/src/Stage.hs`:
```haskell
data Stage = Stage0 | Stage1 | Stage2 | Stage3
deriving (Show, Eq, Ord, Enum, Generic, Bounded)
```
#### Package
From `hadrian/src/Hadrian/Package.hs`:
```haskell
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:
```haskell
data PackageType = Library | Program deriving (Eq, Generic, Ord, Show)
```
This doesn't quite reflect how Cabal packages are actually structured, as
discussed in https://github.com/snowleopard/hadrian/issues/12, but Hadrian can
still function treating packages as either libraries or programs.
Both `PackageName` and `FilePath` are just type synonyms of `String`.
#### Way
From `hadrian/src/Way/Type.hs`:
```haskell
newtype Way = Way IntSet
```
Where `Way` is a set of enumerated `WayUnit`s wrapped in a `newtype`.
`WayUnit` is defined as:
```haskell
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:
```haskell
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`:
```haskell
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"
```
### Builder
From `hadrian/src/Builder.hs`:
> A `Builder` is a (usually external) command invoked in a separate process
> via `cmd`. Here are some examples:
> * `Alex` is a lexical analyser generator that builds `Lexer.hs` from `Lexer.x`.
> * `Ghc` `Stage0` is the bootstrapping Haskell compiler used in `Stage0`.
> * `Ghc` `StageN` (N > 0) is the GHC built in stage (N - 1) and used in `StageN`.
>
> 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:
```haskell
data Builder = Alex
| Ar ArMode Stage
| Autoreconf FilePath
| DeriveConstants
| Cabal ConfigurationInfo Stage
...
| Ghc GhcMode Stage
... etc.
deriving (Eq, Generic, Show)
```
## Action
`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
# Predicates
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:
```haskell
(?) :: 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](user-settings.md)) and changing `userArgs` to:
```haskell
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, and `builder Ghc` matches any stage and any GHC mode.
```haskell
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`.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment