on master help sign in
Merge in @maxsnew's fixes from #424

Tested on small file where I changed a dependency and on elm-lang.org code.

commitb9e627f78e01522a4cf5d34113601237fdd4b7a8 authorEvan Czaplicki <info@elm-lang.org> dateWed 15 Jan 2014, 13:54 (13 years ago) parent66531250e40a32
4 files changed+183−123
compiler/Build/Dependencies.hs+46−42 compiler/Build/File.hs+129−78 compiler/Build/Print.hs+7−1 compiler/Compiler.hs+1−2
compiler/Build/Dependencies.hs+46−42
@@ -1,5 +1,5 @@
11{-# OPTIONS_GHC -W #-}
2−module Build.Dependencies (getSortedDependencies) where
2+module Build.Dependencies (getSortedDependencies, readDeps) where
33
44import Control.Applicative
55import Control.Monad.Error
@@ -12,9 +12,9 @@ import qualified Data.Map as Map
1212import qualified Data.Maybe as Maybe
1313import qualified Data.Set as Set
1414import System.Directory
15−import System.Exit
1615import System.FilePath as FP
17−import System.IO
16+
17+import Build.Print (failure)
1818
1919import qualified SourceSyntax.Module as Module
2020import qualified Parse.Parse as Parse
@@ -26,7 +26,7 @@ getSortedDependencies :: [FilePath] -> Module.Interfaces -> FilePath -> IO [Stri
2626getSortedDependencies srcDirs builtIns root =
2727 do extras <- extraDependencies
2828 let allSrcDirs = srcDirs ++ Maybe.fromMaybe [] extras
29− result <- runErrorT $ readDeps allSrcDirs builtIns root
29+ result <- runErrorT $ readAllDeps allSrcDirs builtIns root
3030 case result of
3131 Right deps -> sortDeps deps
3232 Left err -> failure $ err ++ if Maybe.isJust extras then "" else msg
@@ -34,8 +34,6 @@ getSortedDependencies srcDirs builtIns root =
3434 Path.dependencyFile ++
3535 " file if you\nare trying to use a 3rd party library."
3636
37−failure msg = hPutStrLn stderr msg >> exitFailure
38−
3937extraDependencies :: IO (Maybe [FilePath])
4038extraDependencies =
4139 do exists <- doesFileExist Path.dependencyFile
@@ -76,46 +74,52 @@ sortDeps depends =
7674 mistakes = filter (\scc -> length scc > 1) sccs
7775 msg = "A cyclical module dependency or was detected in:\n"
7876
79−readDeps :: [FilePath] -> Module.Interfaces -> FilePath -> ErrorT String IO [Deps]
80−readDeps srcDirs builtIns root = do
81− let ifaces = (Set.fromList . Map.keys) builtIns
82− State.evalStateT (go ifaces root) Set.empty
77+readAllDeps :: [FilePath] -> Module.Interfaces -> FilePath -> ErrorT String IO [Deps]
78+readAllDeps srcDirs builtIns root =
79+ do let ifaces = (Set.fromList . Map.keys) builtIns
80+ State.evalStateT (go ifaces root) Set.empty
8381 where
8482 go :: Set.Set String -> FilePath -> State.StateT (Set.Set String) (ErrorT String IO) [Deps]
8583 go builtIns root = do
86− (root', txt) <- lift $ getFile srcDirs root
87− case Parse.dependencies txt of
88− Left err -> throwError $ msg ++ show err
89− where msg = "Error resolving dependencies in " ++ root' ++ ":\n"
90−
91− Right (name,deps) ->
92− do seen <- State.get
93− let realDeps = Set.difference (Set.fromList deps) builtIns
94− newDeps = Set.difference (Set.filter (not . isNative) realDeps) seen
95− State.put (Set.insert name (Set.union newDeps seen))
96− rest <- mapM (go builtIns . toFilePath) (Set.toList newDeps)
97− return ((makeRelative "." root', name, Set.toList realDeps) : concat rest)
98−
99−getFile :: [FilePath] -> FilePath -> ErrorT String IO (FilePath,String)
100−getFile [] path =
101− throwError $ unlines
102− [ "Could not find file: " ++ path
103− , " If it is not in the root directory of your project, use"
104− , " --src-dir to declare additional locations for source files."
105− , " If it is part of a 3rd party library, it needs to be declared"
106− , " as a dependency in the " ++ Path.dependencyFile ++ " file." ]
107−
108−getFile (dir:dirs) path = do
109− let path' = dir </> path
110− exists <- liftIO $ doesFileExist path'
111− case exists of
112− True -> (,) path' `fmap` liftIO (readFile path')
113− False -> getFile dirs path
114−
84+ root' <- lift $ findSrcFile srcDirs root
85+ (name, deps) <- lift $ readDeps root'
86+ seen <- State.get
87+ let realDeps = Set.difference (Set.fromList deps) builtIns
88+ newDeps = Set.difference (Set.filter (not . isNative) realDeps) seen
89+ State.put (Set.insert name (Set.union newDeps seen))
90+ rest <- mapM (go builtIns . toFilePath) (Set.toList newDeps)
91+ return ((makeRelative "." root', name, Set.toList realDeps) : concat rest)
92+
93+readDeps :: FilePath -> ErrorT String IO (String, [String])
94+readDeps path = do
95+ txt <- lift $ readFile path
96+ case Parse.dependencies txt of
97+ Left err -> throwError $ msg ++ show err
98+ where msg = "Error resolving dependencies in " ++ path ++ ":\n"
99+ Right o -> return o
100+
101+findSrcFile :: [FilePath] -> FilePath -> ErrorT String IO FilePath
102+findSrcFile dirs path = foldr tryDir notFound dirs
103+ where
104+ notFound = throwError $ unlines
105+ [ "Could not find file: " ++ path
106+ , " If it is not in the root directory of your project, use"
107+ , " --src-dir to declare additional locations for source files."
108+ , " If it is part of a 3rd party library, it needs to be declared"
109+ , " as a dependency in the " ++ Path.dependencyFile ++ " file." ]
110+ tryDir dir next = do
111+ let path' = dir </> path
112+ exists <- liftIO $ doesFileExist path'
113+ if exists
114+ then return path'
115+ else next
116+
117+isNative :: String -> Bool
115118isNative name = List.isPrefixOf "Native." name
116119
117120toFilePath :: String -> FilePath
118121toFilePath name = map swapDots name ++ ext
119− where swapDots '.' = '/'
120− swapDots c = c
121− ext = if isNative name then ".js" else ".elm"
122+ where
123+ swapDots '.' = '/'
124+ swapDots c = c
125+ ext = if isNative name then ".js" else ".elm"
compiler/Build/File.hs+129−78
@@ -1,92 +1,137 @@
11{-# OPTIONS_GHC -W #-}
22module Build.File (build) where
33
4−import Control.Monad (when)
5−import qualified Data.Binary as Binary
6−import qualified Data.Map as Map
4+import Control.Applicative ((<$>))
5+import Control.Monad.Error (runErrorT)
6+import Control.Monad.RWS.Strict
77import System.Directory
88import System.Exit
99import System.FilePath
1010import System.IO
1111
12+import qualified Data.Binary as Binary
13+import qualified Data.Maybe as Maybe
14+import qualified Data.Map as Map
15+import qualified Data.ByteString.Lazy as L
16+
17+import qualified Build.Dependencies as Deps
18+import qualified Build.Flags as Flag
19+import qualified Build.Interface as Interface
20+import qualified Build.Print as Print
21+import qualified Build.Source as Source
22+import qualified Build.Utils as Utils
23+import qualified Generate.JavaScript as JS
24+import qualified Parse.Module as Parser
25+import qualified SourceSyntax.Module as M
1226import qualified Transform.Canonicalize as Canonical
1327
14−import qualified Data.ByteString.Lazy as L
15−
16−import qualified Build.Utils as Utils
17−import qualified Build.Flags as Flag
18−import qualified Build.Interface as Interface
19−import qualified Build.Print as Print
20−import qualified Build.Source as Source
21−import qualified Generate.JavaScript as JS
22−import qualified Parse.Module as Parser
23−import qualified SourceSyntax.Module as M
24−
25−build :: Flag.Flags -> Int -> M.Interfaces -> String -> [FilePath]
26− -> IO (String, M.Interfaces)
27−build _ _ interfaces moduleName [] =
28− return (moduleName, interfaces)
29−build flags numModules interfaces _ (filePath:rest) =
30− do (name,interface) <-
31− build1 flags (numModules - length rest) numModules interfaces filePath
32− let interfaces' = Map.insert name interface interfaces
33− build flags numModules interfaces' name rest
34−
35−
36−build1 :: Flag.Flags -> Int -> Int -> M.Interfaces -> FilePath
37− -> IO (String, M.ModuleInterface)
38−build1 flags moduleNum numModules interfaces filePath =
39− do compiled <- alreadyCompiled flags filePath
40− case compiled of
41− False -> compile flags number interfaces filePath
42− True -> retrieve flags interfaces filePath
43− where
44− number = "[" ++ show moduleNum ++ " of " ++ show numModules ++ "]"
45−
46−
47−alreadyCompiled :: Flag.Flags -> FilePath -> IO Bool
48−alreadyCompiled flags filePath = do
49− existsi <- doesFileExist (Utils.elmi flags filePath)
50− existso <- doesFileExist (Utils.elmo flags filePath)
51− if not existsi || not existso
52− then return False
53− else do tsrc <- getModificationTime filePath
54− tint <- getModificationTime (Utils.elmo flags filePath)
55− return (tsrc <= tint)
56−
57−retrieve :: Flag.Flags -> Map.Map String M.ModuleInterface -> FilePath
58− -> IO (String, M.ModuleInterface)
59−retrieve flags interfaces filePath = do
60− iface <- Interface.load (Utils.elmi flags filePath)
28+-- Reader: Runtime flags, always accessible
29+-- Writer: Remember the last module to be accessed
30+-- State: Build up a map of the module interfaces
31+type BuildT m a = RWST Flag.Flags (Last String) BInterfaces m a
32+type Build a = BuildT IO a
33+
34+-- Interfaces, remembering if something was recompiled
35+type BInterfaces = Map.Map String (Bool, M.ModuleInterface)
36+
37+evalBuild :: Flag.Flags -> M.Interfaces -> Build () -> IO (Maybe String)
38+evalBuild flags interfaces b = do
39+ (_, s) <- evalRWST b flags (fmap notUpdated interfaces)
40+ return . getLast $ s
41+ where
42+ notUpdated i = (False, i)
43+
44+-- | Builds a list of files, returning the moduleName of the last one.
45+-- Returns \"\" if the list is empty
46+build :: Flag.Flags -> M.Interfaces -> [FilePath] -> IO String
47+build flags is = fmap (Maybe.fromMaybe "") . evalBuild flags is . buildAll
48+
49+buildAll :: [FilePath] -> Build ()
50+buildAll fs = mapM_ (uncurry build1) (zip [1..] fs)
51+ where build1 :: Integer -> FilePath -> Build ()
52+ build1 num fname = do
53+ shouldCompile <- shouldBeCompiled fname
54+ if shouldCompile
55+ then compile number fname
56+ else retrieve fname
57+
58+ where number = join ["[", show num, " of ", show total, "]"]
59+
60+ total = length fs
61+
62+shouldBeCompiled :: FilePath -> Build Bool
63+shouldBeCompiled filePath = do
64+ flags <- ask
65+ let alreadyCompiled = liftIO $ do
66+ existsi <- doesFileExist (Utils.elmi flags filePath)
67+ existso <- doesFileExist (Utils.elmo flags filePath)
68+ return $ existsi && existso
69+
70+ outDated = liftIO $ do
71+ tsrc <- getModificationTime filePath
72+ tint <- getModificationTime (Utils.elmo flags filePath)
73+ return (tsrc > tint)
74+
75+ dependenciesUpdated = do
76+ eDeps <- liftIO . runErrorT $ Deps.readDeps filePath
77+ case eDeps of
78+ -- Should never actually reach here
79+ Left err -> liftIO $ Print.failure err
80+ Right (_, deps) -> anyM wasCompiled deps
81+
82+
83+ in (not <$> alreadyCompiled) `orM` outDated `orM` dependenciesUpdated
84+
85+wasCompiled :: String -> Build Bool
86+wasCompiled modul = maybe False fst . Map.lookup modul <$> get
87+
88+-- Short-circuiting monadic (||)
89+infixr 2 `orM`
90+orM :: (Monad m) => m Bool -> m Bool -> m Bool
91+orM m1 m2 = do b1 <- m1
92+ if b1
93+ then return b1
94+ else m2
95+
96+anyM :: (Monad m) => (a -> m Bool) -> [a] -> m Bool
97+anyM f = foldr (orM . f) (return False)
98+
99+retrieve :: FilePath -> Build ()
100+retrieve filePath = do
101+ flags <- ask
102+ iface <- liftIO $ Interface.load (Utils.elmi flags filePath)
61103 case Interface.isValid filePath iface of
62104 Right (name, interface) ->
63− do when (Flag.print_types flags) (Print.interfaceTypes interfaces interface)
64− return (name, interface)
65− Left err ->
66− do hPutStrLn stderr err
67− exitFailure
68−
69−compile :: Flag.Flags -> String -> M.Interfaces -> FilePath
70− -> IO (String, M.ModuleInterface)
71−compile flags number interfaces filePath =
72− do source <- readFile filePath
73− let name = getName source
74− printStatus name
75−
76− createDirectoryIfMissing True (Flag.cache_dir flags)
77− createDirectoryIfMissing True (Flag.build_dir flags)
78−
79− metaModule <-
80− case Source.build (Flag.no_prelude flags) interfaces source of
105+ do binterfaces <- get
106+ let interfaces = snd <$> binterfaces
107+ liftIO $ when (Flag.print_types flags) (Print.interfaceTypes interfaces interface)
108+ update name interface False
109+
110+ Left err -> liftIO $ Print.failure err
111+
112+compile :: String -> FilePath -> Build ()
113+compile number filePath =
114+ do flags <- ask
115+ binterfaces <- get
116+ source <- liftIO $ readFile filePath
117+ let interfaces = snd <$> binterfaces
118+ name = getName source
119+ liftIO $ do
120+ printStatus name
121+ createDirectoryIfMissing True (Flag.cache_dir flags)
122+ createDirectoryIfMissing True (Flag.build_dir flags)
123+
124+ metaModule <-
125+ liftIO $ case Source.build (Flag.no_prelude flags) interfaces source of
81126 Right modul -> return modul
82127 Left errors -> do Print.errors errors
83128 exitFailure
84129
85− when (Flag.print_types flags) $ Print.metaTypes interfaces metaModule
86−
87− let intermediate = (name, Canonical.interface name $ M.metaToInterface metaModule)
88− generateCache intermediate metaModule
89− return intermediate
130+ liftIO $ when (Flag.print_types flags) $ Print.metaTypes interfaces metaModule
131+
132+ let newInters = Canonical.interface name $ M.metaToInterface metaModule
133+ generateCache name newInters metaModule
134+ update name newInters True
90135
91136 where
92137 getName source = case Parser.getModuleName source of
@@ -98,8 +143,14 @@ compile flags number interfaces filePath =
98143 , replicate (max 1 (20 - length name)) ' '
99144 , "( " ++ filePath ++ " )" ]
100145
101− generateCache intermediate metaModule = do
102− createDirectoryIfMissing True . dropFileName $ Utils.elmi flags filePath
103− writeFile (Utils.elmo flags filePath) (JS.generate metaModule)
104− withBinaryFile (Utils.elmi flags filePath) WriteMode $ \handle ->
105− L.hPut handle (Binary.encode intermediate)
146+ generateCache name interfs metaModule = do
147+ flags <- ask
148+ liftIO $ do
149+ createDirectoryIfMissing True . dropFileName $ Utils.elmi flags filePath
150+ writeFile (Utils.elmo flags filePath) (JS.generate metaModule)
151+ withBinaryFile (Utils.elmi flags filePath) WriteMode $ \handle ->
152+ L.hPut handle (Binary.encode (name, interfs))
153+
154+update :: String -> M.ModuleInterface -> Bool -> Build ()
155+update name inter wasUpdated = modify (Map.insert name (wasUpdated, inter))
156+ >> tell (Last . Just $ name)
compiler/Build/Print.hs+7−1
@@ -1,5 +1,8 @@
11module Build.Print where
22
3+import System.IO (hPutStrLn, stderr)
4+import System.Exit (exitFailure)
5+
36import qualified Data.Map as Map
47import qualified Data.List as List
58import qualified SourceSyntax.Module as M
@@ -29,4 +32,7 @@ types interfaces types' aliases imports =
2932
3033errors :: [P.Doc] -> IO ()
3134errors errs =
32− mapM_ print (List.intersperse (P.text " ") errs)
35+ mapM_ print (List.intersperse (P.text " ") errs)
36+
37+failure :: String -> IO a
38+failure msg = hPutStrLn stderr msg >> exitFailure
compiler/Compiler.hs+1−2
@@ -37,8 +37,7 @@ build flags rootFile =
3737 then getSortedDependencies (Flag.src_dir flags) builtIns rootFile
3838 else return [rootFile]
3939
40− (moduleName, _) <-
41− File.build flags (length files) builtIns "" files
40+ moduleName <- File.build flags builtIns files
4241
4342 js <- foldM appendToOutput BS.empty files
4443