diff options
Diffstat (limited to 'src/Language/PureScript/Make/Actions.hs')
-rw-r--r-- | src/Language/PureScript/Make/Actions.hs | 77 |
1 files changed, 43 insertions, 34 deletions
diff --git a/src/Language/PureScript/Make/Actions.hs b/src/Language/PureScript/Make/Actions.hs index 8b54765..a8f6318 100644 --- a/src/Language/PureScript/Make/Actions.hs +++ b/src/Language/PureScript/Make/Actions.hs @@ -16,7 +16,7 @@ import Control.Monad.Reader (asks) import Control.Monad.Supply import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Writer.Class (MonadWriter(..)) -import Data.Aeson (encode) +import qualified Data.Aeson as Aeson import Data.Bifunctor (bimap) import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy as LB @@ -25,7 +25,7 @@ import Data.Either (partitionEithers) import Data.Foldable (for_, minimum) import qualified Data.List.NonEmpty as NEL import qualified Data.Map as M -import Data.Maybe (fromMaybe) +import Data.Maybe (fromMaybe, maybeToList) import qualified Data.Set as S import qualified Data.Text as T import qualified Data.Text.Encoding as TE @@ -44,6 +44,7 @@ import qualified Language.PureScript.CST as CST import qualified Language.PureScript.Docs.Types as Docs import Language.PureScript.Errors import Language.PureScript.Make.Monad +import Language.PureScript.Make.Cache import Language.PureScript.Names import Language.PureScript.Names (runModuleName, ModuleName) import Language.PureScript.Options hiding (codegenTargets) @@ -51,8 +52,8 @@ import Language.PureScript.Pretty.Common (SMap(..)) import qualified Paths_purescript as Paths import SourceMap import SourceMap.Types -import System.Directory (doesFileExist, getModificationTime, createDirectoryIfMissing, getCurrentDirectory) -import System.FilePath ((</>), takeDirectory, makeRelative, splitPath, normalise) +import System.Directory (getCurrentDirectory) +import System.FilePath ((</>), makeRelative, splitPath, normalise) -- | Determines when to rebuild a module data RebuildPolicy @@ -83,10 +84,10 @@ renderProgressMessage (CompilingModule mn) = "Compiling " ++ T.unpack (runModule -- -- * The details of how files are read/written etc. data MakeActions m = MakeActions - { getInputTimestamp :: ModuleName -> m (Either RebuildPolicy (Maybe UTCTime)) - -- ^ Get the timestamp for the input file(s) for a module. If there are multiple - -- files (@.purs@ and foreign files, for example) the timestamp should be for - -- the most recently modified file. + { getInputTimestampsAndHashes :: ModuleName -> m (Either RebuildPolicy (M.Map FilePath (UTCTime, m ContentHash))) + -- ^ Get the timestamps and content hashes for the input files for a module. + -- The content hash is returned as a monadic action so that the file does not + -- have to be read if it's not necessary. , getOutputTimestamp :: ModuleName -> m (Maybe UTCTime) -- ^ Get the timestamp for the output files for a module. This should be the -- timestamp for the oldest modified file, or 'Nothing' if any of the required @@ -100,6 +101,12 @@ data MakeActions m = MakeActions -- ^ Check ffi and print it in the output directory. , progress :: ProgressMessage -> m () -- ^ Respond to a progress update. + , readCacheDb :: m CacheDb + -- ^ Read the cache database (which contains timestamps and hashes for input + -- files) from some external source, e.g. a file on disk. + , writeCacheDb :: CacheDb -> m () + -- ^ Write the given cache database to some external source (e.g. a file on + -- disk). } -- | A set of make actions that read and write modules from the given directory. @@ -114,15 +121,24 @@ buildMakeActions -- ^ Generate a prefix comment? -> MakeActions Make buildMakeActions outputDir filePathMap foreigns usePrefix = - MakeActions getInputTimestamp getOutputTimestamp readExterns codegen ffiCodegen progress + MakeActions getInputTimestampsAndHashes getOutputTimestamp readExterns codegen ffiCodegen progress readCacheDb writeCacheDb where - getInputTimestamp :: ModuleName -> Make (Either RebuildPolicy (Maybe UTCTime)) - getInputTimestamp mn = do + getInputTimestampsAndHashes + :: ModuleName + -> Make (Either RebuildPolicy (M.Map FilePath (UTCTime, Make ContentHash))) + getInputTimestampsAndHashes mn = do let path = fromMaybe (internalError "Module has no filename in 'make'") $ M.lookup mn filePathMap - e1 <- traverse getTimestamp path - fPath <- maybe (return Nothing) getTimestamp $ M.lookup mn foreigns - return $ fmap (max fPath) e1 + case path of + Left policy -> + return (Left policy) + Right filePath -> do + let inputPaths = filePath : maybeToList (M.lookup mn foreigns) + getInfo fp = do + ts <- getTimestamp fp + return (ts, hash <$> readTextFile fp) + pathsWithInfo <- traverse (\fp -> (fp,) <$> getInfo fp) inputPaths + return $ Right $ M.fromList pathsWithInfo outputFilename :: ModuleName -> String -> FilePath outputFilename mn fn = @@ -140,7 +156,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = getOutputTimestamp mn = do codegenTargets <- asks optionsCodegenTargets let outputPaths = [outputFilename mn "externs.json"] <> fmap (targetFilename mn) (S.toList codegenTargets) - timestamps <- traverse getTimestamp outputPaths + timestamps <- traverse getTimestampMaybe outputPaths pure $ fmap minimum . NEL.nonEmpty =<< sequence timestamps readExterns :: ModuleName -> Make (FilePath, Externs) @@ -156,7 +172,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = when (S.member CoreFn codegenTargets) $ do let coreFnFile = targetFilename mn CoreFn json = CFJ.moduleToJSON Paths.version m - lift $ writeTextFile coreFnFile (encode json) + lift $ writeTextFile coreFnFile (Aeson.encode json) when (S.member JS codegenTargets) $ do foreignInclude <- case mn `M.lookup` foreigns of Just _ @@ -167,7 +183,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = Nothing | requiresForeign m -> throwError . errorMessage' (CF.moduleSourceSpan m) $ MissingFFIModule mn | otherwise -> return Nothing rawJs <- J.moduleToJs m foreignInclude - dir <- lift $ makeIO (const (ErrorMessage [] $ CannotGetFileInfo ".")) getCurrentDirectory + dir <- lift $ makeIO "get the current directory" getCurrentDirectory let sourceMaps = S.member JSSourceMap codegenTargets (pjs, mappings) = if sourceMaps then prettyPrintJSWithSourceMaps rawJs else (prettyPrintJS rawJs, []) jsFile = targetFilename mn JS @@ -179,7 +195,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = writeTextFile jsFile (B.fromStrict $ TE.encodeUtf8 $ js <> mapRef) when sourceMaps $ genSourceMap dir mapFile (length prefix) mappings when (S.member Docs codegenTargets) $ do - lift $ writeTextFile (outputFilename mn "docs.json") (encode docs) + lift $ writeTextFile (outputFilename mn "docs.json") (Aeson.encode docs) ffiCodegen :: CF.Module CF.Ann -> Make () ffiCodegen m = do @@ -212,7 +228,7 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = }) mappings } let mapping = generate rawMapping - writeTextFile mapFile (encode mapping) + writeTextFile mapFile (Aeson.encode mapping) where add :: Int -> Int -> SourcePos -> SourcePos add n m (SourcePos n' m') = SourcePos (n+n') (m+m') @@ -224,24 +240,17 @@ buildMakeActions outputDir filePathMap foreigns usePrefix = requiresForeign :: CF.Module a -> Bool requiresForeign = not . null . CF.moduleForeign - getTimestamp :: FilePath -> Make (Maybe UTCTime) - getTimestamp path = makeIO (const (ErrorMessage [] $ CannotGetFileInfo path)) $ do - exists <- doesFileExist path - if exists - then Just <$> getModificationTime path - else pure Nothing - - writeTextFile :: FilePath -> B.ByteString -> Make () - writeTextFile path text = makeIO (const (ErrorMessage [] $ CannotWriteFile path)) $ do - mkdirp path - B.writeFile path text - where - mkdirp :: FilePath -> IO () - mkdirp = createDirectoryIfMissing True . takeDirectory - progress :: ProgressMessage -> Make () progress = liftIO . putStrLn . renderProgressMessage + readCacheDb :: Make CacheDb + readCacheDb = fmap (fromMaybe mempty) $ readJSONFile cacheDbFile + + writeCacheDb :: CacheDb -> Make () + writeCacheDb = writeJSONFile cacheDbFile + + cacheDbFile = outputDir </> "cache-db.json" + -- | Check that the declarations in a given PureScript module match with those -- in its corresponding foreign module. checkForeignDecls :: CF.Module ann -> FilePath -> Make () |