Add documentation for Hadrian expressions
authorJames Foster <jf16688@my.bristol.ac.uk>
Sun, 18 Aug 2019 00:24:15 +0000 (20:24 -0400)
committerMarge Bot <ben+marge-bot@smart-cactus.org>
Thu, 22 Aug 2019 22:47:20 +0000 (18:47 -0400)
This commit adds documentation on Hadrian's 'Expr' type and
references the documentation in hadrian/README.md

hadrian/README.md
hadrian/doc/expressions.md [new file with mode: 0644]

index bb386db..2a0ffaf 100644 (file)
@@ -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
 [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
 
 The Make-based build system uses `mk/build.mk` to specify user build settings.
diff --git a/hadrian/doc/expressions.md b/hadrian/doc/expressions.md
new file mode 100644 (file)
index 0000000..1831bcc
--- /dev/null
@@ -0,0 +1,329 @@
+# 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`.