Cabal-3.16.1.0: src/Distribution/Simple/SetupHooks/Internal.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
-- |
-- Module: Distribution.Simple.SetupHooks.Internal
--
-- Internal implementation module.
-- Users of @build-type: Hooks@ should import "Distribution.Simple.SetupHooks"
-- instead.
module Distribution.Simple.SetupHooks.Internal
( -- * The setup hooks datatype
SetupHooks (..)
, noSetupHooks
-- * Configure hooks
, ConfigureHooks (..)
, noConfigureHooks
-- ** Per-package configure hooks
, PreConfPackageInputs (..)
, PreConfPackageOutputs (..)
, noPreConfPackageOutputs
, PreConfPackageHook
, PostConfPackageInputs (..)
, PostConfPackageHook
-- ** Per-component configure hooks
, PreConfComponentInputs (..)
, PreConfComponentOutputs (..)
, noPreConfComponentOutputs
, PreConfComponentHook
, ComponentDiff (..)
, emptyComponentDiff
, buildInfoComponentDiff
, LibraryDiff
, ForeignLibDiff
, ExecutableDiff
, TestSuiteDiff
, BenchmarkDiff
, BuildInfoDiff
-- * Build hooks
, BuildHooks (..)
, noBuildHooks
, BuildingWhat (..)
, buildingWhatVerbosity
, buildingWhatWorkingDir
, buildingWhatDistPref
-- ** Pre-build rules
, PreBuildComponentInputs (..)
, PreBuildComponentRules
-- ** Post-build hook
, PostBuildComponentInputs (..)
, PostBuildComponentHook
-- * Install hooks
, InstallHooks (..)
, noInstallHooks
, InstallComponentInputs (..)
, InstallComponentHook
-- * Internals
-- ** Per-component hook utilities
, applyComponentDiffs
, forComponents_
-- ** Executing build rules
, executeRules
-- ** HookedBuildInfo compatibility code
, hookedBuildInfoComponents
, hookedBuildInfoComponentDiff_maybe
)
where
import Distribution.Compat.Prelude
import Prelude ()
import Distribution.Compat.Lens ((.~))
import Distribution.PackageDescription
import Distribution.Simple.BuildPaths
import Distribution.Simple.Compiler (Compiler (..))
import Distribution.Simple.Errors
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Program.Db
import Distribution.Simple.Setup
( BuildingWhat (..)
, buildingWhatDistPref
, buildingWhatVerbosity
, buildingWhatWorkingDir
)
import Distribution.Simple.Setup.Build (BuildFlags (..))
import Distribution.Simple.Setup.Config (ConfigFlags (..))
import Distribution.Simple.Setup.Copy (CopyFlags (..))
import Distribution.Simple.SetupHooks.Errors
import Distribution.Simple.SetupHooks.Rule
import qualified Distribution.Simple.SetupHooks.Rule as Rule
import Distribution.Simple.Utils
import Distribution.System (Platform (..))
import Distribution.Utils.Path
import qualified Distribution.Types.BuildInfo.Lens as BI (buildInfo)
import Distribution.Types.LocalBuildConfig as LBC
import Distribution.Types.TargetInfo
import Distribution.Verbosity
import qualified Data.ByteString.Lazy as LBS
import Data.Coerce (coerce)
import qualified Data.Graph as Graph
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as Map
import qualified Data.Set as Set
import System.Directory (doesFileExist)
--------------------------------------------------------------------------------
-- SetupHooks
-- | Hooks into the @cabal@ build phases.
--
-- Usage:
--
-- - In your @.cabal@ file, declare @build-type: Hooks@
-- (with a @cabal-version@ greater than or equal to @3.14@),
-- - In your @.cabal@ file, include a @custom-setup@ stanza
-- which declares the dependencies of your @SetupHooks@ module;
-- this will usually contain a dependency on the @Cabal-hooks@ package.
-- - Provide a @SetupHooks.hs@ module next to your @.cabal@ file;
-- it must export @setupHooks :: SetupHooks@.
data SetupHooks = SetupHooks
{ configureHooks :: ConfigureHooks
-- ^ Hooks into the configure phase.
, buildHooks :: BuildHooks
-- ^ Hooks into the build phase.
--
-- These hooks are relevant to any build-like phase,
-- such as repl or haddock.
, installHooks :: InstallHooks
-- ^ Hooks into the copy/install phase.
}
-- | 'SetupHooks' can be combined monoidally. This is useful to combine
-- setup hooks defined by another package with your own package-specific
-- hooks.
--
-- __Warning__: this 'Semigroup' instance is not commutative.
instance Semigroup SetupHooks where
SetupHooks
{ configureHooks = conf1
, buildHooks = build1
, installHooks = inst1
}
<> SetupHooks
{ configureHooks = conf2
, buildHooks = build2
, installHooks = inst2
} =
SetupHooks
{ configureHooks = conf1 <> conf2
, buildHooks = build1 <> build2
, installHooks = inst1 <> inst2
}
instance Monoid SetupHooks where
mempty = noSetupHooks
-- | Empty hooks.
noSetupHooks :: SetupHooks
noSetupHooks =
SetupHooks
{ configureHooks = noConfigureHooks
, buildHooks = noBuildHooks
, installHooks = noInstallHooks
}
--------------------------------------------------------------------------------
-- Configure hooks.
type PreConfPackageHook = PreConfPackageInputs -> IO PreConfPackageOutputs
-- | Inputs to the package-wide pre-configure step.
data PreConfPackageInputs = PreConfPackageInputs
{ configFlags :: ConfigFlags
, localBuildConfig :: LocalBuildConfig
-- ^ Warning: the 'ProgramDb' in the 'withPrograms' field
-- will not contain any unconfigured programs.
, compiler :: Compiler
, platform :: Platform
}
deriving (Generic, Show)
-- | Outputs of the package-wide pre-configure step.
--
-- Prefer using 'noPreConfPackageOutputs' and overriding the fields
-- you care about, to avoid depending on implementation details
-- of this datatype.
data PreConfPackageOutputs = PreConfPackageOutputs
{ buildOptions :: BuildOptions
, extraConfiguredProgs :: ConfiguredProgs
}
deriving (Generic, Show)
-- | Use this smart constructor to declare an empty set of changes
-- by the package-wide pre-configure hook, and override the fields you
-- care about.
--
-- Use this rather than v'PreConfPackageOutputs' to avoid relying on
-- internal implementation details of the latter.
noPreConfPackageOutputs :: PreConfPackageInputs -> PreConfPackageOutputs
noPreConfPackageOutputs (PreConfPackageInputs{localBuildConfig = lbc}) =
PreConfPackageOutputs
{ buildOptions = LBC.withBuildOptions lbc
, extraConfiguredProgs = Map.empty
}
-- | Package-wide post-configure step.
--
-- Perform side effects. Last opportunity for any package-wide logic;
-- any subsequent hooks work per-component.
type PostConfPackageHook = PostConfPackageInputs -> IO ()
-- | Inputs to the package-wide post-configure step.
data PostConfPackageInputs = PostConfPackageInputs
{ localBuildConfig :: LocalBuildConfig
, packageBuildDescr :: PackageBuildDescr
}
deriving (Generic, Show)
-- | Per-component pre-configure step.
--
-- For each component of the package, this hook can perform side effects,
-- and return a diff to the passed in component, e.g. to declare additional
-- autogenerated modules.
type PreConfComponentHook = PreConfComponentInputs -> IO PreConfComponentOutputs
-- | Inputs to the per-component pre-configure step.
data PreConfComponentInputs = PreConfComponentInputs
{ localBuildConfig :: LocalBuildConfig
, packageBuildDescr :: PackageBuildDescr
, component :: Component
}
deriving (Generic, Show)
-- | Outputs of the per-component pre-configure step.
--
-- Prefer using 'noPreComponentOutputs' and overriding the fields
-- you care about, to avoid depending on implementation details
-- of this datatype.
data PreConfComponentOutputs = PreConfComponentOutputs
{ componentDiff :: ComponentDiff
}
deriving (Generic, Show)
-- | Use this smart constructor to declare an empty set of changes
-- by a per-component pre-configure hook, and override the fields you
-- care about.
--
-- Use this rather than v'PreConfComponentOutputs' to avoid relying on
-- internal implementation details of the latter.
noPreConfComponentOutputs :: PreConfComponentInputs -> PreConfComponentOutputs
noPreConfComponentOutputs (PreConfComponentInputs{component = comp}) =
PreConfComponentOutputs
{ componentDiff = emptyComponentDiff (componentName comp)
}
-- | Configure-time hooks.
--
-- Order of execution:
--
-- - 'preConfPackageHook',
-- - configure the package,
-- - 'postConfPackageHook',
-- - 'preConfComponentHook',
-- - configure the components.
data ConfigureHooks = ConfigureHooks
{ preConfPackageHook :: Maybe PreConfPackageHook
-- ^ Package-wide pre-configure hook. See 'PreConfPackageHook'.
, postConfPackageHook :: Maybe PostConfPackageHook
-- ^ Package-wide post-configure hook. See 'PostConfPackageHook'.
, preConfComponentHook :: Maybe PreConfComponentHook
-- ^ Per-component pre-configure hook. See 'PreConfComponentHook'.
}
-- Note: these configure hooks don't track any kind of dependency information,
-- so we won't know when the configuration is out of date and should be re-done.
-- This seems okay: it should only matter while developing the package, in which
-- case it seems acceptable to rely on the user re-configuring.
instance Semigroup ConfigureHooks where
ConfigureHooks
{ preConfPackageHook = prePkg1
, postConfPackageHook = postPkg1
, preConfComponentHook = preComp1
}
<> ConfigureHooks
{ preConfPackageHook = prePkg2
, postConfPackageHook = postPkg2
, preConfComponentHook = preComp2
} =
ConfigureHooks
{ preConfPackageHook =
coerce
((<>) @(Maybe PreConfPkgSemigroup))
prePkg1
prePkg2
, postConfPackageHook =
postPkg1 <> postPkg2
, preConfComponentHook =
coerce
((<>) @(Maybe PreConfComponentSemigroup))
preComp1
preComp2
}
instance Monoid ConfigureHooks where
mempty = noConfigureHooks
-- | Empty configure phase hooks.
noConfigureHooks :: ConfigureHooks
noConfigureHooks =
ConfigureHooks
{ preConfPackageHook = Nothing
, postConfPackageHook = Nothing
, preConfComponentHook = Nothing
}
-- | A newtype to hang off the @Semigroup PreConfPackageHook@ instance.
newtype PreConfPkgSemigroup = PreConfPkgSemigroup PreConfPackageHook
instance Semigroup PreConfPkgSemigroup where
PreConfPkgSemigroup f1 <> PreConfPkgSemigroup f2 =
PreConfPkgSemigroup $
\inputs@( PreConfPackageInputs
{ configFlags = cfg
, compiler = comp
, platform = plat
, localBuildConfig = lbc0
}
) ->
do
PreConfPackageOutputs
{ buildOptions = opts1
, extraConfiguredProgs = progs1
} <-
f1 inputs
PreConfPackageOutputs
{ buildOptions = opts2
, extraConfiguredProgs = progs2
} <-
f2 $
PreConfPackageInputs
{ configFlags = cfg
, compiler = comp
, platform = plat
, localBuildConfig =
lbc0
{ LBC.withPrograms =
updateConfiguredProgs (`Map.union` progs1) $
LBC.withPrograms lbc0
, LBC.withBuildOptions = opts1
}
}
return $
PreConfPackageOutputs
{ buildOptions = opts2
, extraConfiguredProgs = progs1 <> progs2
}
-- | A newtype to hang off the @Semigroup PreConfComponentHook@ instance.
newtype PreConfComponentSemigroup = PreConfComponentSemigroup PreConfComponentHook
instance Semigroup PreConfComponentSemigroup where
PreConfComponentSemigroup f1 <> PreConfComponentSemigroup f2 =
PreConfComponentSemigroup $ \inputs ->
do
PreConfComponentOutputs
{ componentDiff = diff1
} <-
f1 inputs
PreConfComponentOutputs
{ componentDiff = diff2
} <-
f2 inputs
return $
PreConfComponentOutputs
{ componentDiff = diff1 <> diff2
}
--------------------------------------------------------------------------------
-- Build setup hooks.
data PreBuildComponentInputs = PreBuildComponentInputs
{ buildingWhat :: BuildingWhat
-- ^ what kind of build phase are we hooking into?
, localBuildInfo :: LocalBuildInfo
-- ^ information about the package
, targetInfo :: TargetInfo
-- ^ information about an individual component
}
deriving (Generic, Show)
type PreBuildComponentRules = Rules PreBuildComponentInputs
data PostBuildComponentInputs = PostBuildComponentInputs
{ buildFlags :: BuildFlags
, localBuildInfo :: LocalBuildInfo
, targetInfo :: TargetInfo
}
deriving (Generic, Show)
type PostBuildComponentHook = PostBuildComponentInputs -> IO ()
-- | Build-time hooks.
data BuildHooks = BuildHooks
{ preBuildComponentRules :: Maybe PreBuildComponentRules
-- ^ Per-component fine-grained pre-build rules.
, postBuildComponentHook :: Maybe PostBuildComponentHook
-- ^ Per-component post-build hook.
}
-- Note that the pre-build hook consists of a function which takes a component
-- as an argument (as part of the targetInfo field) and returns a collection of
-- pre-build rules.
--
-- One might wonder why it isn't instead a collection of pre-build rules, one
-- for each component. The reason is that Backpack creates components on-the-fly
-- through instantiation, which means e.g. that a single component name can
-- resolve to multiple components. This means we really need to pass in the
-- components to the function, as we don't know the full details (e.g. their
-- unit ids) ahead of time.
instance Semigroup BuildHooks where
BuildHooks
{ preBuildComponentRules = rs1
, postBuildComponentHook = post1
}
<> BuildHooks
{ preBuildComponentRules = rs2
, postBuildComponentHook = post2
} =
BuildHooks
{ preBuildComponentRules = rs1 <> rs2
, postBuildComponentHook = post1 <> post2
}
instance Monoid BuildHooks where
mempty = noBuildHooks
-- | Empty build hooks.
noBuildHooks :: BuildHooks
noBuildHooks =
BuildHooks
{ preBuildComponentRules = Nothing
, postBuildComponentHook = Nothing
}
--------------------------------------------------------------------------------
-- Install setup hooks.
data InstallComponentInputs = InstallComponentInputs
{ copyFlags :: CopyFlags
, localBuildInfo :: LocalBuildInfo
, targetInfo :: TargetInfo
}
deriving (Generic, Show)
-- | A per-component install hook,
-- which can only perform side effects (e.g. copying files).
type InstallComponentHook = InstallComponentInputs -> IO ()
-- | Copy/install hooks.
data InstallHooks = InstallHooks
{ installComponentHook :: Maybe InstallComponentHook
-- ^ Per-component install hook.
}
instance Semigroup InstallHooks where
InstallHooks
{ installComponentHook = inst1
}
<> InstallHooks
{ installComponentHook = inst2
} =
InstallHooks
{ installComponentHook = inst1 <> inst2
}
instance Monoid InstallHooks where
mempty = noInstallHooks
-- | Empty copy/install hooks.
noInstallHooks :: InstallHooks
noInstallHooks =
InstallHooks
{ installComponentHook = Nothing
}
--------------------------------------------------------------------------------
-- Per-component configure hook implementation details.
type LibraryDiff = Library
type ForeignLibDiff = ForeignLib
type ExecutableDiff = Executable
type TestSuiteDiff = TestSuite
type BenchmarkDiff = Benchmark
type BuildInfoDiff = BuildInfo
-- | A diff to a Cabal 'Component', that gets combined monoidally into
-- an existing 'Component'.
newtype ComponentDiff = ComponentDiff {componentDiff :: Component}
deriving (Semigroup, Show)
emptyComponentDiff :: ComponentName -> ComponentDiff
emptyComponentDiff name = ComponentDiff $
case name of
CLibName{} -> CLib emptyLibrary
CFLibName{} -> CFLib emptyForeignLib
CExeName{} -> CExe emptyExecutable
CTestName{} -> CTest emptyTestSuite
CBenchName{} -> CBench emptyBenchmark
buildInfoComponentDiff :: ComponentName -> BuildInfo -> ComponentDiff
buildInfoComponentDiff name bi = ComponentDiff $
BI.buildInfo .~ bi $
case name of
CLibName{} -> CLib emptyLibrary
CFLibName{} -> CFLib emptyForeignLib
CExeName{} -> CExe emptyExecutable
CTestName{} -> CTest emptyTestSuite
CBenchName{} -> CBench emptyBenchmark
applyLibraryDiff :: Verbosity -> Library -> LibraryDiff -> IO Library
applyLibraryDiff verbosity lib diff =
case illegalLibraryDiffReasons lib diff of
[] -> return $ lib <> diff
(r : rs) ->
dieWithException verbosity $
SetupHooksException $
CannotApplyComponentDiff $
IllegalComponentDiff (CLib lib) (r NE.:| rs)
illegalLibraryDiffReasons :: Library -> LibraryDiff -> [IllegalComponentDiffReason]
illegalLibraryDiffReasons
lib
Library
{ libName = nm
, libExposed = e
, libVisibility = vis
, libBuildInfo = bi
} =
[ CannotChangeName
| not $ nm == libName emptyLibrary || nm == libName lib
]
++ [ CannotChangeComponentField "libExposed"
| not $ e == libExposed emptyLibrary || e == libExposed lib
]
++ [ CannotChangeComponentField "libVisibility"
| not $ vis == libVisibility emptyLibrary || vis == libVisibility lib
]
++ illegalBuildInfoDiffReasons (libBuildInfo lib) bi
applyForeignLibDiff :: Verbosity -> ForeignLib -> ForeignLibDiff -> IO ForeignLib
applyForeignLibDiff verbosity flib diff =
case illegalForeignLibDiffReasons flib diff of
[] -> return $ flib <> diff
(r : rs) ->
dieWithException verbosity $
SetupHooksException $
CannotApplyComponentDiff $
IllegalComponentDiff (CFLib flib) (r NE.:| rs)
illegalForeignLibDiffReasons :: ForeignLib -> ForeignLibDiff -> [IllegalComponentDiffReason]
illegalForeignLibDiffReasons
flib
ForeignLib
{ foreignLibName = nm
, foreignLibType = ty
, foreignLibOptions = opts
, foreignLibVersionInfo = vi
, foreignLibVersionLinux = linux
, foreignLibModDefFile = defs
, foreignLibBuildInfo = bi
} =
[ CannotChangeName
| not $ nm == foreignLibName emptyForeignLib || nm == foreignLibName flib
]
++ [ CannotChangeComponentField "foreignLibType"
| not $ ty == foreignLibType emptyForeignLib || ty == foreignLibType flib
]
++ [ CannotChangeComponentField "foreignLibOptions"
| not $ opts == foreignLibOptions emptyForeignLib || opts == foreignLibOptions flib
]
++ [ CannotChangeComponentField "foreignLibVersionInfo"
| not $ vi == foreignLibVersionInfo emptyForeignLib || vi == foreignLibVersionInfo flib
]
++ [ CannotChangeComponentField "foreignLibVersionLinux"
| not $ linux == foreignLibVersionLinux emptyForeignLib || linux == foreignLibVersionLinux flib
]
++ [ CannotChangeComponentField "foreignLibModDefFile"
| not $ defs == foreignLibModDefFile emptyForeignLib || defs == foreignLibModDefFile flib
]
++ illegalBuildInfoDiffReasons (foreignLibBuildInfo flib) bi
applyExecutableDiff :: Verbosity -> Executable -> ExecutableDiff -> IO Executable
applyExecutableDiff verbosity exe diff =
case illegalExecutableDiffReasons exe diff of
[] -> return $ exe <> diff
(r : rs) ->
dieWithException verbosity $
SetupHooksException $
CannotApplyComponentDiff $
IllegalComponentDiff (CExe exe) (r NE.:| rs)
illegalExecutableDiffReasons :: Executable -> ExecutableDiff -> [IllegalComponentDiffReason]
illegalExecutableDiffReasons
exe
Executable
{ exeName = nm
, modulePath = path
, exeScope = scope
, buildInfo = bi
} =
[ CannotChangeName
| not $ nm == exeName emptyExecutable || nm == exeName exe
]
++ [ CannotChangeComponentField "modulePath"
| not $ path == modulePath emptyExecutable || path == modulePath exe
]
++ [ CannotChangeComponentField "exeScope"
| not $ scope == exeScope emptyExecutable || scope == exeScope exe
]
++ illegalBuildInfoDiffReasons (buildInfo exe) bi
applyTestSuiteDiff :: Verbosity -> TestSuite -> TestSuiteDiff -> IO TestSuite
applyTestSuiteDiff verbosity test diff =
case illegalTestSuiteDiffReasons test diff of
[] -> return $ test <> diff
(r : rs) ->
dieWithException verbosity $
SetupHooksException $
CannotApplyComponentDiff $
IllegalComponentDiff (CTest test) (r NE.:| rs)
illegalTestSuiteDiffReasons :: TestSuite -> TestSuiteDiff -> [IllegalComponentDiffReason]
illegalTestSuiteDiffReasons
test
TestSuite
{ testName = nm
, testInterface = iface
, testCodeGenerators = gens
, testBuildInfo = bi
} =
[ CannotChangeName
| not $ nm == testName emptyTestSuite || nm == testName test
]
++ [ CannotChangeComponentField "testInterface"
| not $ iface == testInterface emptyTestSuite || iface == testInterface test
]
++ [ CannotChangeComponentField "testCodeGenerators"
| not $ gens == testCodeGenerators emptyTestSuite || gens == testCodeGenerators test
]
++ illegalBuildInfoDiffReasons (testBuildInfo test) bi
applyBenchmarkDiff :: Verbosity -> Benchmark -> BenchmarkDiff -> IO Benchmark
applyBenchmarkDiff verbosity bench diff =
case illegalBenchmarkDiffReasons bench diff of
[] -> return $ bench <> diff
(r : rs) ->
dieWithException verbosity $
SetupHooksException $
CannotApplyComponentDiff $
IllegalComponentDiff (CBench bench) (r NE.:| rs)
illegalBenchmarkDiffReasons :: Benchmark -> BenchmarkDiff -> [IllegalComponentDiffReason]
illegalBenchmarkDiffReasons
bench
Benchmark
{ benchmarkName = nm
, benchmarkInterface = iface
, benchmarkBuildInfo = bi
} =
[ CannotChangeName
| not $ nm == benchmarkName emptyBenchmark || nm == benchmarkName bench
]
++ [ CannotChangeComponentField "benchmarkInterface"
| not $ iface == benchmarkInterface emptyBenchmark || iface == benchmarkInterface bench
]
++ illegalBuildInfoDiffReasons (benchmarkBuildInfo bench) bi
illegalBuildInfoDiffReasons :: BuildInfo -> BuildInfoDiff -> [IllegalComponentDiffReason]
illegalBuildInfoDiffReasons
bi
BuildInfo
{ buildable = can_build
, buildTools = build_tools
, buildToolDepends = build_tools_depends
, pkgconfigDepends = pkgconfig_depends
, frameworks = fworks
, targetBuildDepends = target_build_depends
} =
map CannotChangeBuildInfoField $
[ "buildable"
| not $ can_build == buildable bi || can_build == buildable emptyBuildInfo
]
++ [ "buildTools"
| not $ build_tools == buildTools bi || build_tools == buildTools emptyBuildInfo
]
++ [ "buildToolsDepends"
| not $ build_tools_depends == buildToolDepends bi || build_tools_depends == buildToolDepends emptyBuildInfo
]
++ [ "pkgconfigDepends"
| not $ pkgconfig_depends == pkgconfigDepends bi || pkgconfig_depends == pkgconfigDepends emptyBuildInfo
]
++ [ "frameworks"
| not $ fworks == frameworks bi || fworks == frameworks emptyBuildInfo
]
++ [ "targetBuildDepends"
| not $ target_build_depends == targetBuildDepends bi || target_build_depends == targetBuildDepends emptyBuildInfo
]
-- | Traverse the components of a 'PackageDescription'.
--
-- The function must preserve the component type, i.e. map a 'CLib' to a 'CLib',
-- a 'CExe' to a 'CExe', etc.
traverseComponents
:: Applicative m
=> (Component -> m Component)
-> PackageDescription
-> m PackageDescription
traverseComponents f pd =
upd_pd
<$> traverse f_lib (library pd)
<*> traverse f_lib (subLibraries pd)
<*> traverse f_flib (foreignLibs pd)
<*> traverse f_exe (executables pd)
<*> traverse f_test (testSuites pd)
<*> traverse f_bench (benchmarks pd)
where
f_lib lib = \case { CLib lib' -> lib'; c -> mismatch (CLib lib) c } <$> f (CLib lib)
f_flib flib = \case { CFLib flib' -> flib'; c -> mismatch (CFLib flib) c } <$> f (CFLib flib)
f_exe exe = \case { CExe exe' -> exe'; c -> mismatch (CExe exe) c } <$> f (CExe exe)
f_test test = \case { CTest test' -> test'; c -> mismatch (CTest test) c } <$> f (CTest test)
f_bench bench = \case { CBench bench' -> bench'; c -> mismatch (CBench bench) c } <$> f (CBench bench)
upd_pd lib sublibs flibs exes tests benchs =
pd
{ library = lib
, subLibraries = sublibs
, foreignLibs = flibs
, executables = exes
, testSuites = tests
, benchmarks = benchs
}
-- This is a panic, because we maintain this invariant elsewhere:
-- see 'componentDiffError' in 'applyComponentDiff', which catches an
-- invalid per-component configure hook.
mismatch c1 c2 =
error $
"Mismatched component types: "
++ showComponentName (componentName c1)
++ " "
++ showComponentName (componentName c2)
++ "."
{-# INLINEABLE traverseComponents #-}
applyComponentDiffs
:: Verbosity
-> (Component -> IO (Maybe ComponentDiff))
-> PackageDescription
-> IO PackageDescription
applyComponentDiffs verbosity f = traverseComponents apply_diff
where
apply_diff :: Component -> IO Component
apply_diff c = do
mbDiff <- f c
case mbDiff of
Just diff -> applyComponentDiff verbosity c diff
Nothing -> return c
forComponents_ :: PackageDescription -> (Component -> IO ()) -> IO ()
forComponents_ pd f = getConst $ traverseComponents (Const . f) pd
applyComponentDiff
:: Verbosity
-> Component
-> ComponentDiff
-> IO Component
applyComponentDiff verbosity comp (ComponentDiff diff)
| CLib lib <- comp
, CLib lib_diff <- diff =
CLib <$> applyLibraryDiff verbosity lib lib_diff
| CFLib flib <- comp
, CFLib flib_diff <- diff =
CFLib <$> applyForeignLibDiff verbosity flib flib_diff
| CExe exe <- comp
, CExe exe_diff <- diff =
CExe <$> applyExecutableDiff verbosity exe exe_diff
| CTest test <- comp
, CTest test_diff <- diff =
CTest <$> applyTestSuiteDiff verbosity test test_diff
| CBench bench <- comp
, CBench bench_diff <- diff =
CBench <$> applyBenchmarkDiff verbosity bench bench_diff
| otherwise =
componentDiffError $ MismatchedComponentTypes comp diff
where
-- The per-component configure hook specified a diff of the wrong type,
-- e.g. tried to apply an executable diff to a library.
componentDiffError err =
dieWithException verbosity $
SetupHooksException $
CannotApplyComponentDiff err
--------------------------------------------------------------------------------
-- Running pre-build rules
-- | Run all pre-build rules.
--
-- This function should only be called internally within @Cabal@, as it is used
-- to implement the (legacy) Setup.hs interface. The build tool
-- (e.g. @cabal-install@ or @hls@) should instead go through the separate
-- hooks executable, which allows us to only rerun the out-of-date rules
-- (instead of running all of these rules at once).
executeRules
:: Verbosity
-> LocalBuildInfo
-> TargetInfo
-> Map RuleId Rule
-> IO ()
executeRules =
executeRulesUserOrSystem
SUser
(\_rId cmd -> sequenceA $ runRuleDynDepsCmd cmd)
(\_rId cmd -> runRuleExecCmd cmd)
-- | Like 'executeRules', except it can be used when communicating with
-- an external hooks executable.
executeRulesUserOrSystem
:: forall userOrSystem
. SScope userOrSystem
-> (RuleId -> RuleDynDepsCmd userOrSystem -> IO (Maybe ([Rule.Dependency], LBS.ByteString)))
-> (RuleId -> RuleExecCmd userOrSystem -> IO ())
-> Verbosity
-> LocalBuildInfo
-> TargetInfo
-> Map RuleId (RuleData userOrSystem)
-> IO ()
executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo allRules = do
-- Compute all extra dynamic dependency edges.
dynDepsEdges <-
flip Map.traverseMaybeWithKey allRules $
\rId (Rule{ruleCommands = cmds}) ->
runDepsCmdData rId (ruleDepsCmd cmds)
-- Create a build graph of all the rules, with static and dynamic dependencies
-- as edges.
let
(ruleGraph, ruleFromVertex, vertexFromRuleId) =
Graph.graphFromEdges
[ (rule, rId, nub $ mapMaybe directRuleDependencyMaybe allDeps)
| (rId, rule) <- Map.toList allRules
, let dynDeps = fromMaybe [] (fst <$> Map.lookup rId dynDepsEdges)
allDeps = staticDependencies rule ++ dynDeps
]
-- Topologically sort the graph of rules.
sccs = Graph.scc ruleGraph
cycles = mapMaybe $ \(Graph.Node v0 subforest) ->
case subforest of
[]
| r@(_, rId, deps) <- ruleFromVertex v0 ->
if rId `elem` deps
then Just (r, [])
else Nothing
v : vs ->
Just
( ruleFromVertex v0
, map (fmap ruleFromVertex) (v : vs)
)
-- Compute demanded rules.
--
-- SetupHooks TODO: maybe requiring all generated modules to appear
-- in autogen-modules is excessive; we can look through all modules instead.
autogenModPaths =
map (\m -> moduleNameSymbolicPath m <.> "hs") $
autogenModules $
componentBuildInfo $
targetComponent tgtInfo
leafRule_maybe (rId, r) =
if any ((r `ruleOutputsLocation`) . (Location compAutogenDir)) autogenModPaths
then vertexFromRuleId rId
else Nothing
leafRules = mapMaybe leafRule_maybe $ Map.toList allRules
demandedRuleVerts = Set.fromList $ concatMap (Graph.reachable ruleGraph) leafRules
nonDemandedRuleVerts = Set.fromList (Graph.vertices ruleGraph) Set.\\ demandedRuleVerts
case cycles sccs of
-- If there are cycles in the dependency structure, don't execute
-- any rules at all; just throw an error right off the bat.
r : rs ->
let getRule ((ru, _, _), js) = (toRuleBinary ru, fmap (fmap (\(rv, _, _) -> toRuleBinary rv)) js)
in errorOut $
CyclicRuleDependencies $
fmap getRule (r NE.:| rs)
-- Otherwise, run all the demanded rules in dependency order (in one go).
-- (Fine-grained running of rules should happen in cabal-install or HLS,
-- not in the Cabal library.)
[] -> do
-- Emit a warning if there are non-demanded rules.
unless (null nonDemandedRuleVerts) $
warn verbosity $
unlines $
"The following rules are not demanded and will not be run:"
: concat
[ [ " - " ++ show rId ++ ","
, " generating " ++ show (NE.toList $ results r)
]
| v <- Set.toList nonDemandedRuleVerts
, let (r, rId, _) = ruleFromVertex v
]
++ [ "Possible reasons for this error:"
, " - Some autogenerated modules were not declared"
, " (in the package description or in the pre-configure hooks)"
, " - The output location for an autogenerated module is incorrect,"
, " (e.g. the file extension is incorrect, or"
, " it is not in the appropriate 'autogenComponentModules' directory)"
]
-- Run all the demanded rules, in dependency order.
for_ sccs $ \(Graph.Node ruleVertex _) ->
-- Don't run a rule unless it is demanded.
unless (ruleVertex `Set.member` nonDemandedRuleVerts) $ do
let ( r@Rule
{ ruleCommands = cmds
, staticDependencies = staticDeps
, results = reslts
}
, rId
, _staticRuleDepIds
) =
ruleFromVertex ruleVertex
mbDyn = Map.lookup rId dynDepsEdges
allDeps = staticDeps ++ fromMaybe [] (fst <$> mbDyn)
-- Check that the dependencies the rule expects are indeed present.
resolvedDeps <- traverse (resolveDependency verbosity rId allRules) allDeps
missingRuleDeps <- filterM (missingDep mbWorkDir) resolvedDeps
case NE.nonEmpty missingRuleDeps of
Just missingDeps ->
errorOut $ CantFindSourceForRuleDependencies (toRuleBinary r) missingDeps
-- Dependencies OK: run the associated action.
Nothing -> do
let execCmd = ruleExecCmd scope cmds (snd <$> mbDyn)
runCmdData rId execCmd
-- Throw an error if running the action did not result in
-- the generation of outputs that we expected it to.
missingRuleResults <- filterM (missingDep mbWorkDir) $ NE.toList reslts
for_ (NE.nonEmpty missingRuleResults) $ \missingResults ->
errorOut $ MissingRuleOutputs (toRuleBinary r) missingResults
return ()
where
toRuleBinary :: RuleData userOrSystem -> RuleBinary
toRuleBinary = case scope of
SUser -> ruleBinary
SSystem -> id
clbi = targetCLBI tgtInfo
mbWorkDir = mbWorkDirLBI lbi
compAutogenDir = autogenComponentModulesDir lbi clbi
errorOut e =
dieWithException verbosity $
SetupHooksException $
RulesException e
directRuleDependencyMaybe :: Rule.Dependency -> Maybe RuleId
directRuleDependencyMaybe (RuleDependency dep) = Just $ outputOfRule dep
directRuleDependencyMaybe (FileDependency{}) = Nothing
resolveDependency :: Verbosity -> RuleId -> Map RuleId (RuleData scope) -> Rule.Dependency -> IO Location
resolveDependency verbosity rId allRules = \case
FileDependency l -> return l
RuleDependency (RuleOutput{outputOfRule = depId, outputIndex = i}) ->
case Map.lookup depId allRules of
Nothing ->
error $
unlines $
[ "Internal error: missing rule dependency."
, "Rule: " ++ show rId
, "Dependency: " ++ show depId
]
Just (Rule{results = os}) ->
let j :: Int
j = fromIntegral i
in case listToMaybe $ drop j $ NE.toList os of
Just o
| j >= 0 ->
return o
_ ->
dieWithException verbosity $
SetupHooksException $
RulesException $
InvalidRuleOutputIndex rId depId os i
-- | Does the rule output the given location?
ruleOutputsLocation :: RuleData scope -> Location -> Bool
ruleOutputsLocation (Rule{results = rs}) fp =
any (\out -> normaliseLocation out == normaliseLocation fp) rs
normaliseLocation :: Location -> Location
normaliseLocation (Location base rel) =
Location (normaliseSymbolicPath base) (normaliseSymbolicPath rel)
-- | Is the file we depend on missing?
missingDep :: Maybe (SymbolicPath CWD (Dir Pkg)) -> Location -> IO Bool
missingDep mbWorkDir loc = not <$> doesFileExist fp
where
fp = interpretSymbolicPath mbWorkDir (location loc)
--------------------------------------------------------------------------------
-- Compatibility with HookedBuildInfo.
--
-- NB: assumes that the components in HookedBuildInfo are:
-- - an (optional) main library,
-- - executables.
--
-- No support for named sublibraries, foreign libraries, tests or benchmarks,
-- because the HookedBuildInfo datatype doesn't specify what type of component
-- each component name is (so we assume they are executables).
hookedBuildInfoComponents :: HookedBuildInfo -> Set ComponentName
hookedBuildInfoComponents (mb_mainlib, exes) =
Set.fromList $
(case mb_mainlib of Nothing -> id; Just{} -> (CLibName LMainLibName :))
[CExeName exe_nm | (exe_nm, _) <- exes]
hookedBuildInfoComponentDiff_maybe :: HookedBuildInfo -> ComponentName -> Maybe (IO ComponentDiff)
hookedBuildInfoComponentDiff_maybe (mb_mainlib, exes) comp_nm =
case comp_nm of
CLibName lib_nm ->
case lib_nm of
LMainLibName -> return . ComponentDiff . CLib . buildInfoLibraryDiff <$> mb_mainlib
LSubLibName{} -> Nothing
CExeName exe_nm ->
let mb_exe = lookup exe_nm exes
in return . ComponentDiff . CExe . buildInfoExecutableDiff <$> mb_exe
CFLibName{} -> Nothing
CTestName{} -> Nothing
CBenchName{} -> Nothing
buildInfoLibraryDiff :: BuildInfo -> LibraryDiff
buildInfoLibraryDiff bi = emptyLibrary{libBuildInfo = bi}
buildInfoExecutableDiff :: BuildInfo -> ExecutableDiff
buildInfoExecutableDiff bi = emptyExecutable{buildInfo = bi}
--------------------------------------------------------------------------------
-- Instances for serialisation
deriving newtype instance Binary ComponentDiff
deriving newtype instance Structured ComponentDiff
instance Binary PreConfPackageInputs
instance Structured PreConfPackageInputs
instance Binary PreConfPackageOutputs
instance Structured PreConfPackageOutputs
instance Binary PostConfPackageInputs
instance Structured PostConfPackageInputs
instance Binary PreConfComponentInputs
instance Structured PreConfComponentInputs
instance Binary PreConfComponentOutputs
instance Structured PreConfComponentOutputs
instance Binary PreBuildComponentInputs
instance Structured PreBuildComponentInputs
instance Binary PostBuildComponentInputs
instance Structured PostBuildComponentInputs
instance Binary InstallComponentInputs
instance Structured InstallComponentInputs
--------------------------------------------------------------------------------