barley-0.3: src/Barley/Loader.hs
-- Copyright 2010 Google Inc.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
module Barley.Loader (
EntryPoint(),
entryPoint,
compileAndLoadFirst,
)
where
import Barley.AltLoad -- instead of System.Plugins.Load
import Control.Monad (when)
import Data.IORef
import qualified Data.Map as M
import System.Directory (createDirectoryIfMissing)
import System.IO.Unsafe (unsafePerformIO)
import System.Plugins.Make
import System.Plugins.Utils (newer)
-- | An representation of a possible entry point in a file that, when
-- loaded, run, returns an a value of type a
newtype EntryPoint a = EntryPoint { loadEntryPoint :: Module -> IO (Maybe a) }
-- | Builds an entry point. The entry point is named with the given symbol
-- and returns a value of type a. The supplied transformer function is used
-- to supply a value of some type b. This is so that all entry points can
-- return the same type.
entryPoint :: String -> (a -> b) -> EntryPoint b
entryPoint symbol xform = EntryPoint (loadWith symbol xform)
-- | A mapping between source files and the module objects that are loaded
-- While System.Plugins should keep this information, it doesn't, so we have to.
type ModuleMap = M.Map FilePath Module
theModMap :: IORef ModuleMap
theModMap = unsafePerformIO $ newIORef M.empty
{-# NOINLINE theModMap #-}
-- | Given a file, try to make sure it is built and up-to-date
-- Can't use recompileAll here because it "hunts" for the source files (rather
-- than having Module remember them), and we store the objects in a different
-- directory than the sources. Hence, our test doesn't recursively look at
-- dependent modules. Fie!
compileModule :: FilePath -> IO (Either String Module)
compileModule srcFile = do
mm <- readIORef theModMap
let existingModule = M.lookup srcFile mm
needBuild <- case existingModule of
Just m -> srcFile `newer` path m
Nothing -> return True
makeStatus <- case (needBuild, existingModule) of
(False, Just m) -> return (MakeSuccess NotReq $ path m)
_ -> noteMakeAll >> makeAll srcFile makeArgs
case (makeStatus, existingModule) of
(MakeSuccess NotReq _, _ ) -> return ()
(_ , Just m) -> noteUnloadAll >> unloadAll m
(_ , _ ) -> return ()
case (makeStatus, existingModule) of
(MakeFailure errs, _) -> do
noteFailure errs
writeIORef theModMap (M.delete srcFile mm)
return $ Left $ unlines errs
(MakeSuccess NotReq _, Just m) -> do
noteNothing
return (Right m)
(MakeSuccess _ objFile, _) -> do
noteLoading
m <- loadObjectFile objFile [buildDir] []
writeIORef theModMap (M.insert srcFile m mm)
return (Right m)
where
buildDir = ".build"
makeArgs =
[ "-ilib" -- users can put non-served source here
, "-outputdir", buildDir -- sets odir, hidir, and stubdir in one go
, "-odir", buildDir -- plugins only looks for odir, not outputdir
]
debug = False
noteMakeAll = when debug $ putStrLn ("makeAll of " ++ srcFile)
noteUnloadAll = when debug $ putStrLn "unloading..."
noteFailure errs = when debug $ putStrLn ("failed: " ++ concat errs)
noteNothing = when debug $ putStrLn "nothin' to do"
noteLoading = when debug $ putStrLn "loading..."
-- | Given a file, compile and load the file, then load the value from the
-- first entry point that succeeds (Right). If either the compilation fails,
-- or the the loading fails, then an error string is returned (Left).
compileAndLoadFirst :: FilePath -> [EntryPoint a] -> IO (Either String a)
compileAndLoadFirst srcFile eps = do
createDirectoryIfMissing False ".build" -- plugins needs it to pre-exist
cs <- compileModule srcFile
case cs of
Right m -> tryEntryPoints m eps
Left errs -> return $ Left errs
tryEntryPoints :: Module -> [EntryPoint a] -> IO (Either String a)
tryEntryPoints m (ep:eps) =
loadEntryPoint ep m >>= maybe (tryEntryPoints m eps) (return . Right)
tryEntryPoints _ [] = return (Left "No matching entry points found.")
loadWith :: String -> (a -> b) -> Module -> IO (Maybe b)
loadWith sym xfn m = (xfn `fmap`) `fmap` loadFunction m sym