.. _setup-hooks-guide:

How to use the Hooks build type
================================

The ``Hooks`` build type allows customising the configuration and building
of a package using a collection of **hooks** into the build system.
It was introduced in Cabal 3.14 as a replacement for the
:ref:`Custom build type <custom-setup>`.

See also:

  - `Hackage documentation for the Cabal-hooks package
    <https://hackage.haskell.org/package/Cabal-hooks>`__
  - `Haskell Tech Proposal introducing the feature
    <https://github.com/haskellfoundation/tech-proposals/blob/main/rfc/060-replacing-cabal-custom-build.md>`__
  - `Introductory blog post
    <https://well-typed.com/blog/2025/01/cabal-hooks>`__

.. _setup-hooks-motivation:

Why use ``build-type: Hooks``?
-------------------------------

The main problem with ``build-type: Custom`` is that it is a wholesale replacement
of the entire build system, which means it doesn't integrate with other tooling,
such as the Haskell Language Server.

``build-type: Hooks`` aims to remedy this by guaranteeing that the main phases
for configuring/building a package are still the ``Cabal`` ``configure`` and
``build`` phases, while allowing custom hooks to run in between.

Another advantage of ``build-type: Hooks`` is that it is defined as a Haskell
library interface, instead of the command-line interface of ``Setup.hs``.


.. _setup-hooks-usage:

How to use ``build-type: Hooks``
----------------------------------

To define a package with ``build-type: Hooks``, you will need the following:

Update your ``.cabal`` file to set ``build-type: Hooks``, using a ``custom-setup``
stanza to declare the dependencies of your ``SetupHooks``:

.. code-block:: cabal

    cabal-version: 3.14
    build-type: Hooks

    custom-setup
      setup-depends:
        base        >= 4.18 && < 5,
        Cabal-hooks >= 3.16 && < 3.18

Then, define a Haskell module called ``SetupHooks.hs`` next to your ``.cabal``
file. For example:

.. code-block:: haskell

    module SetupHooks where
    import Distribution.Simple.SetupHooks ( SetupHooks, noSetupHooks )

    setupHooks :: SetupHooks
    setupHooks =
     noSetupHooks
       { configureHooks = myConfigureHooks
       , buildHooks = myBuildHooks }

This ``SetupHooks.hs`` module is where you define all of the hooks into the
build system. The following hooks exist:

  * Hooks into the configure phase:

    * Package-wide pre-configure hook. Used for custom ``./configure``-style logic.
    * Package-wide post-configure hook. Used mostly to write information to disk
      for the per-component configure hooks.
    * Per-component pre-configure hook. Used to modify individual components in
      the package description, e.g. specifying per-component build flags or
      declaring autogenerated modules.

  * Hooks into the build phase (per component):

    * Pre-build rules. Used to define code generators or custom preprocessors,
      i.e. rules to generate source files.
    * Post-build hook. Used before linking to inject data into build artifacts.

  * Hooks into the install phase (per component). These are used to copy over
    extra files when installing an executable.

.. _setup-hooks-basic-hooks:

Basic hooks
-----------

With the exception of pre-build rules, all hooks take the form:

.. code-block:: haskell

    HookInput -> IO HookOutput

Specifying a hook thus amounts to providing a Haskell function of that type.

For the package-wide pre-configure, we have:

.. code-block:: haskell

    type PreConfPackageHook = PreConfPackageInputs -> IO PreConfPackageOutputs

and a typical hook would look like:

.. code-block:: haskell

    myPreConfPackageHook :: PreConfPackageHook
    myPreConfPackageHook inputs@(PreConfPackageInputs {..}) = do
       ... -- custom logic goes here (e.g. querying system properties)
       let myNewBuildOptions = ...
           newConfiguredProgs = ...
       return $
         (noPreConfPackageOutputs inputs)
           { buildOptions = myNewBuildOptions
           , extraConfiguredProgs = newConfiguredProgs
           }

For per-component pre-configure hooks, we have:

.. code-block:: haskell

    type PreConfComponentHook = PreConfComponentInputs -> IO PreConfComponentOutputs

and a typical hook would look like:

.. code-block:: haskell

    myPreConfComponentHook :: PreConfComponentHook
    myPreConfComponentHook inputs@(PreConfComponentInputs { component = comp, .. }) =
      case componentName comp of
        CLibName LMainLibName -> do
          ... -- custom logic goes here (e.g. parsing an XML schema)
          let newModules = ...
              myLdOptions = ...
          return $
            (noPreConfComponentOutputs inputs)
              { componentDiff =
                  ComponentDiff $ CLib $
                    emptyLibrary
                      { exposedModules = newModules
                      , libBuildInfo   =
                          emptyBuildInfo
                            { autogenModules = newModules
                            , ldOptions = myLdOptions
                            }
                      }
              }
        _ -> return (noPreConfComponentOutputs inputs)

Post-build hooks and install hooks are similar.

Once you have defined all the hooks you need, finish by populating the
``SetupHooks`` record:

.. code-block:: haskell

    module SetupHooks ( setupHooks ) where

    import Distribution.Simple.SetupHooks

    setupHooks :: SetupHooks
    setupHooks =
      noSetupHooks
        { configureHooks = noConfigureHooks
            { preConfPackageHook = Just myPreConfPackageHook
            , preConfComponentHook = Just myPreConfComponentHook
            }
        }

.. _setup-hooks-pre-build-rules:

Pre-build rules
---------------

A pre-build rule is a specification of what command to run in order to generate
a collection of outputs (most commonly Haskell source modules). Each rule is
registered separately, and rules can depend on each other. This allows for
fine-grained recompilation logic, where only outdated rules are re-run.

A good starting point is the `Hackage documentation for pre-build rules
<https://hackage.haskell.org/package/Cabal-hooks/docs/Distribution-Simple-SetupHooks.html#t:Rules>`__.

Let's work through example usage of the API. First off, we define some code
generators. These must be Haskell functions that take a single argument and
return `IO ()`.

.. code-block:: haskell

    {-# LANGUAGE DeriveAnyClass, DeriveGeneric, DerivingStrategies #-}

    -- An example generator (a Haskell function).
    runMyPP :: MyPPInput -> IO ()
    runMyPP (MyPPInput {..}) = ...

    -- Custom datatype for the argument to our single code generator.
    data MyPPInput
      = MyPPInput
        { ppVerbFlags :: VerbosityFlags
        , ppSrcDir    :: SymbolicPath Pkg (Dir Source)
        , ppOutDir    :: SymbolicPath Pkg (Dir Source)
        , ppBaseName  :: String
        }
      deriving stock    ( Show, Eq, Generic )
      deriving anyclass Binary

Here we've defined a single generator, ``runMyPP``. The idea is that it takes
an input file with extension ``.myPpExt`` in the source directory and output a Haskell
source file in the output directory.

We defined a custom argument type ``MyPPInput`` for our code generator. This
argument type must be serialisable and have equality, so we derive
``Eq`` and ``Binary``. It would be fine to just use a tuple as well, but
a datatype is a bit more readable.

Next, we should search for all input files that need to be preprocessed, and
register one pre-build rule for each such file:

.. code-block:: haskell

    {-# LANGUAGE StaticPointers #-}

    preBuildRules :: PreBuildComponentInputs -> RulesM ()
    preBuildRules pbci@(PreBuildComponentInputs { buildingWhat = what, localBuildInfo = lbi, targetInfo = tgt }) = do
      let clbi       = targetCLBI tgt
          autogenDir = autogenComponentModulesDir lbi clbi

      -- Scan the source directories for .ppExt files, registering one rule each.
      inputFiles <- findAndMonitorSourceDirsFileExts pbci ("ppExt" NE.:| [])
      for_ inputFiles $ \loc@(Location srcDir relPath) -> do
        let baseName = dropExtension (getSymbolicPath relPath)
        registerRule_ (fromString $ "myPP:" ++ baseName) $
          staticRule
            (mkCommand (static Dict) (static runMyPP) $
               MyPPInput { ppVerbFlags = buildingWhatVerbosity what
                         , ppSrcDir    = srcDir
                         , ppOutDir    = autogenDir
                         , ppBaseName  = baseName })
            -- Inputs of the rule: the ".ppExt" file in the source tree.
            [ FileDependency loc ]
            -- Outputs of the rule: a corresponding ".hs" file, in the
            -- directory for autogenerated modules.
            ( Location autogenDir (makeRelativePathEx (baseName <.> "hs")) NE.:| [] )

Here, each rule has a single dependency (on the input ``.ppExt`` file), and
produces a single output (the ``.hs`` file). It is perfectly possible to define
rules that don't depend on any files on disk, or that only depend on other rules.

Note that a rule should never depend on a file generated by another rule; instead
you should declare a dependency on the rule directly (using ``RuleDependency``
instead of ``FileDependency``, with the ``RuleId`` returned by ``registerRule``).

The ``StaticPointers`` extension is used to ensure that code generators are
static (and that the instances they need, such as ``Binary MyPPInput``, are also
static). This is explained in `the Haskell Tech Proposal <https://github.com/haskellfoundation/tech-proposals/blob/main/rfc/060-replacing-cabal-custom-build.md#proposed-design-of-rules>`__,
but you don't really need to know the justification to use the API.
The essential restriction is that any code enclosed by ``static`` cannot refer
to locally-bound variables. It is thus crucial that ``runMyPP`` is defined as a
top-level function, and any arguments it needs must be passed via its argument
type (in this case, the ``MyPPInput`` type).
Refer to the `GHC user guide entry on Static Pointers <https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/static_pointers.html>`__
for more information.

In this example, all rule dependencies are static, so we used ``staticRule``.
For rules with dynamic dependencies (e.g. parsing dependencies from the
input files), you can use ``dynamicRule``. The API for this is more complex;
refer to `the Hackage documentation <https://hackage-content.haskell.org/package/Cabal-hooks-3.16/docs/Distribution-Simple-SetupHooks.html#v:dynamicRule>`__
for details.

A fully worked out representative example usage of pre-build rules, for
preprocessing LBNF grammars, can be found
`here <https://github.com/wenkokke/cabal-hooks-example/blob/main/SetupHooks.hs>`__.
