2012-11-13 14:10:01 +00:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
module Hakyll.Core.Runtime
|
|
|
|
( run
|
|
|
|
) where
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Control.Applicative ((<$>))
|
|
|
|
import Control.Monad (filterM)
|
|
|
|
import Control.Monad.Error (ErrorT, runErrorT, throwError)
|
|
|
|
import Control.Monad.Reader (ask)
|
|
|
|
import Control.Monad.RWS (RWST, runRWST)
|
|
|
|
import Control.Monad.State (get, modify)
|
|
|
|
import Control.Monad.Trans (liftIO)
|
|
|
|
import Data.Map (Map)
|
|
|
|
import qualified Data.Map as M
|
|
|
|
import Data.Monoid (mempty)
|
|
|
|
import Data.Set (Set)
|
|
|
|
import qualified Data.Set as S
|
2012-11-15 09:24:46 +00:00
|
|
|
import System.Exit (ExitCode (..), exitWith)
|
2012-11-13 14:10:01 +00:00
|
|
|
import System.FilePath ((</>))
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Hakyll.Core.Compiler.Internal
|
2012-11-13 16:31:03 +00:00
|
|
|
import Hakyll.Core.Compiler.Require
|
2012-11-13 14:10:01 +00:00
|
|
|
import Hakyll.Core.Configuration
|
|
|
|
import Hakyll.Core.Dependencies
|
|
|
|
import Hakyll.Core.Identifier
|
2012-11-18 20:56:52 +00:00
|
|
|
import Hakyll.Core.Item.SomeItem
|
2012-12-31 14:16:14 +00:00
|
|
|
import Hakyll.Core.Logger (Logger, Verbosity)
|
2012-11-14 10:17:28 +00:00
|
|
|
import qualified Hakyll.Core.Logger as Logger
|
2012-11-18 20:56:52 +00:00
|
|
|
import Hakyll.Core.Provider
|
2012-11-13 14:10:01 +00:00
|
|
|
import Hakyll.Core.Routes
|
|
|
|
import Hakyll.Core.Rules.Internal
|
|
|
|
import Hakyll.Core.Store (Store)
|
|
|
|
import qualified Hakyll.Core.Store as Store
|
|
|
|
import Hakyll.Core.Util.File
|
|
|
|
import Hakyll.Core.Writable
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
2012-12-29 18:31:32 +00:00
|
|
|
-- | TODO Make this return exit code?
|
2012-12-31 14:16:14 +00:00
|
|
|
run :: Configuration -> Verbosity -> Rules a -> IO RuleSet
|
|
|
|
run config verbosity rules = do
|
2012-11-13 14:10:01 +00:00
|
|
|
-- Initialization
|
2012-12-31 14:16:14 +00:00
|
|
|
logger <- Logger.new verbosity
|
2012-11-14 12:32:31 +00:00
|
|
|
Logger.header logger "Initialising..."
|
|
|
|
Logger.message logger "Creating store..."
|
2012-11-21 19:38:13 +00:00
|
|
|
store <- Store.new (inMemoryCache config) $ storeDirectory config
|
2012-11-14 12:32:31 +00:00
|
|
|
Logger.message logger "Creating provider..."
|
2012-11-25 09:45:55 +00:00
|
|
|
provider <- newProvider store (shouldIgnoreFile config) $
|
|
|
|
providerDirectory config
|
2012-11-14 12:32:31 +00:00
|
|
|
Logger.message logger "Running rules..."
|
2012-11-14 10:17:28 +00:00
|
|
|
ruleSet <- runRules rules provider
|
2012-11-13 14:10:01 +00:00
|
|
|
|
|
|
|
-- Get old facts
|
|
|
|
mOldFacts <- Store.get store factsKey
|
|
|
|
let (oldFacts) = case mOldFacts of Store.Found f -> f
|
|
|
|
_ -> mempty
|
|
|
|
|
|
|
|
-- Build runtime read/state
|
|
|
|
let compilers = rulesCompilers ruleSet
|
|
|
|
read' = RuntimeRead
|
2012-11-21 19:38:13 +00:00
|
|
|
{ runtimeConfiguration = config
|
2012-11-13 14:10:01 +00:00
|
|
|
, runtimeLogger = logger
|
|
|
|
, runtimeProvider = provider
|
|
|
|
, runtimeStore = store
|
|
|
|
, runtimeRoutes = rulesRoutes ruleSet
|
2012-12-05 15:49:28 +00:00
|
|
|
, runtimeUniverse = M.fromList compilers
|
2012-11-13 14:10:01 +00:00
|
|
|
}
|
|
|
|
state = RuntimeState
|
|
|
|
{ runtimeDone = S.empty
|
|
|
|
, runtimeTodo = M.empty
|
|
|
|
, runtimeFacts = oldFacts
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Run the program and fetch the resulting state
|
|
|
|
result <- runErrorT $ runRWST build read' state
|
|
|
|
case result of
|
2012-11-15 09:24:46 +00:00
|
|
|
Left e -> do
|
|
|
|
Logger.error logger e
|
|
|
|
Logger.flush logger
|
|
|
|
exitWith $ ExitFailure 1
|
2012-11-13 14:10:01 +00:00
|
|
|
|
2012-11-15 09:24:46 +00:00
|
|
|
Right (_, s, _) -> do
|
|
|
|
Store.set store factsKey $ runtimeFacts s
|
|
|
|
Logger.flush logger
|
|
|
|
return ruleSet
|
2012-11-13 14:10:01 +00:00
|
|
|
where
|
|
|
|
factsKey = ["Hakyll.Core.Runtime.run", "facts"]
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
data RuntimeRead = RuntimeRead
|
|
|
|
{ runtimeConfiguration :: Configuration
|
|
|
|
, runtimeLogger :: Logger
|
2012-11-18 20:56:52 +00:00
|
|
|
, runtimeProvider :: Provider
|
2012-11-13 14:10:01 +00:00
|
|
|
, runtimeStore :: Store
|
|
|
|
, runtimeRoutes :: Routes
|
2012-12-05 15:49:28 +00:00
|
|
|
, runtimeUniverse :: Map Identifier (Compiler SomeItem)
|
2012-11-13 14:10:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
data RuntimeState = RuntimeState
|
|
|
|
{ runtimeDone :: Set Identifier
|
2012-11-18 20:56:52 +00:00
|
|
|
, runtimeTodo :: Map Identifier (Compiler SomeItem)
|
2012-11-13 14:10:01 +00:00
|
|
|
, runtimeFacts :: DependencyFacts
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
type Runtime a = RWST RuntimeRead () RuntimeState (ErrorT String IO) a
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
build :: Runtime ()
|
|
|
|
build = do
|
2012-11-14 10:17:28 +00:00
|
|
|
logger <- runtimeLogger <$> ask
|
|
|
|
Logger.header logger "Checking for out-of-date items"
|
2012-11-13 14:10:01 +00:00
|
|
|
scheduleOutOfDate
|
2012-11-14 10:17:28 +00:00
|
|
|
Logger.header logger "Compiling"
|
2012-11-13 14:10:01 +00:00
|
|
|
pickAndChase
|
2012-11-14 10:17:28 +00:00
|
|
|
Logger.header logger "Success"
|
2012-11-13 14:10:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
scheduleOutOfDate :: Runtime ()
|
|
|
|
scheduleOutOfDate = do
|
2012-11-14 12:32:31 +00:00
|
|
|
logger <- runtimeLogger <$> ask
|
2012-11-13 14:10:01 +00:00
|
|
|
provider <- runtimeProvider <$> ask
|
|
|
|
universe <- runtimeUniverse <$> ask
|
|
|
|
facts <- runtimeFacts <$> get
|
|
|
|
todo <- runtimeTodo <$> get
|
|
|
|
|
2012-12-05 15:49:28 +00:00
|
|
|
let identifiers = M.keys universe
|
2012-11-14 10:17:28 +00:00
|
|
|
modified <- fmap S.fromList $ flip filterM identifiers $
|
|
|
|
liftIO . resourceModified provider
|
2012-11-14 12:32:31 +00:00
|
|
|
let (ood, facts', msgs) = outOfDate identifiers modified facts
|
2012-12-05 15:49:28 +00:00
|
|
|
todo' = M.filterWithKey
|
|
|
|
(\id' _ -> id' `S.member` ood) universe
|
2012-11-13 14:10:01 +00:00
|
|
|
|
2012-11-14 12:32:31 +00:00
|
|
|
-- Print messages
|
|
|
|
mapM_ (Logger.debug logger) msgs
|
|
|
|
|
2012-11-13 14:10:01 +00:00
|
|
|
-- Update facts and todo items
|
|
|
|
modify $ \s -> s
|
2012-11-13 18:03:58 +00:00
|
|
|
{ runtimeDone = runtimeDone s `S.union`
|
|
|
|
(S.fromList identifiers `S.difference` ood)
|
|
|
|
, runtimeTodo = todo `M.union` todo'
|
2012-11-13 14:10:01 +00:00
|
|
|
, runtimeFacts = facts'
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
pickAndChase :: Runtime ()
|
|
|
|
pickAndChase = do
|
|
|
|
todo <- runtimeTodo <$> get
|
|
|
|
case M.minViewWithKey todo of
|
|
|
|
Nothing -> return ()
|
2012-11-13 18:03:58 +00:00
|
|
|
Just ((id', _), _) -> do
|
|
|
|
chase [] id'
|
|
|
|
pickAndChase
|
2012-11-13 14:10:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
chase :: [Identifier] -> Identifier -> Runtime ()
|
|
|
|
chase trail id'
|
|
|
|
| id' `elem` trail = return () -- Cycle detected!
|
|
|
|
| otherwise = do
|
|
|
|
logger <- runtimeLogger <$> ask
|
|
|
|
todo <- runtimeTodo <$> get
|
|
|
|
provider <- runtimeProvider <$> ask
|
|
|
|
universe <- runtimeUniverse <$> ask
|
|
|
|
routes <- runtimeRoutes <$> ask
|
|
|
|
store <- runtimeStore <$> ask
|
|
|
|
config <- runtimeConfiguration <$> ask
|
2012-11-29 11:04:57 +00:00
|
|
|
Logger.debug logger $ "Processing " ++ show id'
|
2012-11-13 14:10:01 +00:00
|
|
|
|
|
|
|
let compiler = todo M.! id'
|
|
|
|
read' = CompilerRead
|
2012-11-18 20:56:52 +00:00
|
|
|
{ compilerUnderlying = id'
|
2012-11-13 16:31:03 +00:00
|
|
|
, compilerProvider = provider
|
2012-12-05 15:49:28 +00:00
|
|
|
, compilerUniverse = M.keysSet universe
|
2012-11-13 16:31:03 +00:00
|
|
|
, compilerRoutes = routes
|
|
|
|
, compilerStore = store
|
|
|
|
, compilerLogger = logger
|
2012-11-13 14:10:01 +00:00
|
|
|
}
|
|
|
|
|
2012-11-14 10:17:28 +00:00
|
|
|
result <- liftIO $ runCompiler compiler read'
|
2012-11-13 14:10:01 +00:00
|
|
|
case result of
|
|
|
|
-- Rethrow error
|
|
|
|
CompilerError e -> throwError e
|
|
|
|
|
|
|
|
-- Huge success
|
2012-11-18 20:56:52 +00:00
|
|
|
CompilerDone (SomeItem item) cwrite -> do
|
|
|
|
-- TODO: Sanity check on itemIdentifier?
|
2012-11-24 09:56:19 +00:00
|
|
|
let facts = compilerDependencies cwrite
|
2012-11-14 10:17:28 +00:00
|
|
|
cacheHits
|
2012-11-14 12:32:31 +00:00
|
|
|
| compilerCacheHits cwrite <= 0 = "updated"
|
|
|
|
| otherwise = "cached "
|
|
|
|
|
|
|
|
-- Print some info
|
|
|
|
Logger.message logger $ cacheHits ++ " " ++ show id'
|
2012-11-14 10:17:28 +00:00
|
|
|
|
2012-11-13 14:10:01 +00:00
|
|
|
-- Write if necessary
|
|
|
|
case runRoutes routes id' of
|
|
|
|
Nothing -> return ()
|
2012-11-14 10:17:28 +00:00
|
|
|
Just url -> do
|
2012-11-13 14:10:01 +00:00
|
|
|
let path = destinationDirectory config </> url
|
|
|
|
liftIO $ makeDirectories path
|
2012-11-18 20:56:52 +00:00
|
|
|
liftIO $ write path item
|
2012-11-14 12:32:31 +00:00
|
|
|
Logger.debug logger $ "Routed to " ++ path
|
2012-11-13 14:10:01 +00:00
|
|
|
|
2012-12-13 21:25:28 +00:00
|
|
|
-- Save! (For load)
|
2012-11-24 09:56:19 +00:00
|
|
|
liftIO $ save store item
|
2012-11-13 16:31:03 +00:00
|
|
|
|
2012-11-13 14:10:01 +00:00
|
|
|
-- Update state
|
|
|
|
modify $ \s -> s
|
|
|
|
{ runtimeDone = S.insert id' (runtimeDone s)
|
|
|
|
, runtimeTodo = M.delete id' (runtimeTodo s)
|
|
|
|
, runtimeFacts = M.insert id' facts (runtimeFacts s)
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Try something else first
|
|
|
|
CompilerRequire dep c -> do
|
|
|
|
-- Update the compiler so we don't execute it twice
|
|
|
|
depDone <- (dep `S.member`) . runtimeDone <$> get
|
|
|
|
modify $ \s -> s
|
|
|
|
{ runtimeTodo = M.insert id'
|
|
|
|
(if depDone then c else compilerResult result)
|
|
|
|
(runtimeTodo s)
|
|
|
|
}
|
|
|
|
|
|
|
|
-- If the required item is already compiled, continue, or, start
|
|
|
|
-- chasing that
|
|
|
|
if depDone then chase trail id' else chase (id' : trail) dep
|