on master help sign in
Pick dependency analyzer from old develop branch
commit9aa11b26cdba009fe268f874c07f9037250bf2c6 authorJasper Van der Jeugt <m@jaspervdj.be> dateSat 10 Nov 2012, 20:42 (14 years ago) parent9eda342
7 files changed+297−381
hakyll.cabal+4−3 src/Hakyll/Core/DependencyAnalyzer.hs+138−150 src/Hakyll/Core/DirectedGraph.hs+35−40 src/Hakyll/Core/DirectedGraph/Dot.hs+1−1 src/Hakyll/Core/DirectedGraph/Internal.hs−52 src/Hakyll/Core/Identifier.hs+4 src/Hakyll/Core/Run.hs+115−135
hakyll.cabal+4−3
@@ -72,6 +72,7 @@ Library
7272 citeproc-hs >= 0.3.2 && < 0.4,
7373 containers >= 0.3 && < 0.6,
7474 cryptohash >= 0.7 && < 0.8,
75+ deepseq >= 1.3 && < 1.4,
7576 directory >= 1.0 && < 1.3,
7677 filepath >= 1.0 && < 1.4,
7778 hamlet >= 1.0 && < 1.2,
@@ -93,8 +94,6 @@ Library
9394 Hakyll.Core.Compiler
9495 Hakyll.Core.Configuration
9596 Hakyll.Core.DependencyAnalyzer
96− Hakyll.Core.DirectedGraph
97− Hakyll.Core.DirectedGraph.Dot
9897 Hakyll.Core.Identifier
9998 Hakyll.Core.Identifier.Pattern
10099 Hakyll.Core.Logger
@@ -130,7 +129,8 @@ Library
130129 Other-Modules:
131130 Hakyll.Core.CompiledItem
132131 Hakyll.Core.Compiler.Internal
133− Hakyll.Core.DirectedGraph.Internal
132+ Hakyll.Core.DirectedGraph
133+ Hakyll.Core.DirectedGraph.Dot
134134 Hakyll.Core.ResourceProvider.Internal
135135 Hakyll.Core.ResourceProvider.Metadata
136136 Hakyll.Core.ResourceProvider.MetadataCache
@@ -181,6 +181,7 @@ Test-suite hakyll-tests
181181 citeproc-hs >= 0.3.2 && < 0.4,
182182 containers >= 0.3 && < 0.6,
183183 cryptohash >= 0.7 && < 0.8,
184+ deepseq >= 1.3 && < 1.4,
184185 directory >= 1.0 && < 1.3,
185186 filepath >= 1.0 && < 1.4,
186187 hamlet >= 1.0 && < 1.2,
src/Hakyll/Core/DependencyAnalyzer.hs+138−150
@@ -1,156 +1,144 @@
1+--------------------------------------------------------------------------------
12module Hakyll.Core.DependencyAnalyzer
2− ( DependencyAnalyzer (..)
3− , Signal (..)
4− , makeDependencyAnalyzer
5− , step
6− , stepAll
3+ ( Analysis (..)
4+ , analyze
75 ) where
86
9−import Prelude hiding (reverse)
10−import qualified Prelude as P (reverse)
11−import Control.Arrow (first)
12−import Data.Set (Set)
13−import qualified Data.Set as S
14−import Data.Monoid (Monoid, mappend, mempty)
15−
16−import Hakyll.Core.DirectedGraph
17−
18−-- | This data structure represents the state of the dependency analyzer. It
19−-- holds a complete graph in 'analyzerGraph', which always contains all items,
20−-- whether they are to be compiled or not.
21−--
22−-- The 'analyzerRemains' fields holds the items that still need to be compiled,
23−-- and 'analyzerDone' holds the items which are already compiled. This means
24−-- that initally, 'analyzerDone' is empty and 'analyzerRemains' contains the
25−-- items which are out-of-date (or items which have out-of-date dependencies).
26−--
27−-- We also hold the dependency graph from the previous run because we need it
28−-- when we want to determine when an item is out-of-date. An item is out-of-date
29−-- when:
30−--
31−-- * the resource from which it compiles is out-of-date, or;
32−--
33−-- * any of it's dependencies is out-of-date, or;
34−--
35−-- * it's set of dependencies has changed since the previous run.
36−--
37−data DependencyAnalyzer a = DependencyAnalyzer
38− { -- | The complete dependency graph
39− analyzerGraph :: DirectedGraph a
40− , -- | A set of items yet to be compiled
41− analyzerRemains :: Set a
42− , -- | A set of items already compiled
43− analyzerDone :: Set a
44− , -- | The dependency graph from the previous run
45− analyzerPreviousGraph :: DirectedGraph a
46− } deriving (Show)
47−
48−data Signal a = Build a
49− | Cycle [a]
50− | Done
51− deriving (Show)
52−
53−instance (Ord a, Show a) => Monoid (DependencyAnalyzer a) where
54− mempty = DependencyAnalyzer mempty mempty mempty mempty
55− mappend x y = growRemains $ DependencyAnalyzer
56− (analyzerGraph x `mappend` analyzerGraph y)
57− (analyzerRemains x `mappend` analyzerRemains y)
58− (analyzerDone x `mappend` analyzerDone y)
59− (analyzerPreviousGraph x `mappend` analyzerPreviousGraph y)
60−
61−-- | Construct a dependency analyzer
62−--
63−makeDependencyAnalyzer :: (Ord a, Show a)
64− => DirectedGraph a -- ^ The dependency graph
65− -> (a -> Bool) -- ^ Is an item out-of-date?
66− -> DirectedGraph a -- ^ The old dependency graph
67− -> DependencyAnalyzer a -- ^ Resulting analyzer
68−makeDependencyAnalyzer graph isOutOfDate prev =
69− growRemains $ DependencyAnalyzer graph remains S.empty prev
70− where
71− -- Construct the remains set by filtering using the given predicate
72− remains = S.fromList $ filter isOutOfDate $ map fst $ toList graph
73−
74−-- | The 'analyzerRemains' field of a 'DependencyAnalyzer' is supposed to
75−-- contain all out-of-date items, including the items with out-of-date
76−-- dependencies. However, it is easier to just set up the directly out-of-date
77−-- items initially -- and then grow the remains fields.
78−--
79−-- This function assumes the 'analyzerRemains' fields in incomplete, and tries
80−-- to correct it. Running it when the field is complete has no effect -- but it
81−-- is a pretty expensive function, and it should be used with care.
82−--
83−growRemains :: (Ord a, Show a) => DependencyAnalyzer a -> DependencyAnalyzer a
84−growRemains (DependencyAnalyzer graph remains done prev) =
85− (DependencyAnalyzer graph remains' done prev)
7+
8+--------------------------------------------------------------------------------
9+import Control.Applicative ((<$>))
10+import Control.DeepSeq (NFData (..))
11+import Control.Monad (filterM, forM_, msum, when)
12+import Control.Monad.Reader (ask)
13+import Control.Monad.RWS (RWS, runRWS)
14+import Control.Monad.State (evalState, get, modify)
15+import Control.Monad.Writer (tell)
16+import qualified Data.Map as M
17+import Data.Set (Set)
18+import qualified Data.Set as S
19+
20+
21+--------------------------------------------------------------------------------
22+import Hakyll.Core.DirectedGraph
23+
24+
25+--------------------------------------------------------------------------------
26+data Analysis a
27+ = Cycle [a]
28+ | Order [a]
29+ deriving (Show)
30+
31+
32+--------------------------------------------------------------------------------
33+instance NFData a => NFData (Analysis a) where
34+ rnf (Cycle c) = rnf c `seq` ()
35+ rnf (Order o) = rnf o `seq` ()
36+
37+
38+--------------------------------------------------------------------------------
39+analyze :: Ord a
40+ => DirectedGraph a -- ^ Old graph
41+ -> DirectedGraph a -- ^ New graph
42+ -> (a -> Bool) -- ^ Out of date?
43+ -> Analysis a -- ^ Result
44+analyze old new ood = case findCycle new of
45+ Just c -> Cycle c
46+ Nothing -> Order $ findOrder old new ood
47+
48+
49+--------------------------------------------------------------------------------
50+-- | Simple algorithm do find a cycle in a graph, if any exists. This one can
51+-- still be optimised by a lot.
52+findCycle :: Ord a
53+ => DirectedGraph a
54+ -> Maybe [a]
55+findCycle dg = fmap reverse $ msum
56+ [ findCycle' [x] x n
57+ | x <- S.toList $ nodes dg
58+ , n <- neighbours x dg
59+ ]
8660 where
87− -- Grow the remains set using the indirect and changedDeps values, then
88− -- filter out the items already done
89− remains' = S.filter (`S.notMember` done) indirect
90−
91− -- Select the nodes which are reachable from the remaining nodes in the
92− -- reversed dependency graph: these are the indirectly out-of-date items
93− indirect = reachableNodes (remains `S.union` changedDeps) $ reverse graph
94−
95− -- For all nodes in the graph, check which items have a different dependency
96− -- set compared to the previous run
97− changedDeps = S.fromList $ map fst $
98− filter (uncurry (/=) . first (`neighbours` prev)) $ toList graph
99−
100−-- | Step a dependency analyzer
101−--
102−step :: (Ord a, Show a) => DependencyAnalyzer a -> (Signal a, DependencyAnalyzer a)
103−step analyzer@(DependencyAnalyzer graph remains done prev)
104− -- No remaining items
105− | S.null remains = (Done, analyzer)
106− -- An item remains, let's find a ready item
107− | otherwise =
108− let item = S.findMin remains
109− in case findReady analyzer item of
110− Done -> (Done, analyzer)
111− Cycle c -> (Cycle c, analyzer)
112− -- A ready item was found, signal a build
113− Build build ->
114− let remains' = S.delete build remains
115− done' = S.insert build done
116− in (Build build, DependencyAnalyzer graph remains' done' prev)
117−
118−-- | Step until done, creating a set of items we need to build -- mostly used
119−-- for debugging purposes
120−--
121−stepAll :: (Ord a, Show a) => DependencyAnalyzer a -> Maybe (Set a)
122−stepAll = stepAll' S.empty
61+ findCycle' stack start x
62+ | x == start = Just (x : stack)
63+ | otherwise = msum
64+ [ findCycle' (x : stack) start n
65+ | n <- neighbours x dg
66+ ]
67+
68+
69+--------------------------------------------------------------------------------
70+-- | Do not call this on graphs with cycles
71+findOrder :: Ord a
72+ => DirectedGraph a
73+ -> DirectedGraph a
74+ -> (a -> Bool)
75+ -> [a]
76+findOrder old new ood = ls
12377 where
124− stepAll' xs analyzer = case step analyzer of
125− (Build x, analyzer') -> stepAll' (S.insert x xs) analyzer'
126− (Done, _) -> Just xs
127− (Cycle _, _) -> Nothing
128−
129−-- | Find an item ready to be compiled
130−--
131−findReady :: (Ord a, Show a) => DependencyAnalyzer a -> a -> Signal a
132−findReady analyzer = findReady' [] S.empty
78+ -- Make an extension of ood: an item is ood when it is actually ood OR if
79+ -- the list of its dependencies has changed. Based on that, create a set of
80+ -- dirty items.
81+ ood' x = ood x || neighbours x old /= neighbours x new
82+ dirty' = dirty ood' new
83+
84+ -- Run all walks in our own little monad...
85+ (_, _, ls) = runRWS walks new dirty'
86+
87+
88+--------------------------------------------------------------------------------
89+type Analyzer i a = RWS (DirectedGraph i) [i] (Set i) a
90+
91+
92+--------------------------------------------------------------------------------
93+isDirty :: Ord a => a -> Analyzer a Bool
94+isDirty x = (x `S.member`) <$> get
95+
96+
97+--------------------------------------------------------------------------------
98+walks :: Ord a
99+ => Analyzer a ()
100+walks = do
101+ dirty' <- get
102+ if S.null dirty'
103+ then return ()
104+ else do
105+ walk $ S.findMin dirty'
106+ walks
107+
108+
109+--------------------------------------------------------------------------------
110+-- | Invariant: given node to walk /must/ be dirty
111+walk :: Ord a
112+ => a
113+ -> Analyzer a ()
114+walk x = do
115+ -- Determine dirty neighbours and walk them
116+ dg <- ask
117+ forM_ (neighbours x dg) $ \n -> do
118+ d <- isDirty n
119+ when d $ walk n
120+
121+ -- Once all dirty neighbours are done, we're safe to go
122+ tell [x]
123+ modify $ S.delete x
124+
125+
126+--------------------------------------------------------------------------------
127+-- | This auxiliary function checks which nodes are dirty: a node is dirty if
128+-- it's out-of-date or if one of its dependencies is dirty.
129+dirty :: Ord a
130+ => (a -> Bool) -- ^ Out of date?
131+ -> DirectedGraph a -- ^ Graph
132+ -> Set a -- ^ All dirty items
133+dirty ood dg = S.fromList $ flip evalState M.empty $
134+ filterM go $ S.toList $ nodes dg
133135 where
134− -- The dependency graph
135− graph = analyzerGraph analyzer
136−
137− -- Items to do
138− todo = analyzerRemains analyzer `S.difference` analyzerDone analyzer
139−
140− -- Worker
141− findReady' stack visited item
142− -- We already visited this item, the cycle is the reversed stack
143− | item `S.member` visited = Cycle $ P.reverse stack'
144− -- Look at the neighbours we to do
145− | otherwise = case filter (`S.member` todo) neighbours' of
146− -- No neighbours available to be done: it's ready!
147− [] -> Build item
148− -- At least one neighbour is available, search for that one
149− (x : _) -> findReady' stack' visited' x
150− where
151− -- Our neighbours
152− neighbours' = S.toList $ neighbours item graph
153−
154− -- The new visited stack/set
155− stack' = item : stack
156− visited' = S.insert item visited
136+ go x = do
137+ m <- get
138+ case M.lookup x m of
139+ Just d -> return d
140+ Nothing -> do
141+ nd <- mapM go $ neighbours x dg
142+ let d = ood x || or nd
143+ modify $ M.insert x d
144+ return d
src/Hakyll/Core/DirectedGraph.hs+35−40
@@ -1,45 +1,61 @@
11--------------------------------------------------------------------------------
22-- | Representation of a directed graph. In Hakyll, this is used for dependency
33-- tracking.
4+{-# LANGUAGE DeriveDataTypeable #-}
5+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
46module Hakyll.Core.DirectedGraph
57 ( DirectedGraph
8+
69 , fromList
710 , toList
11+
812 , member
913 , nodes
1014 , neighbours
11− , reverse
12− , reachableNodes
1315 ) where
1416
1517
1618--------------------------------------------------------------------------------
17−import Prelude hiding (reverse)
18−import Control.Arrow (second)
19−import Data.Monoid (mconcat)
20−import Data.Set (Set)
21−import Data.Maybe (fromMaybe)
22−import qualified Data.Map as M
23−import qualified Data.Set as S
19+import Control.Arrow (second)
20+import Control.DeepSeq (NFData)
21+import Data.Binary (Binary)
22+import Data.Map (Map)
23+import qualified Data.Map as M
24+import Data.Maybe (fromMaybe)
25+import Data.Monoid (Monoid (..))
26+import Data.Set (Set)
27+import qualified Data.Set as S
28+import Data.Typeable (Typeable)
29+import Prelude hiding (reverse)
30+
31+
32+--------------------------------------------------------------------------------
33+-- | Type used to represent a directed graph
34+newtype DirectedGraph a = DirectedGraph {unDirectedGraph :: Map a [a]}
35+ deriving (Show, Binary, NFData, Typeable)
2436
2537
2638--------------------------------------------------------------------------------
27−import Hakyll.Core.DirectedGraph.Internal
39+-- | Allow users to concatenate different graphs
40+instance Ord a => Monoid (DirectedGraph a) where
41+ mempty = DirectedGraph M.empty
42+ mappend (DirectedGraph m1) (DirectedGraph m2) = DirectedGraph $
43+ M.unionWith (\x y -> sortUnique (x ++ y)) m1 m2
2844
2945
3046--------------------------------------------------------------------------------
3147-- | Construction of directed graphs
3248fromList :: Ord a
33− => [(a, Set a)] -- ^ List of (node, reachable neighbours)
49+ => [(a, [a])] -- ^ List of (node, reachable neighbours)
3450 -> DirectedGraph a -- ^ Resulting directed graph
35−fromList = DirectedGraph . M.fromList . map (\(t, d) -> (t, Node t d))
51+fromList = DirectedGraph . M.fromList . map (second sortUnique)
3652
3753
3854--------------------------------------------------------------------------------
3955-- | Deconstruction of directed graphs
4056toList :: DirectedGraph a
41− -> [(a, Set a)]
42−toList = map (second nodeNeighbours) . M.toList . unDirectedGraph
57+ -> [(a, [a])]
58+toList = M.toList . unDirectedGraph
4359
4460
4561--------------------------------------------------------------------------------
@@ -64,32 +80,11 @@ nodes = M.keysSet . unDirectedGraph
6480neighbours :: Ord a
6581 => a -- ^ Node to get the neighbours of
6682 -> DirectedGraph a -- ^ Graph to search in
67− -> Set a -- ^ Set containing the neighbours
68−neighbours x = fromMaybe S.empty . fmap nodeNeighbours .
69− M.lookup x . unDirectedGraph
70−
71−
72−--------------------------------------------------------------------------------
73−-- | Reverse a directed graph (i.e. flip all edges)
74−reverse :: Ord a
75− => DirectedGraph a
76− -> DirectedGraph a
77−reverse = mconcat . map reverse' . M.toList . unDirectedGraph
78− where
79− reverse' (id', Node _ neighbours') = fromList $
80− zip (S.toList neighbours') $ repeat $ S.singleton id'
83+ -> [a] -- ^ Set containing the neighbours
84+neighbours x dg = fromMaybe [] $ M.lookup x $ unDirectedGraph dg
8185
8286
8387--------------------------------------------------------------------------------
84−-- | Find all reachable nodes from a given set of nodes in the directed graph
85−reachableNodes :: Ord a => Set a -> DirectedGraph a -> Set a
86−reachableNodes set graph = reachable (setNeighbours set) set
87− where
88− reachable next visited
89− | S.null next = visited
90− | otherwise = reachable (sanitize neighbours') (next `S.union` visited)
91− where
92− sanitize = S.filter (`S.notMember` visited)
93− neighbours' = setNeighbours (sanitize next)
94−
95− setNeighbours = S.unions . map (`neighbours` graph) . S.toList
88+-- | Sort and make unique
89+sortUnique :: Ord a => [a] -> [a]
90+sortUnique = S.toAscList . S.fromList
src/Hakyll/Core/DirectedGraph/Dot.hs+1−1
@@ -25,7 +25,7 @@ toDot showTag graph = unlines $ concat
2525 ]
2626 where
2727 showNode node = " \"" ++ showTag node ++ "\";"
28− showEdges node = map (showEdge node) $ S.toList $ neighbours node graph
28+ showEdges node = map (showEdge node) $ neighbours node graph
2929 showEdge x y = " \"" ++ showTag x ++ "\" -> \"" ++ showTag y ++ "\";"
3030
3131
src/Hakyll/Core/DirectedGraph/Internal.hsdeleted−52
@@ -1,52 +0,0 @@
1−-- | Internal structure of the DirectedGraph type. Not exported outside of the
2−-- library.
3−--
4−{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveDataTypeable #-}
5−module Hakyll.Core.DirectedGraph.Internal
6− ( Node (..)
7− , DirectedGraph (..)
8− ) where
9−
10−import Prelude hiding (reverse, filter)
11−import Control.Applicative ((<$>), (<*>))
12−import Data.Monoid (Monoid, mempty, mappend)
13−import Data.Set (Set)
14−import Data.Map (Map)
15−import qualified Data.Map as M
16−import qualified Data.Set as S
17−
18−import Data.Binary (Binary, put, get)
19−import Data.Typeable (Typeable)
20−
21−-- | A node in the directed graph
22−--
23−data Node a = Node
24− { nodeTag :: a -- ^ Tag identifying the node
25− , nodeNeighbours :: Set a -- ^ Edges starting at this node
26− } deriving (Show, Typeable)
27−
28−instance (Binary a, Ord a) => Binary (Node a) where
29− put (Node t n) = put t >> put n
30− get = Node <$> get <*> get
31−
32−-- | Append two nodes. Useful for joining graphs.
33−--
34−appendNodes :: Ord a => Node a -> Node a -> Node a
35−appendNodes (Node t1 n1) (Node t2 n2)
36− | t1 /= t2 = error'
37− | otherwise = Node t1 (n1 `S.union` n2)
38− where
39− error' = error $ "Hakyll.Core.DirectedGraph.Internal.appendNodes: "
40− ++ "Appending differently tagged nodes"
41−
42−-- | Type used to represent a directed graph
43−--
44−newtype DirectedGraph a = DirectedGraph {unDirectedGraph :: Map a (Node a)}
45− deriving (Show, Binary, Typeable)
46−
47−-- | Allow users to concatenate different graphs
48−--
49−instance Ord a => Monoid (DirectedGraph a) where
50− mempty = DirectedGraph M.empty
51− mappend (DirectedGraph m1) (DirectedGraph m2) = DirectedGraph $
52− M.unionWith appendNodes m1 m2
src/Hakyll/Core/Identifier.hs+4
@@ -40,6 +40,7 @@ module Hakyll.Core.Identifier
4040 ) where
4141
4242import Control.Applicative ((<$>), (<*>))
43+import Control.DeepSeq (NFData (..))
4344import Control.Monad (mplus)
4445import Data.Monoid (Monoid, mempty, mappend)
4546import Data.List (intercalate)
@@ -72,6 +73,9 @@ instance Show (Identifier a) where
7273instance IsString (Identifier a) where
7374 fromString = parseIdentifier
7475
76+instance NFData (Identifier a) where
77+ rnf (Identifier g p) = rnf g `seq` rnf p `seq` ()
78+
7579-- | Discard the phantom type parameter of an identifier
7680--
7781castIdentifier :: Identifier a -> Identifier b
src/Hakyll/Core/Run.hs+115−135
@@ -1,41 +1,47 @@
1+--------------------------------------------------------------------------------
12-- | This is the module which binds it all together
2−--
3−{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE OverloadedStrings #-}
45module Hakyll.Core.Run
56 ( run
67 ) where
78
8−import Control.Applicative (Applicative, (<$>))
9−import Control.Monad (filterM, forM_)
10−import Control.Monad.Error (ErrorT, runErrorT, throwError)
11−import Control.Monad.Reader (ReaderT, runReaderT, ask)
12−import Control.Monad.State.Strict (StateT, runStateT, get, put)
13−import Control.Monad.Trans (liftIO)
14−import Data.Map (Map)
15−import Data.Monoid (mempty, mappend)
16−import Prelude hiding (reverse)
17−import System.FilePath ((</>))
18−import qualified Data.Map as M
19−import qualified Data.Set as S
20−
21−import Hakyll.Core.CompiledItem
22−import Hakyll.Core.Compiler
23−import Hakyll.Core.Compiler.Internal
24−import Hakyll.Core.Configuration
25−import Hakyll.Core.DependencyAnalyzer
26−import Hakyll.Core.DirectedGraph
27−import Hakyll.Core.Identifier
28−import Hakyll.Core.Logger
29−import Hakyll.Core.ResourceProvider
30−import Hakyll.Core.Routes
31−import Hakyll.Core.Rules.Internal
32−import Hakyll.Core.Store (Store)
33−import Hakyll.Core.Util.File
34−import Hakyll.Core.Writable
35−import qualified Hakyll.Core.Store as Store
369
10+--------------------------------------------------------------------------------
11+import Control.Applicative (Applicative, (<$>))
12+import Control.DeepSeq (deepseq)
13+import Control.Monad (filterM, forM_)
14+import Control.Monad.Error (ErrorT, runErrorT, throwError)
15+import Control.Monad.Reader (ReaderT, ask, runReaderT)
16+import Control.Monad.Trans (liftIO)
17+import Data.Map (Map)
18+import qualified Data.Map as M
19+import Data.Monoid (mempty)
20+import qualified Data.Set as S
21+import Prelude hiding (reverse)
22+import System.FilePath ((</>))
23+
24+
25+--------------------------------------------------------------------------------
26+import Hakyll.Core.CompiledItem
27+import Hakyll.Core.Compiler
28+import Hakyll.Core.Compiler.Internal
29+import Hakyll.Core.Configuration
30+import Hakyll.Core.DependencyAnalyzer
31+import qualified Hakyll.Core.DirectedGraph as DG
32+import Hakyll.Core.Identifier
33+import Hakyll.Core.Logger
34+import Hakyll.Core.ResourceProvider
35+import Hakyll.Core.Routes
36+import Hakyll.Core.Rules.Internal
37+import Hakyll.Core.Store (Store)
38+import qualified Hakyll.Core.Store as Store
39+import Hakyll.Core.Util.File
40+import Hakyll.Core.Writable
41+
42+
43+--------------------------------------------------------------------------------
3744-- | Run all rules needed, return the rule set used
38−--
3945run :: HakyllConfiguration -> RulesM a -> IO RuleSet
4046run configuration rules = do
4147 logger <- makeLogger putStrLn
@@ -46,136 +52,113 @@ run configuration rules = do
4652 provider <- timed logger "Creating provider" $ newResourceProvider
4753 store (ignoreFile configuration) "."
4854
49− -- Fetch the old graph from the store. If we don't find it, we consider this
50− -- to be the first run
51− graph <- Store.get store ["Hakyll.Core.Run.run", "dependencies"]
52− let (firstRun, oldGraph) = case graph of Store.Found g -> (False, g)
53− _ -> (True, mempty)
54−
5555 ruleSet <- timed logger "Running rules" $ runRules rules provider
5656 let compilers = rulesCompilers ruleSet
5757
5858 -- Extract the reader/state
59− reader = unRuntime $ addNewCompilers compilers
60− stateT = runReaderT reader $ RuntimeEnvironment
61− { hakyllLogger = logger
62− , hakyllConfiguration = configuration
63− , hakyllRoutes = rulesRoutes ruleSet
64− , hakyllResourceProvider = provider
65− , hakyllStore = store
66− , hakyllFirstRun = firstRun
59+ reader = unRuntime analyzeAndBuild
60+ errorT = runReaderT reader $ RuntimeEnvironment
61+ { runtimeLogger = logger
62+ , runtimeConfiguration = configuration
63+ , runtimeRoutes = rulesRoutes ruleSet
64+ , runtimeProvider = provider
65+ , runtimeStore = store
66+ , runtimeCompilers = M.fromList compilers
6767 }
6868
6969 -- Run the program and fetch the resulting state
70− result <- runErrorT $ runStateT stateT $ RuntimeState
71− { hakyllAnalyzer = makeDependencyAnalyzer mempty (const False) oldGraph
72− , hakyllCompilers = M.empty
73− }
74−
70+ result <- runErrorT errorT
7571 case result of
76− Left e ->
77− thrown logger e
78− Right ((), state') ->
79− -- We want to save the final dependency graph for the next run
80− Store.set store ["Hakyll.Core.Run.run", "dependencies"] $
81− analyzerGraph $ hakyllAnalyzer state'
72+ Left e -> thrown logger e
73+ _ -> return ()
8274
8375 -- Flush and return
8476 flushLogger logger
8577 return ruleSet
8678
79+
80+--------------------------------------------------------------------------------
8781data RuntimeEnvironment = RuntimeEnvironment
88− { hakyllLogger :: Logger
89− , hakyllConfiguration :: HakyllConfiguration
90− , hakyllRoutes :: Routes
91− , hakyllResourceProvider :: ResourceProvider
92− , hakyllStore :: Store
93− , hakyllFirstRun :: Bool
82+ { runtimeLogger :: Logger
83+ , runtimeConfiguration :: HakyllConfiguration
84+ , runtimeRoutes :: Routes
85+ , runtimeProvider :: ResourceProvider
86+ , runtimeStore :: Store
87+ , runtimeCompilers :: Map (Identifier ()) (Compiler () CompiledItem)
9488 }
9589
96−data RuntimeState = RuntimeState
97− { hakyllAnalyzer :: DependencyAnalyzer (Identifier ())
98− , hakyllCompilers :: Map (Identifier ()) (Compiler () CompiledItem)
99− }
10090
91+--------------------------------------------------------------------------------
10192newtype Runtime a = Runtime
102− { unRuntime :: ReaderT RuntimeEnvironment
103− (StateT RuntimeState (ErrorT String IO)) a
93+ { unRuntime :: ReaderT RuntimeEnvironment (ErrorT String IO) a
10494 } deriving (Functor, Applicative, Monad)
10595
106−-- | Add a number of compilers and continue using these compilers
107−--
108−addNewCompilers :: [(Identifier (), Compiler () CompiledItem)]
109− -- ^ Compilers to add
110− -> Runtime ()
111−addNewCompilers newCompilers = Runtime $ do
112− -- Get some information
113− logger <- hakyllLogger <$> ask
114− section logger "Adding new compilers"
115− provider <- hakyllResourceProvider <$> ask
116− firstRun <- hakyllFirstRun <$> ask
117−
118− -- Old state information
119− oldCompilers <- hakyllCompilers <$> get
120− oldAnalyzer <- hakyllAnalyzer <$> get
121−
122− let -- All known compilers
123− universe = M.keys oldCompilers ++ map fst newCompilers
124−
125− -- Create a new partial dependency graph
126− dependencies = flip map newCompilers $ \(id', compiler) ->
127− let deps = runCompilerDependencies compiler id' universe
128− in (id', deps)
129−
130− -- Create the dependency graph
131− newGraph = fromList dependencies
132−
133− -- Check which items have been modified
134− modified <- fmap S.fromList $ flip filterM (map fst newCompilers) $
135− liftIO . resourceModified provider
136− let checkModified = if firstRun then const True else (`S.member` modified)
137−
138− -- Create a new analyzer and append it to the currect one
139− let newAnalyzer = makeDependencyAnalyzer newGraph checkModified $
140− analyzerPreviousGraph oldAnalyzer
141− analyzer = mappend oldAnalyzer newAnalyzer
142−
143− -- Update the state
144− put $ RuntimeState
145− { hakyllAnalyzer = analyzer
146− , hakyllCompilers = M.union oldCompilers (M.fromList newCompilers)
147− }
148−
149− -- Continue
150− unRuntime stepAnalyzer
151−
152−stepAnalyzer :: Runtime ()
153−stepAnalyzer = Runtime $ do
154− -- Step the analyzer
155− state <- get
156− let (signal, analyzer') = step $ hakyllAnalyzer state
157− put $ state { hakyllAnalyzer = analyzer' }
158−
159− case signal of Done -> return ()
160− Cycle c -> unRuntime $ dumpCycle c
161− Build id' -> unRuntime $ build id'
16296
97+--------------------------------------------------------------------------------
98+analyzeAndBuild :: Runtime ()
99+analyzeAndBuild = Runtime $ do
100+ -- Get some stuff
101+ logger <- runtimeLogger <$> ask
102+ provider <- runtimeProvider <$> ask
103+ store <- runtimeStore <$> ask
104+ compilers <- runtimeCompilers <$> ask
105+
106+ -- Checking which items have been modified
107+ let universe = M.keys compilers
108+ modified <- timed logger "Checking for modified items" $
109+ fmap S.fromList $ flip filterM universe $
110+ liftIO . resourceModified provider
111+
112+ -- Fetch the old graph from the store. If we don't find it, we consider this
113+ -- to be the first run
114+ mOldGraph <- liftIO $ Store.get store graphKey
115+ let (firstRun, oldGraph) = case mOldGraph of Store.Found g -> (False, g)
116+ _ -> (True, mempty)
117+
118+ -- Create a new dependency graph
119+ graph = DG.fromList $
120+ flip map (M.toList compilers) $ \(id', compiler) ->
121+ let deps = runCompilerDependencies compiler id' universe
122+ in (id', S.toList deps)
123+
124+ ood | firstRun = const True
125+ | otherwise = (`S.member` modified)
126+
127+ -- Check for cycles and analyze the graph
128+ analysis = analyze oldGraph graph ood
129+
130+ -- Make sure this stuff is evaluated
131+ () <- timed logger "Analyzing dependency graph" $
132+ oldGraph `deepseq` analysis `deepseq` return ()
133+
134+ -- We want to save the new dependency graph for the next run
135+ liftIO $ Store.set store graphKey graph
136+
137+ case analysis of
138+ Cycle c -> unRuntime $ dumpCycle c
139+ Order o -> mapM_ (unRuntime . build) o
140+ where
141+ graphKey = ["Hakyll.Core.Run.run", "dependencies"]
142+
143+
144+--------------------------------------------------------------------------------
163145-- | Dump cyclic error and quit
164−--
165146dumpCycle :: [Identifier ()] -> Runtime ()
166147dumpCycle cycle' = Runtime $ do
167− logger <- hakyllLogger <$> ask
148+ logger <- runtimeLogger <$> ask
168149 section logger "Dependency cycle detected! Conflict:"
169150 forM_ (zip cycle' $ drop 1 cycle') $ \(x, y) ->
170151 report logger $ show x ++ " -> " ++ show y
171152
153+
154+--------------------------------------------------------------------------------
172155build :: Identifier () -> Runtime ()
173156build id' = Runtime $ do
174− logger <- hakyllLogger <$> ask
175− routes <- hakyllRoutes <$> ask
176− provider <- hakyllResourceProvider <$> ask
177− store <- hakyllStore <$> ask
178− compilers <- hakyllCompilers <$> get
157+ logger <- runtimeLogger <$> ask
158+ routes <- runtimeRoutes <$> ask
159+ provider <- runtimeProvider <$> ask
160+ store <- runtimeStore <$> ask
161+ compilers <- runtimeCompilers <$> ask
179162
180163 section logger $ "Compiling " ++ show id'
181164
@@ -197,13 +180,10 @@ build id' = Runtime $ do
197180 Nothing -> return ()
198181 Just url -> timed logger ("Routing to " ++ url) $ do
199182 destination <-
200− destinationDirectory . hakyllConfiguration <$> ask
183+ destinationDirectory . runtimeConfiguration <$> ask
201184 let path = destination </> url
202185 liftIO $ makeDirectories path
203186 liftIO $ write path compiled
204187
205− -- Continue for the remaining compilers
206− unRuntime stepAnalyzer
207−
208188 -- Some error happened, rethrow in Runtime monad
209189 Left err -> throwError err