-- | Aggregate all types and data used through shaker module Shaker.Type where import Data.Monoid import Data.List import Distribution.Simple.LocalBuildInfo import Distribution.Simple.PackageIndex import DynFlags hiding (OneShot) import qualified Data.Map as M import Control.Monad.Reader import System.Time import Control.Concurrent.MVar import Control.Concurrent -- | Environnement containing the project configuration. -- It is generated at startup and won't change type Shaker = ReaderT ShakerInput type ShakerR = Reader ShakerInput type ThreadIdList = MVar [ThreadId] type Token = MVar Int -- | MVar used to store currentFiles listed type CurrentFiles = MVar [FileInfo] -- | MVar used to store modifiedFiles since the last check type MvModifiedFiles = MVar [FileInfo] -- | MVar used to pass action to the fileListenInfoDirectory scanner type Job = MVar [FileListenInfo] -- | Environnement for the project compilation -- This environnement can change depending on the compile -- action called type CompileM = Reader CompileInput data ConductorData = ConductorData ListenState ([FileInfo] -> IO () ) -- | Agregate all information of listener data ListenState = ListenState { currentFiles :: CurrentFiles -- ^ Files found in the last check ,mvModifiedFiles :: MvModifiedFiles -- ^ Differences between last and before last check ,threadIds :: [ThreadId] -- ^ List of all forks id initialized } -- | Duration define the life span of an action data Duration = OneShot -- ^Execute the action and give back control | Continuous-- ^Execute the action when a source file modification is done until it is stopped deriving (Show,Eq) -- | Action represents the differents action with arguments data Action = Action ShakerAction | ActionWithArg ShakerAction [String] deriving (Show,Eq,Ord) -- | The input mvar is used to push the parsed command type InputCommand = MVar (Maybe Command) data InputState = InputState { shakerInputStateCommand :: InputCommand, shakerInputStateToken :: Token -- ^ Token is used to manage the token between action executor and command-line listener } -- | ShakerAction represents the differents actions realisable by shaker data ShakerAction = Compile -- ^ Compile sources with ghc | FullCompile -- ^ Compile all hs sources with ghc | TestFramework -- ^ Execute both quickcheck and hunit using test framework | ModuleTestFramework -- ^ Execute both quickcheck and hunit using test framework with module filtering | IntelligentTestFramework -- ^ Execute both quickcheck and hunit using test framework on recompiled modules | IntelligentModuleTestFramework -- ^ Execute both quickcheck and hunit using test framework on recompiled modules | InvalidAction -- ^ Display an error when invalid action is inputed | Help -- ^ Display the help | Execute -- ^ Execute a command | Empty -- ^ Nothing to execute | Quit -- ^ Exit shaker | Clean -- ^ Delete generated deriving (Show,Eq,Ord) -- | Command agregate a duration with an action data Command = Command Duration [Action] deriving (Show,Eq) data Verbosity = Silent | Debug -- | Represents the global configuration of the system data ShakerInput = ShakerInput { shakerCompileInputs :: [CompileInput] ,shakerListenerInput :: ListenerInput ,shakerPluginMap :: PluginMap ,shakerCommandMap :: CommandMap ,shakerArgument :: [String] ,shakerModifiedInfoFiles :: [FileInfo] ,shakerThreadData :: ThreadData ,shakerInputState :: InputState ,shakerLocalBuildInfo :: LocalBuildInfo ,shakerPackageIndex :: PackageIndex ,shakerModuleData :: [ModuleData] ,shakerVerbosity :: Verbosity } data ThreadData = ThreadData { threadDataListenToken :: Token ,threadDataQuitToken :: Token ,threadDataListenList :: ThreadIdList ,threadDataQuitList :: ThreadIdList } getListenThreadList :: ShakerInput -> ThreadIdList getListenThreadList = threadDataListenList . shakerThreadData -- | Configuration flags to pass to the ghc compiler data CompileInput = CompileInput{ compileInputSourceDirs :: [String] -- ^ Source fileListenInfoDirectory of haskell files ,compileInputBuildDirectory :: String -- ^ Destination of .o and .hi files ,compileInputDynFlags :: DynFlags->DynFlags -- ^ A transform fonction wich will takes the DynFlags of the current ghc session and change some values ,compileInputCommandLineFlags :: [String] -- ^ The command line to pass options to pass to the ghc compiler ,compileInputTargetFiles :: [String] -- ^ List of files or list of modules to compile } -- | Default compilation shakerArgument. -- Wall is activated by default instance Monoid CompileInput where mempty = CompileInput { compileInputSourceDirs = ["."] ,compileInputBuildDirectory = "dist/shakerTarget" ,compileInputDynFlags = defaultCompileFlags ,compileInputCommandLineFlags = ["-Wall"] ,compileInputTargetFiles = [] } mappend cpIn1 cpIn2 = CompileInput { compileInputSourceDirs = nub $ compileInputSourceDirs cpIn1 `mappend` compileInputSourceDirs cpIn2 ,compileInputBuildDirectory = compileInputBuildDirectory cpIn1 ,compileInputDynFlags = compileInputDynFlags cpIn1 . compileInputDynFlags cpIn2 ,compileInputCommandLineFlags = nub $ compileInputCommandLineFlags cpIn1 `mappend` compileInputCommandLineFlags cpIn2 ,compileInputTargetFiles = nub $ compileInputTargetFiles cpIn1 `mappend` compileInputTargetFiles cpIn2 } instance Show CompileInput where show (CompileInput src _ _ commandLine target) = concat ["CompileInput |source : ",show src," |cmdLine : ",show commandLine," |targetfiles : ", show target] -- | Configuration of the continuous listener data ListenerInput = ListenerInput { listenerInputFiles :: [FileListenInfo] -- ^ The files to listen ,listenerInputDelay :: Int -- ^ Delay beetween 2 check in microsecond } -- | The default Listener configuration -- Listened sources are all haskell sources in . -- The default listenerInputDelay is 2 sec instance Monoid ListenerInput where mempty = ListenerInput { listenerInputFiles = mempty ,listenerInputDelay = 1000000 } mappend l1 l2 = ListenerInput { listenerInputFiles = listenerInputFiles l1 `mappend` listenerInputFiles l2 ,listenerInputDelay = listenerInputDelay l1 } -- | Represents fileListenInfoDirectory to listen data FileListenInfo = FileListenInfo{ fileListenInfoDir :: FilePath -- ^ location of the listened fileListenInfoDirectory ,fileListenInfoIgnore :: [String] -- ^ fileListenInfoIgnore patterns ,fileListenInfoInclude :: [String] -- ^fileListenInfoInclude patterns } deriving (Show,Eq) instance Monoid FileListenInfo where mempty = FileListenInfo "." defaultExclude defaultHaskellPatterns mappend f1 f2 = FileListenInfo { fileListenInfoDir = fileListenInfoDir f1 ,fileListenInfoIgnore = nub $ fileListenInfoIgnore f1 `mappend` fileListenInfoIgnore f2 ,fileListenInfoInclude = nub $ fileListenInfoInclude f1 `mappend` fileListenInfoInclude f2 } -- |Agregate a FilePath with its modification time data FileInfo = FileInfo { fileInfoFilePath :: FilePath ,fileInfoClockTime:: ClockTime } deriving (Show,Eq) data PackageData = PackageData { packageDataMapImportToModules :: MapImportToModules ,packageDataListProjectModules :: [String] } data ModuleData = ModuleData { moduleDataName :: String ,moduleDataFileName :: String ,moduleDataHasMain :: Bool ,moduleDataProperties :: [String] ,moduleDataAssertions :: [String] ,moduleDataTestCase :: [String] } | GhcModuleData { ghcModuleDataName :: String ,ghcModuleDataAssertions :: [String] ,ghcModuleDataTestCase :: [String] } deriving (Read, Show) instance Monoid ModuleData where mempty = ModuleData "" "" False [] [] [] mappend fstModData@(GhcModuleData _ _ _) sndModData@(ModuleData _ _ _ _ _ _) = sndModData `mappend` fstModData mappend fstModData@(ModuleData _ _ _ fstProps fstAsserts fstTestCases) sndModData = fstModData { moduleDataProperties = nub $ fstProps ++ sndProps ,moduleDataAssertions = nub $ fstAsserts ++ sndAsserts ,moduleDataTestCase = nub $ fstTestCases ++ sndTestCases } where (sndProps, sndAsserts, sndTestCases) = getModuleDataTests sndModData mappend fstModData@(GhcModuleData _ fstTestCases fstAsserts) sndModData = fstModData { ghcModuleDataTestCase = nub $ fstTestCases ++ sndTestCases ,ghcModuleDataAssertions = nub $ fstAsserts ++ sndAsserts } where (_, sndAsserts, sndTestCases) = getModuleDataTests sndModData instance Eq ModuleData where mod1 == mod2 = getModuleDataName mod1 == getModuleDataName mod2 getModuleDataTests :: ModuleData -> ([String], [String], [String]) getModuleDataTests (ModuleData _ _ _ prps asserts tests)= (prps, asserts, tests) getModuleDataTests (GhcModuleData _ asserts tests)= ([], asserts, tests) getModuleDataName :: ModuleData -> String getModuleDataName (ModuleData name _ _ _ _ _) = name getModuleDataName (GhcModuleData name _ _) = name type MapImportToModules = M.Map String [String] -- | Represents the mapping beetween an action and the function to execute type PluginMap = M.Map ShakerAction Plugin -- | Represents the mapping between the command-line input and the action type CommandMap = M.Map String ShakerAction -- | Represents an action of shaker type Plugin = Shaker IO() -- * Default data -- | default dynamics flags -- the sources are expected to be in src as described in -- the result of compilation (.o and .hi) are placed in the dist/shakerTarget -- there is no main linkage by default to allow faster compilation feedback defaultCompileFlags :: (DynFlags -> DynFlags) defaultCompileFlags a = a { verbosity = 1 ,ghcLink = NoLink } -- | Default haskell file pattern : *.hs defaultHaskellPatterns :: [String] defaultHaskellPatterns = [".*\\.hs$", ".*\\.lhs"] -- | Default exclude pattern : Setup.hs defaultExclude :: [String] defaultExclude = [".*Setup\\.lhs$",".*Setup\\.hs$", ".*/\\."] exitCommand :: Command exitCommand = Command OneShot [Action Quit] emptyCommand :: Command emptyCommand = Command OneShot [Action Empty] listTestLibs :: [String] listTestLibs = ["QuickCheck","HUnit","test-framework-hunit","test-framework","test-framework-quickcheck2","shaker-test-provider"] moduleDataExtension :: String moduleDataExtension = ".mdata" defaultDistDir :: String defaultDistDir = "dist/shakerTarget"