packages feed

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 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.