1-----------------------------------------------------------------------------
4-- Copyright : (c) 2013-14 Phil Freeman, (c) 2014 Gary Burgess, and other contributors
7-- Maintainer : Phil Freeman <paf31@cantab.net>
8-- Stability : experimental
13-----------------------------------------------------------------------------
15{-# LANGUAGE DataKinds #-}
16{-# LANGUAGE FlexibleContexts #-}
17{-# LANGUAGE GeneralizedNewtypeDeriving #-}
18{-# LANGUAGE TupleSections #-}
19{-# LANGUAGE RankNTypes #-}
20{-# LANGUAGE RecordWildCards #-}
21{-# LANGUAGE ScopedTypeVariables #-}
23module Language.PureScript.Make
27 , ProgressMessage(..), renderProgressMessage
32 -- * Implementation of Make API using files on disk
38import Control.Applicative
39import Control.Arrow ((&&&))
41import Control.Monad.Error.Class (MonadError(..))
42import Control.Monad.Trans.Except
43import Control.Monad.Reader
44import Control.Monad.Writer
45import Control.Monad.Supply
47import Data.Function (on)
48import Data.List (sortBy, groupBy)
49import Data.Maybe (fromMaybe)
51import Data.Traversable (traverse)
52import Data.Version (showVersion)
53import qualified Data.Map as M
54import qualified Data.Set as S
56import System.Directory
57 (doesFileExist, getModificationTime, createDirectoryIfMissing)
58import System.FilePath ((</>), takeDirectory)
59import System.IO.Error (tryIOError)
62import Language.PureScript.AST
63import Language.PureScript.CodeGen.Externs (moduleToPs)
64import Language.PureScript.Environment
65import Language.PureScript.Errors
66import Language.PureScript.Linter
67import Language.PureScript.ModuleDependencies
68import Language.PureScript.Names
69import Language.PureScript.Options
70import Language.PureScript.Parser
71import Language.PureScript.Pretty
72import Language.PureScript.Renamer
73import Language.PureScript.Sugar
74import Language.PureScript.TypeChecker
75import qualified Language.PureScript.Constants as C
77import qualified Language.PureScript.CodeGen.JS as J
78import qualified Language.PureScript.CoreFn as CF
79import qualified Paths_purescript as Paths
81-- | Progress messages from the make process
83 = CompilingModule ModuleName
84 deriving (Show, Eq, Ord)
86-- | Render a progress message
87renderProgressMessage :: ProgressMessage -> String
88renderProgressMessage (CompilingModule mn) = "Compiling " ++ runModuleName mn
90-- | Actions that require implementations when running in "make" mode.
92-- This type exists to make two things abstract:
94-- * The particular backend being used (Javascript, C++11, etc.)
96-- * The details of how files are read/written etc.
98data MakeActions m = MakeActions {
100 -- Get the timestamp for the input file(s) for a module. If there are multiple
101 -- files (.purs and foreign files, for example) the timestamp should be for
102 -- the most recently modified file.
104 getInputTimestamp :: ModuleName -> m (Either RebuildPolicy (Maybe UTCTime))
106 -- Get the timestamp for the output files for a module. This should be the
107 -- timestamp for the oldest modified file, or Nothing if any of the required
108 -- output files are missing.
110 , getOutputTimestamp :: ModuleName -> m (Maybe UTCTime)
112 -- Read the externs file for a module as a string and also return the actual
113 -- path for the file.
114 , readExterns :: ModuleName -> m (FilePath, String)
116 -- Run the code generator for the module and write any required output files.
118 , codegen :: CF.Module CF.Ann -> Environment -> Externs -> SupplyT m ()
120 -- Respond to a progress update.
122 , progress :: ProgressMessage -> m ()
126-- Generated code for an externs file.
131-- Determines when to rebuild a module
134 -- | Never rebuild this module
136 -- | Always rebuild this module
137 | RebuildAlways deriving (Show, Eq, Ord)
140-- Compiles in "make" mode, compiling each module separately to a js files and an externs file
142-- If timestamps have not changed, the externs file can be used to provide the module's types without
143-- having to typecheck the module again.
145make :: forall m. (Functor m, Applicative m, Monad m, MonadReader Options m, MonadError MultipleErrors m, MonadWriter MultipleErrors m)
149make MakeActions{..} ms = do
150 (sorted, graph) <- sortModules $ map importPrim ms
152 toRebuild <- foldM (\s (Module _ moduleName' _ _) -> do
153 inputTimestamp <- getInputTimestamp moduleName'
154 outputTimestamp <- getOutputTimestamp moduleName'
155 return $ case (inputTimestamp, outputTimestamp) of
156 (Right (Just t1), Just t2) | t1 < t2 -> s
157 (Left RebuildNever, Just _) -> s
158 _ -> S.insert moduleName' s) S.empty sorted
160 marked <- rebuildIfNecessary (reverseDependencies graph) toRebuild sorted
161 (desugared, nextVar) <- runSupplyT 0 $ zip (map fst marked) <$> desugar (map snd marked)
162 evalSupplyT nextVar $ go initEnvironment desugared
165 go :: Environment -> [(Bool, Module)] -> SupplyT m Environment
166 go env [] = return env
167 go env ((False, m) : ms') = do
168 (_, env') <- lift . runCheck' env $ typeCheckModule Nothing m
170 go env ((True, m@(Module coms moduleName' _ exps)) : ms') = do
171 lift . progress $ CompilingModule moduleName'
172 (checked@(Module _ _ elaborated _), env') <- lift . runCheck' env $ typeCheckModule Nothing m
173 checkExhaustiveModule env' checked
174 regrouped <- createBindingGroups moduleName' . collapseBindingGroups $ elaborated
175 let mod' = Module coms moduleName' regrouped exps
176 corefn = CF.moduleToCoreFn env' mod'
177 [renamed] = renameInModules [corefn]
178 exts = moduleToPs mod' env'
179 codegen renamed env' exts
182 rebuildIfNecessary :: M.Map ModuleName [ModuleName] -> S.Set ModuleName -> [Module] -> m [(Bool, Module)]
183 rebuildIfNecessary _ _ [] = return []
184 rebuildIfNecessary graph toRebuild (m@(Module _ moduleName' _ _) : ms') | moduleName' `S.member` toRebuild = do
185 let deps = fromMaybe [] $ moduleName' `M.lookup` graph
186 toRebuild' = toRebuild `S.union` S.fromList deps
187 (:) (True, m) <$> rebuildIfNecessary graph toRebuild' ms'
188 rebuildIfNecessary graph toRebuild (Module _ moduleName' _ _ : ms') = do
189 (path, externs) <- readExterns moduleName'
190 externsModules <- fmap (map snd) . alterErrors $ parseModulesFromFiles id [(path, externs)]
191 case externsModules of
192 [m'@(Module _ moduleName'' _ _)] | moduleName'' == moduleName' -> (:) (False, m') <$> rebuildIfNecessary graph toRebuild ms'
193 _ -> throwError . errorMessage . InvalidExternsFile $ path
195 alterErrors = flip catchError $ \(MultipleErrors errs) ->
196 throwError . MultipleErrors $ flip map errs $ \e -> case e of
197 SimpleErrorWrapper (ErrorParsingModule err) -> SimpleErrorWrapper (ErrorParsingExterns err)
200reverseDependencies :: ModuleGraph -> M.Map ModuleName [ModuleName]
201reverseDependencies g = combine [ (dep, mn) | (mn, deps) <- g, dep <- deps ]
203 combine :: (Ord a) => [(a, b)] -> M.Map a [b]
204 combine = M.fromList . map ((fst . head) &&& map snd) . groupBy ((==) `on` fst) . sortBy (compare `on` fst)
207-- Add an import declaration for a module if it does not already explicitly import it.
209addDefaultImport :: ModuleName -> Module -> Module
210addDefaultImport toImport m@(Module coms mn decls exps) =
211 if isExistingImport `any` decls || mn == toImport then m
212 else Module coms mn (ImportDeclaration toImport Implicit Nothing : decls) exps
214 isExistingImport (ImportDeclaration mn' _ _) | mn' == toImport = True
215 isExistingImport (PositionedDeclaration _ _ d) = isExistingImport d
216 isExistingImport _ = False
218importPrim :: Module -> Module
219importPrim = addDefaultImport (ModuleName [ProperName C.prim])
222-- A monad for running make actions
224newtype Make a = Make { unMake :: ReaderT Options (WriterT MultipleErrors (ExceptT MultipleErrors IO)) a }
225 deriving (Functor, Applicative, Monad, MonadIO, MonadError MultipleErrors, MonadWriter MultipleErrors, MonadReader Options)
228-- Execute a 'Make' monad, returning either errors, or the result of the compile plus any warnings.
230runMake :: Options -> Make a -> IO (Either MultipleErrors (a, MultipleErrors))
231runMake opts = runExceptT . runWriterT . flip runReaderT opts . unMake
233makeIO :: (IOError -> ErrorMessage) -> IO a -> Make a
235 e <- liftIO $ tryIOError io
236 either (throwError . singleError . f) return e
238-- Traverse (Either e) instance (base 4.7)
239traverseEither :: Applicative f => (a -> f b) -> Either e a -> f (Either e b)
240traverseEither _ (Left x) = pure (Left x)
241traverseEither f (Right y) = Right <$> f y
244-- A set of make actions that read and write modules from the given directory.
246buildMakeActions :: FilePath -- ^ the output directory
247 -> M.Map ModuleName (Either RebuildPolicy String) -- ^ a map between module names and paths to the file containing the PureScript module
248 -> M.Map ModuleName (FilePath, ForeignJS) -- ^ a map between module name and the file containing the foreign javascript for the module
249 -> Bool -- ^ Generate a prefix comment?
251buildMakeActions outputDir filePathMap foreigns usePrefix =
252 MakeActions getInputTimestamp getOutputTimestamp readExterns codegen progress
255 getInputTimestamp :: ModuleName -> Make (Either RebuildPolicy (Maybe UTCTime))
256 getInputTimestamp mn = do
257 let path = fromMaybe (error "Module has no filename in 'make'") $ M.lookup mn filePathMap
258 e1 <- traverseEither getTimestamp path
259 fPath <- maybe (return Nothing) (getTimestamp . fst) $ M.lookup mn foreigns
260 return $ fmap (max fPath) e1
262 getOutputTimestamp :: ModuleName -> Make (Maybe UTCTime)
263 getOutputTimestamp mn = do
264 let filePath = runModuleName mn
265 jsFile = outputDir </> filePath </> "index.js"
266 externsFile = outputDir </> filePath </> "externs.purs"
267 min <$> getTimestamp jsFile <*> getTimestamp externsFile
269 readExterns :: ModuleName -> Make (FilePath, String)
271 let path = outputDir </> runModuleName mn </> "externs.purs"
272 (path, ) <$> readTextFile path
274 codegen :: CF.Module CF.Ann -> Environment -> Externs -> SupplyT Make ()
275 codegen m _ exts = do
276 let mn = CF.moduleName m
277 foreignInclude <- case mn `M.lookup` foreigns of
279 | not $ requiresForeign m -> do
280 tell $ errorMessage $ UnnecessaryFFIModule mn path
282 | otherwise -> return $ Just $ J.JSApp (J.JSVar "require") [J.JSStringLiteral "./foreign"]
283 Nothing | requiresForeign m -> throwError . errorMessage $ MissingFFIModule mn
284 | otherwise -> return Nothing
285 pjs <- prettyPrintJS <$> J.moduleToJs m foreignInclude
286 let filePath = runModuleName mn
287 jsFile = outputDir </> filePath </> "index.js"
288 externsFile = outputDir </> filePath </> "externs.purs"
289 foreignFile = outputDir </> filePath </> "foreign.js"
290 prefix = ["Generated by psc version " ++ showVersion Paths.version | usePrefix]
291 js = unlines $ map ("// " ++) prefix ++ [pjs]
293 writeTextFile jsFile js
294 maybe (return ()) (writeTextFile foreignFile . snd) $ mn `M.lookup` foreigns
295 writeTextFile externsFile exts
297 requiresForeign :: CF.Module a -> Bool
298 requiresForeign = not . null . CF.moduleForeign
300 getTimestamp :: FilePath -> Make (Maybe UTCTime)
301 getTimestamp path = makeIO (const (SimpleErrorWrapper $ CannotGetFileInfo path)) $ do
302 exists <- doesFileExist path
303 traverse (const $ getModificationTime path) $ guard exists
305 readTextFile :: FilePath -> Make String
306 readTextFile path = makeIO (const (SimpleErrorWrapper $ CannotReadFile path)) $ readFile path
308 writeTextFile :: FilePath -> String -> Make ()
309 writeTextFile path text = makeIO (const (SimpleErrorWrapper $ CannotWriteFile path)) $ do
313 mkdirp :: FilePath -> IO ()
314 mkdirp = createDirectoryIfMissing True . takeDirectory
316 progress :: ProgressMessage -> Make ()
317 progress = liftIO . putStrLn . renderProgressMessage
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list