CurryInfo: cass-4.1.0 / CASS.Dependencies

classes:

              
documentation:
-----------------------------------------------------------------------
--- Operations to handle dependencies of analysis files.
---
--- @author Heiko Hoffmann, Michael Hanus
--- @version April 2021
-----------------------------------------------------------------------
name:
CASS.Dependencies
operations:
getModulesToAnalyze reduceDependencies
sourcecode:
module CASS.Dependencies(getModulesToAnalyze,reduceDependencies) where

import Control.Monad     ( when )
import FlatCurry.Types
import FlatCurry.Goodies ( progImports )
import System.Directory  ( doesFileExist, getModificationTime, removeFile )
import Data.Maybe        ( fromMaybe )
import Data.List         ( delete )
import Data.Time(ClockTime)

import RW.Base

import Analysis.Logging   ( DLevel, debugMessage )
import Analysis.Types
import Analysis.ProgInfo
import Analysis.Files
import CASS.Configuration ( CConfig, debugLevel, withPrelude, ccOptions )
import CASS.Options

-----------------------------------------------------------------------
--- Compute the modules and their imports which must be analyzed
--- w.r.t. a given analysis and main module.
--- If the first argument is true, then the analysis is enforced
--- (even if analysis information exists).
getModulesToAnalyze :: (Eq a, Read a, ReadWrite a) => CConfig -> Bool
                    -> Analysis a -> String -> IO [(String,[String])]
getModulesToAnalyze cconfig enforce analysis moduleName = do
  checkPrivateProgInfo cconfig analysis moduleName
  if isSimpleAnalysis analysis
    then do
      ananewer <- isAnalysisFileNewer ananame moduleName
      return (if ananewer && not enforce then [] else [(moduleName,[])])
    else do
      valid <- isAnalysisValid ananame moduleName
      if valid && not enforce
        then do
          debugMessage dl 3 $
            "Analysis file for '" ++ moduleName ++ "' up-to-date"
          return []
        else do
          moduleList <- getDependencyList cconfig [moduleName] []
          debugMessage dl 3 $ "Complete module list: "++ show moduleList
          let impmods = map fst moduleList
          storeImportModuleList dl moduleName impmods
          sourceTimeList <- mapM getSourceFileTime        impmods
          fcyTimeList    <- mapM getFlatCurryFileTime     impmods
          anaTimeList    <- mapM (getAnaFileTime ananame) impmods
          let (modulesToDo,modulesUpToDate) =
                  findModulesToAnalyze moduleList anaTimeList sourceTimeList
                                       fcyTimeList ([],[])
          --debugMessage dl 3 ("Modules up-to-date: "++ show modulesUpToDate)
          let modulesToAnalyze =
                if enforce
                  then moduleList
                  else
                    if withPrelude cconfig
                      then reduceDependencies modulesToDo modulesUpToDate
                      else let reduced = reduceDependencies modulesToDo
                                          (modulesUpToDate ++ ["Prelude"])
                          in case reduced of
                               (("Prelude",_):remaining) -> remaining
                               _                         -> reduced
          debugMessage dl 3 ("Modules to analyze: " ++ show modulesToAnalyze)
          return modulesToAnalyze
 where
   dl = debugLevel cconfig
   ananame = analysisName analysis

---- The empty program information for a given analysis.
emptyAnalysisInfo:: Analysis a -> ProgInfo a
emptyAnalysisInfo _ = emptyProgInfo

-- Checks whether the private `ProgInfo` is empty if option `--all` is set.
-- If this is the case, the analysis files will be deleted so that the
-- module will be re-analyzed. This is necessary if the analysis files
-- have been created from the CurryInfo system (which contains only information
-- about public entities).
checkPrivateProgInfo :: (Eq a, Read a, ReadWrite a) => CConfig
                     -> Analysis a -> String -> IO ()
checkPrivateProgInfo cconfig analysis modname
  | not (optAll (ccOptions cconfig)) = return ()
  | otherwise
  = do
  privfname <- getAnalysisPrivateFile modname ananame
  privexists <- doesFileExist privfname
  if privexists
    then do
      privinfo <- readAnalysisPrivateFile dl privfname
      when (equalProgInfo (emptyAnalysisInfo analysis) privinfo) $ do
        removeFile privfname
        removePubInfo
    else removePubInfo
 where
  dl = debugLevel cconfig
  ananame = analysisName analysis
  removePubInfo = do
    pubfname  <- getAnalysisPublicFile modname ananame
    debugMessage dl 3 $ "Removing public analysis file '" ++ pubfname ++ "'..."
    pubexists <- doesFileExist pubfname
    when pubexists $ removeFile pubfname

-- Checks whether the analysis file is up-to-date.
-- Returns True if the analysis file is newer than the source file
-- and the FlatCurry file (if is exists).
isAnalysisFileNewer :: String -> String -> IO Bool
isAnalysisFileNewer ananame modname = do
  atime <- getAnaFileTime ananame modname
  stime <- getSourceFileTime modname
  ftime <- getFlatCurryFileTime modname
  return (isAnalysisFileTimeNewer (snd atime) (Just (snd stime)) (snd ftime))

-- Is the analysis file time up-to-date w.r.t. the file times of
-- the source file and the FlatCurry file?
-- Returns True if the analysis file is newer than the source file
-- and the FlatCurry file (if is exists).
isAnalysisFileTimeNewer :: Maybe ClockTime -> Maybe ClockTime -> Maybe ClockTime
                        -> Bool
isAnalysisFileTimeNewer anatime srctime fcytime =
  anatime >= srctime && anatime >= fcytime

-- Read current import dependencies and checks whether the current analysis
-- file is valid, i.e., it is newer than the source and FlatCurry files
-- of all (directly and indirectly) imported modules.
isAnalysisValid :: String -> String -> IO Bool
isAnalysisValid ananame modname =
  getImportModuleListFile modname >>= maybe
    (return False)
    (\importListFile -> do
      itime <- getModificationTime importListFile
      stime <- getSourceFileTime modname >>= return . snd
      if itime>=stime
       then do
        implist <- readFile importListFile >>= return . read
        sourceTimeList <- mapM getSourceFileTime        implist
        fcyTimeList    <- mapM getFlatCurryFileTime     implist
        anaTimeList    <- mapM (getAnaFileTime ananame) implist
        return (all (\ (x,y,z) -> isAnalysisFileTimeNewer x y z)
                    (zip3 (map snd anaTimeList)
                          (map (Just . snd) sourceTimeList)
                          (map snd fcyTimeList)))
       else return False)


--- Gets the list of all modules required by the first module.
--- The result is sorted according to their dependencies
--- (Prelude first, main module last)
getDependencyList :: CConfig -> [String] -> [(String,[String])]
                  -> IO [(String,[String])]
getDependencyList _  []           moddeps = return moddeps
getDependencyList cc (mname:mods) moddeps =
  maybe (do --debugMessage 3 ("Getting imports of "++ mname)
            --debugMessage 3 ("Still to do: "++ show mods)
            imports <- getImports dl mname
            getDependencyList cc (addNewMods mods imports)
                              ((mname,imports):moddeps))
        (\ (newmoddeps,imps) ->
              getDependencyList cc (addNewMods mods imps) newmoddeps)
        (lookupAndReorder mname [] moddeps)
 where dl = debugLevel cc

-- add new modules if they are not already there:
addNewMods :: [String] -> [String] -> [String]
addNewMods oldmods newmods = oldmods ++ filter (`notElem` oldmods) newmods

lookupAndReorder :: String -> [(String, [String])] -> [(String, [String])]
                 -> Maybe ([(String, [String])], [String])
lookupAndReorder _ _ [] = Nothing
lookupAndReorder mname list1 ((amod,amodimports):rest)
  | mname==amod = Just ((amod,amodimports):reverse list1++rest, amodimports)
  | otherwise   = lookupAndReorder mname ((amod,amodimports):list1) rest

-- get timestamp of analysis file
getAnaFileTime :: String -> String -> IO (String,Maybe ClockTime)
getAnaFileTime anaName moduleName = do
  fileName <- getAnalysisPublicFile moduleName anaName
  fileExists <- doesFileExist fileName
  if fileExists
    then do time <- getModificationTime fileName
            return (moduleName,Just time)
    else return (moduleName,Nothing)


-- check if analysis result of a module can be loaded or needs to be
-- newly analyzed
findModulesToAnalyze :: [(String,[String])]
                     -> [(String,Maybe ClockTime)]
                     -> [(String,ClockTime)]
                     -> [(String,Maybe ClockTime)]
                     -> ([(String,[String])],[String])
                     -> ([(String,[String])],[String])
findModulesToAnalyze [] _ _ _ (modulesToDo,modulesUpToDate) =
  (reverse modulesToDo, modulesUpToDate)
findModulesToAnalyze (m@(mod,imports):ms)
                     anaTimeList sourceTimeList fcyTimeList
                     (modulesToDo,modulesUpToDate) =
  case (lookup mod anaTimeList) of
    Just Nothing    -> findModulesToAnalyze ms anaTimeList sourceTimeList
                                            fcyTimeList
                                            ((m:modulesToDo),modulesUpToDate)
    Just (Just time) ->
      if checkTime mod time imports anaTimeList sourceTimeList fcyTimeList
                   modulesToDo
      then findModulesToAnalyze ms anaTimeList sourceTimeList fcyTimeList
                                (modulesToDo,(mod:modulesUpToDate))
      else findModulesToAnalyze ms anaTimeList sourceTimeList fcyTimeList
                                ((m:modulesToDo),modulesUpToDate)
    Nothing -> error
                 "Internal error in AnalysisDependencies.findModulesToAnalyz"


-- function to check if result file is up-to-date
-- compares timestamp of analysis result file with module source/FlatCurry file
-- and with timpestamp of result files of all imported modules
checkTime :: String -> ClockTime -> [String] -> [(String,Maybe ClockTime)]
          -> [(String,ClockTime)] -> [(String,Maybe ClockTime)]
          -> [(String,[String])] -> Bool
checkTime mod time1 [] _ sourceTimeList fcyTimeList _ =
  isAnalysisFileTimeNewer (Just time1) (lookup mod sourceTimeList)
                          (fromMaybe Nothing (lookup mod fcyTimeList))
checkTime mod time1 (impt:impts) anaTimeList sourceTimeList fcyTimeList
          resultList =
  (lookup impt resultList) == Nothing
  && (Just time1) >= (fromMaybe Nothing (lookup impt anaTimeList))
  && checkTime mod time1 impts anaTimeList sourceTimeList fcyTimeList resultList

-----------------------------------------------------------------------
-- Remove the module analysis dependencies (first argument) w.r.t.
-- a list of modules that are already analyzed (second argument).
reduceDependencies :: [(String,[String])] -> [String] -> [(String,[String])]
reduceDependencies modulesToDo [] = modulesToDo
reduceDependencies modulesToDo (mod:mods) =
  let modulesToDo2 = map (\ (m,list) -> (m,(delete mod list))) modulesToDo
   in reduceDependencies modulesToDo2 mods
types:

              
unsafe:
safe