Cabal-hooks (empty) → 3.14
raw patch · 6 files changed
+642/−0 lines, 6 filesdep +Cabaldep +Cabal-syntaxdep +basesetup-changed
Dependencies added: Cabal, Cabal-syntax, base, containers, transformers
Files
- Cabal-hooks.cabal +68/−0
- LICENSE +34/−0
- Setup.hs +4/−0
- changelog.md +6/−0
- readme.md +64/−0
- src/Distribution/Simple/SetupHooks.hs +466/−0
+ Cabal-hooks.cabal view
@@ -0,0 +1,68 @@+cabal-version: 2.2 +name: Cabal-hooks +version: 3.14 +copyright: 2023, Cabal Development Team +license: BSD-3-Clause +license-file: LICENSE +author: Cabal Development Team <cabal-devel@haskell.org> +maintainer: cabal-devel@haskell.org +homepage: http://www.haskell.org/cabal/ +bug-reports: https://github.com/haskell/cabal/issues +synopsis: API for the Hooks build-type +description: + User-facing API for the Hooks build-type. +category: Distribution +build-type: Simple + +extra-source-files: + readme.md changelog.md + +source-repository head + type: git + location: https://github.com/haskell/cabal/ + subdir: Cabal-hooks + +library + default-language: Haskell2010 + hs-source-dirs: src + + build-depends: + Cabal-syntax >= 3.14 && < 3.15, + Cabal >= 3.14 && < 3.15, + base >= 4.13 && < 5, + containers >= 0.5.0.0 && < 0.8, + transformers >= 0.5.6.0 && < 0.7 + + ghc-options: -Wall -fno-ignore-asserts -fwarn-tabs -fwarn-incomplete-uni-patterns -fwarn-incomplete-record-updates + + exposed-modules: + Distribution.Simple.SetupHooks + + other-extensions: + BangPatterns + CPP + DefaultSignatures + DeriveDataTypeable + DeriveFoldable + DeriveFunctor + DeriveGeneric + DeriveTraversable + ExistentialQuantification + FlexibleContexts + FlexibleInstances + GeneralizedNewtypeDeriving + ImplicitParams + KindSignatures + LambdaCase + NondecreasingIndentation + OverloadedStrings + PatternSynonyms + RankNTypes + RecordWildCards + ScopedTypeVariables + StandaloneDeriving + Trustworthy + TypeFamilies + TypeOperators + TypeSynonymInstances + UndecidableInstances
+ LICENSE view
@@ -0,0 +1,34 @@+Copyright (c) 2003-2023, Cabal Development Team. +See the AUTHORS file for the full list of copyright holders. + +See */LICENSE for the copyright holders of the subcomponents. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Isaac Jones nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,4 @@+import Distribution.Simple + +main :: IO () +main = defaultMain
+ changelog.md view
@@ -0,0 +1,6 @@+# Changelog for `Cabal-hooks` + +## 3.14 – November 2024 + + * Initial release of the `Hooks` API. +
+ readme.md view
@@ -0,0 +1,64 @@+# `Cabal-hooks` + +This library provides an API for the `Cabal` `Hooks` build type. + +## What is the `Hooks` build type? + +The `Hooks` build type is a new `Cabal` build type that is scheduled to +replace the `Custom` build type, providing better integration with +the rest of the Haskell ecosystem. + +The original specification for the `Hooks` build type can be found in +the associated [Haskell Foundation Tech Proposal](https://github.com/haskellfoundation/tech-proposals/pull/60). + +These *setup hooks* allow package authors to customise the configuration and +building of a package by providing certain hooks that get folded into the +general package configuration and building logic within `Cabal`. + +## Defining a package with custom hooks + +To use the `Hooks` build type, you will need to + + * Update your `.cabal` file by: + + - using `cabal-version >= 3.14`, + - declaring `build-type: Hooks`, + - declaring a `custom-setup` stanza, with a `setup-depends` + field which includes a dependency on `Cabal-hooks`. + + * Define a Haskell module `SetupHooks`, which must be placed + at the root of your project and must define a value + `setupHooks :: SetupHooks`. + +That is, your `.cabal` file should contain the following + +```cabal +-- my-package.cabal +cabal-version: 3.14 +name: my-package +build-type: Hooks + +custom-setup + setup-depends: + Cabal-hooks >= 0.1 && < 0.2 +``` + +and your `SetupHooks.hs` file should look like: + +```haskell +-- SetupHooks.hs +module SetupHooks ( setupHooks ) where + +-- Cabal-hooks +import Distribution.Simple.SetupHooks + +setupHooks :: SetupHooks +setupHooks = ... + -- use the API provided by 'Distribution.Simple.SetupHooks' + -- to define the hooks relevant to your package +``` + +## Using the API + +The [Haddock documentation](https://hackage.haskell.org/package/Cabal-hooks) +should help you get started using this library's API.
+ src/Distribution/Simple/SetupHooks.hs view
@@ -0,0 +1,466 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecursiveDo #-}+{-# LANGUAGE StaticPointers #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE OverloadedStrings #-}++{-|+Module: Distribution.Simple.SetupHooks+Description: Interface for the @Hooks@ @build-type@.++This module defines the interface for the @Hooks@ @build-type@.++To write a package that implements @build-type: Hooks@, you should define+a module @SetupHooks.hs@ which exports a value @setupHooks :: 'SetupHooks'@.+This is a record that declares actions that should be hooked into the+cabal build process.++See 'SetupHooks' for more details.+-}+module Distribution.Simple.SetupHooks+ ( -- * Hooks++ -- $setupHooks+ SetupHooks(..)+ , noSetupHooks++ -- * Configure hooks++ -- $configureHooks+ , ConfigureHooks(..)+ , noConfigureHooks+ -- ** Per-package configure hooks+ , PreConfPackageInputs(..)+ , PreConfPackageOutputs(..) -- See Note [Not hiding SetupHooks constructors]+ , noPreConfPackageOutputs+ , PreConfPackageHook+ , PostConfPackageInputs(..)+ , PostConfPackageHook+ -- ** Per-component configure hooks+ , PreConfComponentInputs(..)+ , PreConfComponentOutputs(..) -- See Note [Not hiding SetupHooks constructors]+ , noPreConfComponentOutputs+ , PreConfComponentHook+ , ComponentDiff(..), emptyComponentDiff, buildInfoComponentDiff+ , LibraryDiff, ForeignLibDiff, ExecutableDiff+ , TestSuiteDiff, BenchmarkDiff+ , BuildInfoDiff++ -- * Build hooks++ , BuildHooks(..), noBuildHooks+ , BuildingWhat(..), buildingWhatVerbosity, buildingWhatDistPref++ -- ** Pre-build rules++ -- $preBuildRules+ , PreBuildComponentInputs(..)+ , PreBuildComponentRules++ -- ** Post-build hooks+ , PostBuildComponentInputs(..)+ , PostBuildComponentHook++ -- ** Rules+ , Rules+ , rules+ , noRules+ , Rule+ , Dependency (..)+ , RuleOutput (..)+ , RuleId+ , staticRule, dynamicRule+ -- *** Rule inputs/outputs++ -- $rulesDemand+ , Location(..)+ , location+ , autogenComponentModulesDir+ , componentBuildDir++ -- *** Actions+ , RuleCommands(..)+ , Command+ , mkCommand+ , Dict(..)++ -- *** Rules API++ -- $rulesAPI+ , RulesM+ , registerRule+ , registerRule_++ -- **** File/directory monitoring+ , addRuleMonitors+ , module Distribution.Simple.FileMonitor.Types++ -- * Install hooks+ , InstallHooks(..), noInstallHooks+ , InstallComponentInputs(..), InstallComponentHook++ -- * Re-exports++ -- ** Hooks+ -- *** Configure hooks+ , ConfigFlags(..)+ -- *** Build hooks+ , BuildFlags(..), ReplFlags(..), HaddockFlags(..), HscolourFlags(..)+ -- *** Install hooks+ , CopyFlags(..)++ -- ** @Hooks@ API+ --+ -- | These are functions provided as part of the @Hooks@ API.+ -- It is recommended to import them from this module as opposed to+ -- manually importing them from inside the Cabal module hierarchy.++ -- *** Copy/install functions+ , installFileGlob++ -- *** Interacting with the program database+ , Program(..), ConfiguredProgram(..), ProgArg+ , ProgramLocation(..)+ , ProgramDb+ , addKnownPrograms+ , configureUnconfiguredProgram+ , simpleProgram++ -- ** General @Cabal@ datatypes+ , Verbosity, Compiler(..), Platform(..), Suffix(..)++ -- *** Package information+ , LocalBuildConfig, LocalBuildInfo, PackageBuildDescr+ -- NB: we can't simply re-export all the fields of LocalBuildConfig etc,+ -- due to the presence of duplicate record fields.+ -- Ideally, we'd like to e.g. re-export LocalBuildConfig qualified,+ -- but qualified re-exports aren't a thing currently.++ , PackageDescription(..)++ -- *** Component information+ , Component(..), ComponentName(..), componentName+ , BuildInfo(..), emptyBuildInfo+ , TargetInfo(..), ComponentLocalBuildInfo(..)++ -- **** Components+ , Library(..), ForeignLib(..), Executable(..)+ , TestSuite(..), Benchmark(..)+ , LibraryName(..)+ , emptyLibrary, emptyForeignLib, emptyExecutable+ , emptyTestSuite, emptyBenchmark++ )+where+import Distribution.PackageDescription+ ( PackageDescription(..)+ , Library(..), ForeignLib(..)+ , Executable(..), TestSuite(..), Benchmark(..)+ , emptyLibrary, emptyForeignLib+ , emptyExecutable, emptyBenchmark, emptyTestSuite+ , BuildInfo(..), emptyBuildInfo+ , ComponentName(..), LibraryName(..)+ )+import Distribution.Simple.BuildPaths+ ( autogenComponentModulesDir )+import Distribution.Simple.Compiler+ ( Compiler(..) )+import Distribution.Simple.Errors+ ( CabalException(SetupHooksException) )+import Distribution.Simple.FileMonitor.Types+import Distribution.Simple.Install+ ( installFileGlob )+import Distribution.Simple.LocalBuildInfo+ ( componentBuildDir )+import Distribution.Simple.PreProcess.Types+ ( Suffix(..) )+import Distribution.Simple.Program.Db+ ( ProgramDb, addKnownPrograms+ , configureUnconfiguredProgram+ )+import Distribution.Simple.Program.Find+ ( simpleProgram )+import Distribution.Simple.Program.Types+ ( Program(..), ConfiguredProgram(..)+ , ProgArg+ , ProgramLocation(..)+ )+import Distribution.Simple.Setup+ ( BuildFlags(..)+ , ConfigFlags(..)+ , CopyFlags(..)+ , HaddockFlags(..)+ , HscolourFlags(..)+ , ReplFlags(..)+ )+import Distribution.Simple.SetupHooks.Errors+import Distribution.Simple.SetupHooks.Internal+import Distribution.Simple.SetupHooks.Rule as Rule+import Distribution.Simple.Utils+ ( dieWithException )+import Distribution.System+ ( Platform(..) )+import Distribution.Types.Component+ ( Component(..), componentName )+import Distribution.Types.ComponentLocalBuildInfo+ ( ComponentLocalBuildInfo(..) )+import Distribution.Types.LocalBuildInfo+ ( LocalBuildInfo(..) )+import Distribution.Types.LocalBuildConfig+ ( LocalBuildConfig, PackageBuildDescr )+import Distribution.Types.TargetInfo+ ( TargetInfo(..) )+import Distribution.Utils.ShortText+ ( ShortText )+import Distribution.Verbosity+ ( Verbosity )++import Control.Monad+ ( void )+import Control.Monad.IO.Class+ ( MonadIO(liftIO) )+import Control.Monad.Trans.Class+ ( lift )+import qualified Control.Monad.Trans.Reader as Reader+import qualified Control.Monad.Trans.State as State+#if MIN_VERSION_transformers(0,5,6)+import qualified Control.Monad.Trans.Writer.CPS as Writer+#else+import qualified Control.Monad.Trans.Writer.Strict as Writer+#endif+import Data.Foldable+ ( for_ )+import Data.Map.Strict as Map+ ( insertLookupWithKey )++--------------------------------------------------------------------------------+-- Haddocks for the SetupHooks API++{- $setupHooks+A Cabal package with @Hooks@ @build-type@ must define the Haskell module+@SetupHooks@ which defines a value @setupHooks :: 'SetupHooks'@.++These *setup hooks* allow package authors to customise the configuration and+building of a package by providing certain hooks that get folded into the+general package configuration and building logic within @Cabal@.++This mechanism replaces the @Custom@ @build-type@, providing better+integration with the rest of the Haskell ecosystem.++Usage example:++> -- In your .cabal file+> build-type: Hooks+>+> custom-setup+> setup-depends:+> base >= 4.18 && < 5,+> Cabal-hooks >= 3.14 && < 3.15+>+> The declared Cabal version should also be at least 3.14.++> -- In SetupHooks.hs, next to your .cabal file+> module SetupHooks where+> import Distribution.Simple.SetupHooks ( SetupHooks, noSetupHooks )+>+> setupHooks :: SetupHooks+> setupHooks =+> noSetupHooks+> { configureHooks = myConfigureHooks+> , buildHooks = myBuildHooks }++Note that 'SetupHooks' can be monoidally combined, e.g.:++> module SetupHooks where+> import Distribution.Simple.SetupHooks+> import qualified SomeOtherLibrary ( setupHooks )+>+> setupHooks :: SetupHooks+> setupHooks = SomeOtherLibrary.setupHooks <> mySetupHooks+>+> mySetupHooks :: SetupHooks+> mySetupHooks = ...+-}++{- $configureHooks+Configure hooks can be used to augment the Cabal configure logic with+package-specific logic. The main principle is that the configure hooks can+feed into updating the 'PackageDescription' of a @cabal@ package. From then on,+this package configuration is set in stone, and later hooks (e.g. hooks into+the build phase) can no longer modify this configuration; instead they will+receive this configuration in their inputs, and must honour it.++Configuration happens at two levels:++ * global configuration covers the entire package,+ * local configuration covers a single component.++Once the global package configuration is done, all hooks work on a+per-component level. The configuration hooks thus follow a simple philosophy:++ * All modifications to global package options must use `preConfPackageHook`.+ * All modifications to component configuration options must use `preConfComponentHook`.++For example, to generate modules inside a given component, you should:++ * In the per-component configure hook, declare the modules you are going to+ generate by adding them to the `autogenModules` field for that component+ (unless you know them ahead of time, in which case they can be listed+ textually in the @.cabal@ file of the project).+ * In the build hooks, describe the actions that will generate these modules.+-}++{- $preBuildRules+Pre-build hooks are specified as a collection of pre-build 'Rules'.+Each t'Rule' consists of:++ - a specification of its static dependencies and outputs,+ - the commands that execute the rule.++Rules are constructed using either one of the 'staticRule' or 'dynamicRule'+smart constructors. Directly constructing a t'Rule' using the constructors of+that data type is not advised, as this relies on internal implementation details+which are subject to change in between versions of the `Cabal-hooks` library.++Note that:++ - To declare the dependency on the output of a rule, one must refer to the+ rule directly, and not to the path to the output executing that rule will+ eventually produce.+ To do so, registering a t'Rule' with the API returns a unique identifier+ for that rule, in the form of a t'RuleId'.+ - File dependencies and outputs are not specified directly by+ 'FilePath', but rather use the 'Location' type (which is more convenient+ when working with preprocessors).+ - Rules refer to the actions that execute them using static pointers, in order+ to enable serialisation/deserialisation of rules.+ - Rules can additionally monitor files or directories, which determines+ when to re-compute the entire set of rules.+-}++{- $rulesDemand+Rules can declare various kinds of dependencies:++ - 'staticDependencies': files or other rules that a rule statically depends on,+ - extra dynamic dependencies, using the 'DynamicRuleCommands' constructor,+ - 'MonitorFilePath': additional files and directories to monitor.++Rules are considered __out-of-date__ precisely when any of the following+conditions apply:++ [O1] there has been a (relevant) change in the files and directories+ monitored by the rules,+ [O2] the environment passed to the computation of rules has changed.++If the rules are out-of-date, the build system is expected to re-run the+computation that computes all rules.++After this re-computation of the set of all rules, we match up new rules+with old rules, by 'RuleId'. A rule is then considered __stale__ if any of+following conditions apply:++ [N] the rule is new, or+ [S] the rule matches with an old rule, and either:++ [S1] a file dependency of the rule has been modified/created/deleted, or+ a (transitive) rule dependency of the rule is itself stale, or+ [S2] the rule is different from the old rule, e.g. the argument stored in+ the rule command has changed, or the pointer to the action to run the+ rule has changed. (This is determined using the @Eq Rule@ instance.)++A stale rule becomes no longer stale once we run its associated action. The+build system is responsible for re-running the actions associated with+each stale rule, in dependency order. This means the build system is expected+to behave as follows:++ 1. Any time the rules are out-of-date, query the rules to obtain+ up-to-date rules.+ 2. Re-run stale rules.+-}++{- $rulesAPI+Defining pre-build rules can be done in the following style:++> {-# LANGUAGE BlockArguments, StaticPointers #-}+> myPreBuildRules :: PreBuildComponentRules+> myPreBuildRules = rules (static ()) $ \ preBuildEnvironment -> do+> let cmd1 = mkCommand (static Dict) $ static \ arg -> do { .. }+> cmd2 = mkCommand (static Dict) $ static \ arg -> do { .. }+> myData <- liftIO someIOAction+> addRuleMonitors [ monitorDirectory "someSearchDir" ]+> registerRule_ "rule_1_1" $ staticRule (cmd1 arg1) deps1 outs1+> registerRule_ "rule_1_2" $ staticRule (cmd1 arg2) deps2 outs2+> registerRule_ "rule_1_3" $ staticRule (cmd1 arg3) deps3 outs3+> registerRule_ "rule_2_4" $ staticRule (cmd2 arg4) deps4 outs4++Here we use the 'rules', 'staticRule' and 'mkCommand' smart constructors,+rather than directly using the v'Rules', v'Rule' and v'Command' constructors,+which insulates us from internal changes to the t'Rules', t'Rule' and t'Command'+datatypes, respectively.++We use 'addRuleMonitors' to declare a monitored directory that the collection+of rules as a whole depends on. In this case, we declare that they depend on the+contents of the "searchDir" directory. This means that the rules will be+computed anew whenever the contents of this directory change.+-}++{- Note [Not hiding SetupHooks constructors]+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+We would like to hide as many datatype constructors from the API as possible+and provide smart constructors instead, so that hook authors don't end up+depending on internal implementation details that are subject to change.++However, doing so significantly degrades the Haddock documentation. So we+instead opt for exposing the constructor, but suggesting users use the+corresponding smart constructor instead.+-}++--------------------------------------------------------------------------------+-- API functions++-- | Register a rule. Returns an identifier for that rule.+registerRule+ :: ShortText -- ^ user-given rule name;+ -- these should be unique on a per-package level+ -> Rule -- ^ the rule to register+ -> RulesM RuleId+registerRule nm !newRule = RulesT $ do+ RulesEnv { rulesEnvNameSpace = ns+ , rulesEnvVerbosity = verbosity } <- Reader.ask+ oldRules <- lift $ State.get+ let rId = RuleId { ruleNameSpace = ns, ruleName = nm }+ (mbDup, newRules) = Map.insertLookupWithKey (\ _ new _old -> new) rId newRule oldRules+ for_ mbDup $ \ oldRule ->+ liftIO $ dieWithException verbosity+ $ SetupHooksException+ $ RulesException+ $ DuplicateRuleId rId oldRule newRule+ lift $ State.put newRules+ return rId++-- | Register a rule, discarding the produced 'RuleId'.+--+-- Using this function means that you don't expect any other rules to ever+-- depend on any outputs of this rule. Use 'registerRule' to retain the+-- 'RuleId' instead.+registerRule_+ :: ShortText -- ^ user-given rule name;+ -- these should be unique on a per-package level+ -> Rule -- ^ the rule to register+ -> RulesT IO ()+registerRule_ i r = void $ registerRule i r++-- | Declare additional monitored objects for the collection of all rules.+--+-- When these monitored objects change, the rules are re-computed.+addRuleMonitors :: Monad m => [MonitorFilePath] -> RulesT m ()+addRuleMonitors = RulesT . lift . lift . Writer.tell+{-# INLINEABLE addRuleMonitors #-}++-- TODO: add API functions that search and declare the appropriate monitoring+-- at the same time.