@@ -1,68 +1,100 @@
1module Build.File (build) where
1module Build.File (build) where
3−import Control.Applicative ((<$>))
3+import Control.Applicative ((<$>), (<*>), pure)
4−import Control.Monad (when)
4+import Control.Monad (when)
5+import Control.Monad.Error (runErrorT)
5import Control.Monad.RWS.Strict
6import Control.Monad.RWS.Strict
6−import qualified Data.Binary as Binary
7−import qualified Data.List as List
8−import qualified Data.Maybe as Maybe
9−import Data.Monoid (Last(..))
7+import Data.Monoid (Last(..))
10−import qualified Data.Map as Map
11import System.Directory
8import System.Directory
12import System.Exit
9import System.Exit
13import System.FilePath
10import System.FilePath
14import System.IO
11import System.IO
16−import qualified Transform.Canonicalize as Canonical
13+import qualified Data.Binary as Binary
14+import qualified Data.List as List
15+import qualified Data.Maybe as Maybe
16+import qualified Data.Map as Map
17−import qualified Data.ByteString.Lazy as L
17+import qualified Data.ByteString.Lazy as L
19−import qualified Build.Utils as Utils
19+import qualified Build.Dependencies as Deps
20−import qualified Build.Flags as Flag
20+import qualified Build.Flags as Flag
21−import qualified Build.Source as Source
21+import qualified Build.Print as Print
22+import qualified Build.Source as Source
22−import qualified Build.Print as Print
23+import qualified Build.Utils as Utils
23−import qualified Generate.JavaScript as JS
24+import qualified Generate.JavaScript as JS
24import qualified InterfaceSerialization as IS
25import qualified InterfaceSerialization as IS
25−import qualified Parse.Module as Parser
26+import qualified Parse.Module as Parser
26−import qualified SourceSyntax.Module as M
27+import qualified SourceSyntax.Module as M
28+import qualified Transform.Canonicalize as Canonical
28-- Reader: Runtime flags, always accessible
30-- Reader: Runtime flags, always accessible
29-- Writer: Remember the last module to be accessed
31-- Writer: Remember the last module to be accessed
30-- State: Build up a map of the module interfaces
32-- State: Build up a map of the module interfaces
31−type BuildT m a = RWST Flag.Flags (Last String) M.Interfaces m a
33+type BuildT m a = RWST Flag.Flags (Last String) BInterfaces m a
32type Build a = BuildT IO a
34type Build a = BuildT IO a
36+-- Interfaces, remembering if something was recompiled
37+type BInterfaces = Map.Map String (Bool, M.ModuleInterface)
34evalBuild :: Flag.Flags -> M.Interfaces -> Build () -> IO (Maybe String)
39evalBuild :: Flag.Flags -> M.Interfaces -> Build () -> IO (Maybe String)
35evalBuild fs is b = do
40evalBuild fs is b = do
36− (_, s) <- evalRWST b fs is
41+ (_, s) <- evalRWST b fs (fmap notUpdated is)
37 return . getLast $ s
42 return . getLast $ s
43+ where notUpdated i = (False, i)
39−-- Builds a list of files, returning the moduleName of the last one.
45+-- | Builds a list of files, returning the moduleName of the last one.
40−-- Returns "" if the list is empty
46+-- Returns \"\" if the list is empty
41build :: Flag.Flags -> M.Interfaces -> [FilePath] -> IO String
47build :: Flag.Flags -> M.Interfaces -> [FilePath] -> IO String
42build flags is = fmap (Maybe.fromMaybe "") . evalBuild flags is . buildAll
48build flags is = fmap (Maybe.fromMaybe "") . evalBuild flags is . buildAll
44buildAll :: [FilePath] -> Build ()
50buildAll :: [FilePath] -> Build ()
45−buildAll fs = mapM_ build1 (zip [1..] fs)
51+buildAll fs = mapM_ (uncurry build1) (zip [1..] fs)
46− where build1 (num, fname) = do
52+ where build1 num fname = do
47− compiled <- alreadyCompiled filePath
53+ shouldCompile <- shouldBeCompiled fname
48− case compiled of
54+ if shouldCompile
49− False ->
55+ then compile number fname
50− let number = join ["[", show num, " of ", show total, "]"]
58+ where number = join ["[", show num, " of ", show total, "]"]
51− compile number filePath
59+
52− True -> retrieve filePath
53 total = length fs
60 total = length fs
55−alreadyCompiled :: FilePath -> Build Bool
62+shouldBeCompiled :: FilePath -> Build Bool
56−alreadyCompiled filePath = do
63+shouldBeCompiled filePath = do
57 flags <- ask
64 flags <- ask
58− liftIO $ do
65+ let alreadyCompiled = liftIO $ do
59− existsi <- doesFileExist (Utils.elmi flags filePath)
66+ existsi <- doesFileExist (Utils.elmi flags filePath)
60− existso <- doesFileExist (Utils.elmo flags filePath)
67+ existso <- doesFileExist (Utils.elmo flags filePath)
61− if not existsi || not existso
68+ return $ existsi && existso
70+ outDated = liftIO $ do
71+ tsrc <- getModificationTime filePath
72+ tint <- getModificationTime (Utils.elmo flags filePath)
75+ dependenciesUpdated = do
76+ eDeps <- liftIO . runErrorT $ Deps.readDeps filePath
78+ -- Should never actually reach here
79+ Left err -> liftIO $ Print.failure err
80+ Right (_, deps) -> anyM wasCompiled deps
83+ in (not <$> alreadyCompiled) `orM` outDated `orM` dependenciesUpdated
85+wasCompiled :: String -> Build Bool
86+wasCompiled modul = maybe False fst . Map.lookup modul <$> get
88+-- Short-circuiting monadic (||)
90+orM :: (Monad m) => m Bool -> m Bool -> m Bool
91+orM m1 m2 = do b1 <- m1
62− then return False
93+ then return b1
63− else do tsrc <- getModificationTime filePath
94+ else m2
64− tint <- getModificationTime (Utils.elmo flags filePath)
95+
65− return (tsrc <= tint)
96+anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
97+anyM f = foldr (orM . f) (return False)
67retrieve :: FilePath -> Build ()
99retrieve :: FilePath -> Build ()
68retrieve filePath = do
100retrieve filePath = do
@@ -72,19 +104,20 @@ retrieve filePath = do
72 let binary = IS.interfaceDecode elmi =<< bytes
104 let binary = IS.interfaceDecode elmi =<< bytes
73 case IS.validVersion filePath =<< binary of
105 case IS.validVersion filePath =<< binary of
74 Right (name, interface) ->
106 Right (name, interface) ->
75− do interfaces <- get
107+ do binterfaces <- get
108+ let interfaces = snd <$> binterfaces
76 liftIO $ when (Flag.print_types flags) (Print.interfaceTypes interfaces interface)
109 liftIO $ when (Flag.print_types flags) (Print.interfaceTypes interfaces interface)
77− update name interface
110+ update name interface False
79− Left err ->
112+ Left err -> liftIO $ Print.failure err
80− liftIO $ hPutStrLn stderr err >> exitFailure
82compile :: String -> FilePath -> Build ()
114compile :: String -> FilePath -> Build ()
83compile number filePath =
115compile number filePath =
84 do flags <- ask
116 do flags <- ask
85− interfaces <- get
117+ binterfaces <- get
86 source <- liftIO $ readFile filePath
118 source <- liftIO $ readFile filePath
119+ let interfaces = snd <$> binterfaces
87− let name = getName source
120+ name = getName source
88 liftIO $ do
121 liftIO $ do
89 printStatus name
122 printStatus name
90 createDirectoryIfMissing True (Flag.cache_dir flags)
123 createDirectoryIfMissing True (Flag.cache_dir flags)
@@ -98,9 +131,9 @@ compile number filePath =
99 liftIO $ when (Flag.print_types flags) $ Print.metaTypes interfaces metaModule
132 liftIO $ when (Flag.print_types flags) $ Print.metaTypes interfaces metaModule
101− let intermediate = (name, Canonical.interface name $ M.metaToInterface metaModule)
134+ let newInters = Canonical.interface name $ M.metaToInterface metaModule
102− generateCache intermediate metaModule
135+ generateCache name newInters metaModule
103− uncurry update intermediate
136+ update name newInters True
106 getName source = case Parser.getModuleName source of
139 getName source = case Parser.getModuleName source of
@@ -112,14 +145,14 @@ compile number filePath =
112 , replicate (max 1 (20 - length name)) ' '
145 , replicate (max 1 (20 - length name)) ' '
113 , "( " ++ filePath ++ " )" ]
146 , "( " ++ filePath ++ " )" ]
115− generateCache intermediate metaModule = do
148+ generateCache name interfs metaModule = do
116 flags <- ask
149 flags <- ask
117 liftIO $ do
150 liftIO $ do
118 createDirectoryIfMissing True . dropFileName $ Utils.elmi flags filePath
151 createDirectoryIfMissing True . dropFileName $ Utils.elmi flags filePath
119 writeFile (Utils.elmo flags filePath) (JS.generate metaModule)
152 writeFile (Utils.elmo flags filePath) (JS.generate metaModule)
120 withBinaryFile (Utils.elmi flags filePath) WriteMode $ \handle ->
153 withBinaryFile (Utils.elmi flags filePath) WriteMode $ \handle ->
121− L.hPut handle (Binary.encode intermediate)
154+ L.hPut handle (Binary.encode (name, interfs))
123−update :: String -> M.ModuleInterface -> Build ()
156+update :: String -> M.ModuleInterface -> Bool -> Build ()
124−update name inter = modify (Map.insert name inter)
157+update name inter wasUpdated = modify (Map.insert name (wasUpdated, inter))
125− >> tell (Last . Just $ name)
158+ >> tell (Last . Just $ name)