cryptol-3.3.0: src/Cryptol/Project.hs
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Cryptol.Project
( Config(..)
, loadConfig
, ScanStatus(..)
, ChangeStatus(..)
, InvalidStatus(..)
, LoadProjectMode(..)
, Parsed
, loadProject
, depMap
) where
import Control.Monad (void)
import Data.Foldable
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Traversable
import System.Directory
import System.FilePath as FP
import Cryptol.ModuleSystem.Base as M
import Cryptol.ModuleSystem.Env
import Cryptol.ModuleSystem.Fingerprint
import Cryptol.ModuleSystem.Monad as M
import Cryptol.Project.Config
import Cryptol.Project.Cache
import Cryptol.Project.Monad
import Cryptol.Project.WildMatch
import qualified Cryptol.Parser.AST as P
import Cryptol.Parser.Position (Located(..))
import Control.Exception (try)
-- | Load a project.
-- Returns information about the modules that are part of the project.
loadProject :: LoadProjectMode -> Config -> M.ModuleM (Map CacheModulePath FullFingerprint, Map ModulePath ScanStatus, Map CacheModulePath (Maybe Bool))
loadProject mode cfg =
do (fps, statuses, out) <- runLoadM mode cfg (loadPatterns (modules cfg) >> getOldDocstringResults)
let deps = depMap [p | Scanned _ _ ps <- Map.elems statuses, p <- ps]
let untested (InMem{}) = False
untested (InFile f) =
case out of
Left _ -> True
Right m -> Map.lookup (CacheInFile f) m /= Just (Just True)
let needLoad = case mode of
RefreshMode -> [thing (P.mName m) | Scanned _ _ ps <- Map.elems statuses, (m, _) <- ps]
ModifiedMode -> [thing (P.mName m) | Scanned Changed _ ps <- Map.elems statuses, (m, _) <- ps]
UntestedMode -> [thing (P.mName m) | (k, Scanned ch _ ps) <- Map.assocs statuses, ch == Changed || untested k, (m, _) <- ps]
let order = loadOrder deps needLoad
let modDetails = Map.fromList [(thing (P.mName m), (m, mp, fp)) | (mp, Scanned _ fp ps) <- Map.assocs statuses, (m, _) <- ps]
let fingerprints = Map.fromList [(path, moduleFingerprint ffp) | (CacheInFile path, ffp) <- Map.assocs fps]
for_ order \name ->
let (m, path, fp) = modDetails Map.! name in
-- XXX: catch modules that don't load?
doLoadModule
True {- eval -}
False {- quiet -}
(FromModule name)
path
(moduleFingerprint fp)
fingerprints
m
(Map.findWithDefault mempty name deps)
let oldResults =
case out of
Left{} -> mempty
Right x -> x
pure (fps, statuses, oldResults)
--------------------------------------------------------------------------------
loadPatterns :: [String] -> LoadM any ()
loadPatterns patterns =
do mb <- tryLoadM (doIO (map flatten <$> listDirectoryRecursive []))
case mb of
Left{} -> pure ()
Right files ->
do let files' = filter (\x -> any (`wildmatch` x) patterns) files
for_ files' scanPath
-- | Process all .cry files in the given path.
scanPath :: FilePath -> LoadM any ()
scanPath path =
tryLoadM (doIO (doesDirectoryExist path)) >>=
\case
Left {} -> pure ()
Right True ->
tryLoadM (doIO (listDirectory path)) >>=
\case
Left {} -> pure ()
Right entries ->
for_ entries \entry -> scanPath (path FP.</> entry)
Right False ->
-- XXX: This should probably handle other extenions
-- (literate Cryptol)
case takeExtension path of
".cry" -> void (tryLoadM (scanFromPath path))
-- XXX: failure: file disappeared.
_ -> pure ()
-- | Process a particular file path.
-- Fails if we can't find the module at this path.
scanFromPath :: FilePath -> LoadM Err ScanStatus
scanFromPath fpath =
liftCallback (withPrependedSearchPath [takeDirectory fpath])
do foundFPath <- doModule (M.findFile fpath)
mpath <- doIO (InFile <$> canonicalizePath foundFPath)
scan mpath
-- | Process the given module, and return information about what happened.
-- Also, saves the status of the module path.
scan :: ModulePath -> LoadM any ScanStatus
scan mpath =
tryIt $
do mbStat <- getStatus mpath
case mbStat of
Just status -> pure status
Nothing ->
do (newFp,parsed) <- doParse mpath
mbOldFP <- getCachedFingerprint mpath
let needLoad = mbOldFP /= Just newFp
let deps = [ dep
| (_,ds) <- parsed
, dep@(_,otherPath) <- ds
, mpath /= otherPath
]
mb <- checkDeps False deps
case mb of
Left (a,b) -> addScanned mpath (Invalid (InvalidDep a b))
Right depChanges ->
do let ch = if needLoad || depChanges
then Changed else Unchanged
addScanned mpath (Scanned ch newFp parsed)
where
tryIt m =
do mb <- tryLoadM m
case mb of
Left err -> addScanned mpath (Invalid (InvalidModule err))
Right a -> pure a
-- | Parse a module.
doParse :: ModulePath -> LoadM Err (FullFingerprint, Parsed)
doParse mpath =
do lab <- getModulePathLabel mpath
lPutStrLn ("Scanning " ++ lab)
(parsed, newFp) <-
doModule
do (fi, parsed) <- parseWithDeps mpath
foreignFps <- getForeignFps (fiForeignDeps fi)
pure ( parsed
, FullFingerprint
{ moduleFingerprint = fiFingerprint fi
, includeFingerprints = fiIncludeDeps fi
, foreignFingerprints = foreignFps
}
)
addFingerprint mpath newFp
ps <- forM parsed \(m,ds) ->
do paths <- mapM findModule' ds
pure (m, zip ds paths)
pure (newFp, ps)
-- | Get the fingerprints for external libraries.
getForeignFps :: Map FilePath Bool -> ModuleM (Set Fingerprint)
getForeignFps fsrcPaths =
Set.fromList <$>
let foundFiles = Map.keys (Map.filter id fsrcPaths) in
for foundFiles \fsrcPath ->
M.io (fingerprintFile fsrcPath) >>=
\case
Left ioe -> otherIOError fsrcPath ioe
Right fp -> pure fp
-- | Scan the dependencies of a module.
checkDeps ::
Bool {- ^ Should we load the dependencies -} ->
[(ImportSource,ModulePath)] {- ^ The dependencies -} ->
LoadM err (Either (ImportSource,ModulePath) Bool)
-- ^ Returns `Left bad_dep` if one of the dependencies fails to load.
-- Returns `Right changes` if all dependencies were validated correctly.
-- The boolean flag `changes` indicates if any of the dependencies contain
-- changes and so we should also load the main module.
checkDeps shouldLoad ds =
case ds of
[] -> pure (Right shouldLoad)
(imp, mpath) : more ->
do status <- scan mpath
case status of
Invalid {} -> pure (Left (imp,mpath))
Scanned ch _ _ ->
case ch of
Changed -> checkDeps True more
Unchanged -> checkDeps shouldLoad more
depMap :: Parsed -> Map P.ModName (Set P.ModName)
depMap xs = Map.fromList [(thing (P.mName k), Set.fromList [importedModule i | (i, _) <- v]) | (k, v) <- xs]
loadOrder :: Map P.ModName (Set P.ModName) -> [P.ModName] -> [P.ModName]
loadOrder deps roots0 = snd (go Set.empty roots0) []
where
go seen mms =
case mms of
[] -> (seen, id)
m : ms
| Set.member m seen -> go seen ms
| (seen1, out1) <- go (Set.insert m seen) (Set.toList (Map.findWithDefault mempty m deps))
, (seen2, out2) <- go seen1 ms
-> (seen2, out1 . (m:) . out2)
-- Similar to listDirectory except directories are expanded
-- when possible instead of returned in the list
listDirectoryRecursive :: [FilePath] -> IO [[FilePath]]
listDirectoryRecursive d =
do localEntries <- listDirectory (flatten d)
concat <$> for localEntries \x ->
do let x' = d ++ [x]
mb <- try (listDirectoryRecursive x')
case mb of
Left (_ :: IOError) -> pure [x']
Right xs -> pure xs
flatten :: [String] -> String
flatten [] = "."
flatten xs = foldr1 (\x y -> x ++ "/" ++ y) xs