Skip to content

Commit 3b46b10

Browse files
committed
Pre-build rules can generate autogen extra sources
With this commit, the SetupHooks API now considers all extra source files placed in the autogen directory for the component to be additional demands on pre-build rules. For example, a pre-build rule that generates a C or JavaScript source file (putting it in the appropriate autogen module for the component) will now be run, while it used to not be run before (as it used to be considered "not demanded").
1 parent 67690f2 commit 3b46b10

15 files changed

Lines changed: 466 additions & 30 deletions

File tree

Cabal-hooks/src/Distribution/Simple/SetupHooks.hs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ module Distribution.Simple.SetupHooks
7373
, staticRule, dynamicRule
7474
-- *** Rule inputs/outputs
7575

76-
-- $rulesDemand
76+
-- $rulesDeps
7777
, Location(..)
7878
, location
7979
, autogenComponentModulesDir
@@ -317,7 +317,7 @@ Each t'Rule' consists of:
317317
Rules are constructed using either one of the 'staticRule' or 'dynamicRule'
318318
smart constructors. Directly constructing a t'Rule' using the constructors of
319319
that data type is not advised, as this relies on internal implementation details
320-
which are subject to change in between versions of the `Cabal-hooks` library.
320+
which are subject to change in between versions of the "Cabal-hooks" library.
321321
322322
Note that:
323323
@@ -335,7 +335,7 @@ Note that:
335335
when to re-compute the entire set of rules.
336336
-}
337337

338-
{- $rulesDemand
338+
{- $rulesDeps
339339
Rules can declare various kinds of dependencies:
340340
341341
- 'staticDependencies': files or other rules that a rule statically depends on,
@@ -373,6 +373,26 @@ to behave as follows:
373373
1. Any time the rules are out-of-date, query the rules to obtain
374374
up-to-date rules.
375375
2. Re-run stale rules.
376+
377+
Cabal will execute all **demanded** rules in dependency order. A rule is
378+
demanded if it satisfies one of the following conditions:
379+
380+
1. It is a dependency of another demanded rule.
381+
2. The rule generates a Haskell file declared in the autogen-modules field.
382+
In this case, the rule **must** place the generated source file in the
383+
'autogenComponentModulesDir' appropriate for the component.
384+
3. (Since Cabal 3.18 only) The rule generates a non-Haskell source file, such
385+
as a C or JavaScript source. In this case (because there is no
386+
"autogen-c-sources" field), the following steps must be taken:
387+
a. Add the file to the 'c-sources' (or 'js-sources', etc) field of the
388+
package description in a per-component pre-configure hook, declaring it
389+
in the same 'autogenComponentModulesDir' directory (as if it was a @.hs@ file).
390+
b. Add a pre-build rule that generates the source file and puts it in
391+
this same 'autogenComponentModulesDir' directory.
392+
Note that any file declared in the 'includes'/'autogen-includes' fields
393+
must be present at **configure** time, so cannot be generated in a
394+
pre-build rule. In that case, either use a pre-configure hook or don't
395+
declare it under the 'includes' field (if possible).
376396
-}
377397

378398
{- $rulesAPI

Cabal-syntax/src/Distribution/Utils/Path.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ module Distribution.Utils.Path
5959
, dropExtensionsSymbolicPath
6060
, replaceExtensionSymbolicPath
6161
, normaliseSymbolicPath
62+
, relativePathMaybe
6263

6364
-- ** Working directory handling
6465
, interpretSymbolicPathCWD
@@ -90,6 +91,9 @@ import qualified System.FilePath as FilePath
9091
import Data.Kind
9192
( Type
9293
)
94+
import Data.List
95+
( stripPrefix
96+
)
9397
import GHC.Stack
9498
( HasCallStack
9599
)
@@ -338,6 +342,22 @@ interpretSymbolicPathAbsolute (AbsolutePath p) sym = interpretSymbolicPath (Just
338342
coerceSymbolicPath :: SymbolicPathX allowAbsolute from to1 -> SymbolicPathX allowAbsolute from to2
339343
coerceSymbolicPath = coerce
340344

345+
-- | Does the second argument point to a sub-directory of the first one?
346+
-- If so, return the relative portion of the path, relative to the first argument.
347+
relativePathMaybe :: SymbolicPath from (Dir dir) -> SymbolicPath from to -> Maybe (RelativePath dir to)
348+
relativePathMaybe base fp =
349+
let dirPieces =
350+
FilePath.splitDirectories $
351+
FilePath.dropTrailingPathSeparator $
352+
FilePath.normalise $
353+
getSymbolicPath base
354+
pathPieces =
355+
FilePath.splitDirectories $
356+
FilePath.normalise $
357+
getSymbolicPath fp
358+
in unsafeMakeSymbolicPath . FilePath.joinPath
359+
<$> stripPrefix dirPieces pathPieces
360+
341361
-- | Change both what a symbolic path is pointing from and pointing to.
342362
--
343363
-- Avoid using this in new code.

Cabal/src/Distribution/Simple/SetupHooks/Internal.hs

Lines changed: 121 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
{-# LANGUAGE LambdaCase #-}
88
{-# LANGUAGE ScopedTypeVariables #-}
99
{-# LANGUAGE StandaloneDeriving #-}
10+
{-# LANGUAGE TupleSections #-}
1011
{-# LANGUAGE TypeApplications #-}
1112

1213
-- |
@@ -88,6 +89,7 @@ import Distribution.Compat.Prelude
8889
import Prelude ()
8990

9091
import Distribution.Compat.Lens ((.~))
92+
import Distribution.ModuleName (ModuleName)
9193
import Distribution.PackageDescription
9294
import Distribution.Simple.BuildPaths
9395
import Distribution.Simple.Compiler (Compiler (..))
@@ -123,6 +125,7 @@ import qualified Data.Map as Map
123125
import qualified Data.Set as Set
124126

125127
import System.Directory (doesFileExist)
128+
import qualified System.FilePath as FilePath
126129

127130
--------------------------------------------------------------------------------
128131
-- SetupHooks
@@ -890,20 +893,64 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a
890893
, map (fmap ruleFromVertex) (v : vs)
891894
)
892895

893-
-- Compute demanded rules.
896+
-- Compute demanded rules: anything reachable from the roots, which are:
894897
--
895-
-- SetupHooks TODO: maybe requiring all generated modules to appear
896-
-- in autogen-modules is excessive; we can look through all modules instead.
898+
-- - autogen modules
899+
-- - extra-c-sources, extra-asm-sources, ...
900+
-- (there is no 'autogen' field for those, at least not yet)
901+
--
902+
-- This does not include autogen-includes, because .h files are required
903+
-- during configure time, so not relevant for pre-build rules which are run
904+
-- after configure.
905+
autogenModPaths :: [RelativePath Source File]
897906
autogenModPaths =
898907
map (\m -> moduleNameSymbolicPath m <.> "hs") $
899-
autogenModules $
900-
componentBuildInfo $
901-
targetComponent tgtInfo
902-
leafRule_maybe (rId, r) =
903-
if any ((r `ruleOutputsLocation`) . (Location compAutogenDir)) autogenModPaths
904-
then vertexFromRuleId rId
905-
else Nothing
906-
leafRules = mapMaybe leafRule_maybe $ Map.toList allRules
908+
autogenModules compBuildInfo
909+
autogenExtraSourcesPaths :: [RelativePath Source File]
910+
autogenExtraSourcesPaths =
911+
concatMap (mapMaybe relativeToAutogen) $
912+
[ cSources compBuildInfo
913+
, cxxSources compBuildInfo
914+
, cmmSources compBuildInfo
915+
, asmSources compBuildInfo
916+
, jsSources compBuildInfo
917+
]
918+
leafRule_maybe
919+
:: (RuleId, RuleData scope)
920+
-> Either (NotDemandedRuleReasons scope) Graph.Vertex
921+
leafRule_maybe (rId, r)
922+
| any (any $ (r `ruleOutputsLocation`) . Location compAutogenDir) $
923+
[ autogenModPaths
924+
, autogenExtraSourcesPaths
925+
] =
926+
case vertexFromRuleId rId of
927+
Just v -> Right v
928+
Nothing ->
929+
error $
930+
unlines
931+
[ "internal error: no graph vertex for rule " ++ show rId
932+
, "Rule: " ++ show rId
933+
]
934+
| otherwise =
935+
Left $
936+
NDRR
937+
{ nonDemandedRules = Map.singleton rId r
938+
, nonAutogenHaskellModules =
939+
Map.singleton
940+
rId
941+
[ fromString $ intercalate "." $ FilePath.splitDirectories hsPath
942+
| Location _ outPath <- NE.toList (results r)
943+
, (hsPath, ".hs") <- [FilePath.splitExtension (getSymbolicPath outPath)]
944+
]
945+
, filesNotInAutogenFolders =
946+
Map.singleton
947+
rId
948+
[ unsafeCoerceSymbolicPath fp
949+
| Location base fp <- NE.toList (results r)
950+
, Nothing <- [relativeToAutogen base]
951+
]
952+
}
953+
(nonDmdReasons, leafRules) = partitionEithers $ map leafRule_maybe $ Map.toList allRules
907954
demandedRuleVerts = Set.fromList $ concatMap (Graph.reachable ruleGraph) leafRules
908955
nonDemandedRuleVerts = Set.fromList (Graph.vertices ruleGraph) Set.\\ demandedRuleVerts
909956

@@ -922,22 +969,7 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a
922969
-- Emit a warning if there are non-demanded rules.
923970
unless (null nonDemandedRuleVerts) $
924971
warn verbosity $
925-
unlines $
926-
"The following rules are not demanded and will not be run:"
927-
: concat
928-
[ [ " - " ++ show rId ++ ","
929-
, " generating " ++ show (NE.toList $ results r)
930-
]
931-
| v <- Set.toList nonDemandedRuleVerts
932-
, let (r, rId, _) = ruleFromVertex v
933-
]
934-
++ [ "Possible reasons for this error:"
935-
, " - Some autogenerated modules were not declared"
936-
, " (in the package description or in the pre-configure hooks)"
937-
, " - The output location for an autogenerated module is incorrect,"
938-
, " (e.g. the file extension is incorrect, or"
939-
, " it is not in the appropriate 'autogenComponentModules' directory)"
940-
]
972+
pprNotDemandedRuleReasons comp compAutogenDir (mconcat nonDmdReasons)
941973

942974
-- Run all the demanded rules, in dependency order.
943975
for_ sccs $ \(Graph.Node ruleVertex _) ->
@@ -977,12 +1009,74 @@ executeRulesUserOrSystem scope runDepsCmdData runCmdData verbosity lbi tgtInfo a
9771009
SSystem -> id
9781010
clbi = targetCLBI tgtInfo
9791011
mbWorkDir = mbWorkDirLBI lbi
1012+
comp = targetComponent tgtInfo
9801013
compAutogenDir = autogenComponentModulesDir lbi clbi
1014+
compBuildInfo = componentBuildInfo comp
9811015
errorOut e =
9821016
dieWithException verbosity $
9831017
SetupHooksException $
9841018
RulesException e
9851019

1020+
relativeToAutogen :: SymbolicPath Pkg to -> Maybe (RelativePath Source to)
1021+
relativeToAutogen = relativePathMaybe compAutogenDir
1022+
1023+
data NotDemandedRuleReasons scope = NDRR
1024+
{ nonDemandedRules :: Map RuleId (RuleData scope)
1025+
, nonAutogenHaskellModules :: Map RuleId [ModuleName]
1026+
, filesNotInAutogenFolders :: Map RuleId [RelativePath Pkg File]
1027+
}
1028+
instance Semigroup (NotDemandedRuleReasons scope) where
1029+
NDRR r1 m1 f1 <> NDRR r2 m2 f2 = NDRR (r1 <> r2) (m1 <> m2) (f1 <> f2)
1030+
instance Monoid (NotDemandedRuleReasons scope) where
1031+
mempty = NDRR mempty mempty mempty
1032+
1033+
pprNotDemandedRuleReasons
1034+
:: Component
1035+
-> SymbolicPath Pkg (Dir Source)
1036+
-> NotDemandedRuleReasons scope
1037+
-> String
1038+
pprNotDemandedRuleReasons
1039+
comp
1040+
compAutogenDir
1041+
(NDRR non_dmd_verts mods_map miss_files_map) =
1042+
unlines $ header ++ mods_lines ++ files_lines
1043+
where
1044+
mods = aux mods_map
1045+
miss_files = aux miss_files_map
1046+
1047+
aux xs = concatMap (\(rId, x) -> map (rId,) x) $ Map.toList xs
1048+
ppr (rId, x) = " - " ++ show x ++ " (for rule " ++ show rId ++ ")"
1049+
1050+
header :: [String]
1051+
header =
1052+
"The following rules are not demanded and will not be run:"
1053+
: concat
1054+
[ [ " - " ++ show rId ++ ","
1055+
, " generating " ++ show (NE.toList $ results r)
1056+
]
1057+
| (rId, r) <- Map.toList non_dmd_verts
1058+
]
1059+
1060+
mods_lines, files_lines :: [String]
1061+
mods_lines
1062+
| null mods =
1063+
[]
1064+
| otherwise =
1065+
("Perhaps add the following to the 'autogen-modules' field of '" ++ show comp ++ "'.")
1066+
: map ppr mods
1067+
files_lines
1068+
| null miss_files =
1069+
[]
1070+
| otherwise =
1071+
("The following autogenerated file" ++ s ++ " for " ++ show comp ++ " " ++ isOrAre ++ " misplaced.")
1072+
: (itOrThey ++ " should go in " ++ show compAutogenDir ++ "'.")
1073+
: map ppr miss_files
1074+
where
1075+
(s, isOrAre, itOrThey) =
1076+
case miss_files of
1077+
[_] -> ("", "is", "It")
1078+
_ -> ("s", "are", "They")
1079+
9861080
directRuleDependencyMaybe :: Rule.Dependency -> Maybe RuleId
9871081
directRuleDependencyMaybe (RuleDependency dep) = Just $ outputOfRule dep
9881082
directRuleDependencyMaybe (FileDependency{}) = Nothing
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{-# LANGUAGE ForeignFunctionInterface, CApiFFI #-}
2+
3+
module A ( bobble, isNeeded ) where
4+
5+
import Foreign.C.Types ( CInt(..) )
6+
7+
-- B is autogenerated
8+
import B ( foo, isNeeded )
9+
10+
bar x = 2 + foo x * 3
11+
12+
foreign export ccall bar :: CInt -> CInt
13+
14+
wobble x = gen_quux x
15+
16+
foreign import capi "Gen.h gen_quux" gen_quux :: CInt -> CInt
17+
foreign import capi "Gen.h gen_nozzle" gen_nozzle :: CInt -> CInt
18+
19+
foreign import capi "Top.h wyzzy" wyzzy :: CInt -> CInt
20+
21+
bobble = wyzzy 0
22+
23+
foreign export ccall wobble :: CInt -> CInt
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3+
int xyzzy(int x) {
4+
return (x - 99);
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
int xyzzy(int);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{-# LANGUAGE ForeignFunctionInterface #-}
2+
3+
module Main where
4+
5+
import Foreign.C.Types (CInt(..))
6+
7+
import A (bobble, isNeeded)
8+
9+
foreign import ccall razzle :: CInt -> CInt
10+
11+
main = do
12+
print bobble
13+
print $ razzle 3
14+
print $ isNeeded 77
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module Main where
2+
3+
import Distribution.Simple ( defaultMainWithSetupHooks )
4+
import SetupHooks ( setupHooks )
5+
6+
main :: IO ()
7+
main = defaultMainWithSetupHooks setupHooks

0 commit comments

Comments
 (0)