shaker 0.1 → 0.2
raw patch · 14 files changed
+160/−236 lines, 14 files
Files
- README +7/−0
- README.textile +0/−78
- TODO +1/−1
- shaker.cabal +5/−4
- src/Shaker/Action/Compile.hs +18/−45
- src/Shaker/Action/Quickcheck.hs +62/−28
- src/Shaker/Action/Standard.hs +1/−1
- src/Shaker/Cabal/CabalInfo.hs +1/−4
- src/Shaker/Config.hs +0/−34
- src/Shaker/Io.hs +7/−21
- src/Shaker/Type.hs +54/−3
- testsuite/tests/Shaker/Cabal/CabalInfoTest.hs +4/−15
- testsuite/tests/Shaker/ConductorTest.hs +0/−1
- testsuite/tests/Shaker/Properties.hs +0/−1
+ README view
@@ -0,0 +1,7 @@+Shaker is a build tool which allow to compile and launch test on haskell project with some features like continuous action similar to SBT.++The package is uploaded in hackage - http://hackage.haskell.org/package/shaker++To install shaker, either use cabal install - cabal update && cabal install shaker +Or build shaker from sources - runhaskell Setup.hs configure && runhaskell Setup.hs install+
− README.textile
@@ -1,78 +0,0 @@-Shaker is a build tool which allow to compile and launch test on haskell project with some features like continuous action similar to SBT.-For now, all configuration are made via cabal. Shaker will read cabal configuration to discover source directories, compilation options and targets to compile.--h2. Installation --You can either install shaker using cabal-install or by building it yourself.--1. Using cabal-install--<pre>-cabal install shaker -</pre>--2. Building from sources--<pre>-runhaskell Setup.hs configure && runhaskell Setup.hs build && runhaskell Setup.hs install-</pre>--h2. Usage--The cabal configuration file should be generated beforehand. -<pre>-runhaskell Setup.hs configure-</pre>-If you change your cabal configuration, you need to recreate the configuration file. --h3. Execution-In the root of your project, launch-<pre>-shaker-</pre>-An interactive prompt will allow you to execute differents action.--h3. Action execution--h4. Simple Execution-An action can be launched normally, by entering the action name -<pre>-Compile-</pre>-It will compile the projet and give the control back.--h4. Multiple action execution-You can specify multiple action to execute.-<pre>-Clean Compile-</pre>-It will clean all .o and .hi files before Compiling the project.--h4. Continuous action-A continuous action will execute the action when a changement on files has been detected.-Every action are elligible to continuous action, you simply need to prefix them with '~'--<pre>-~Compile-</pre>-It will compile the project every time a change in the code has been detected.-To get control back, simply enter a character.--h3. Actions available--h4. Compile--Compile the project. Targets of the compilation are main file (in case of executable) or exposed modules (in case of library).--h4. FullCompile--Compile all hs files found in source directory. It is usefull to compile test sources.--h4. Clean--Clean the directory containing .o and .hi files.--h4. Quit --Exit the application.-
TODO view
@@ -10,7 +10,6 @@ Correct import module (only import necessary functions) Create action command Use a monad writer for cabal information extraction- Extract extra-source-dirs Done : bug : ~Compile launche two fork the first time@@ -28,4 +27,5 @@ Get config through cabal api Exclude main methods from fullCompile If no source dir is specified, an error is throwed+ correct fullcompile. Concat source dir from all artifacts (currently only use the first)
shaker.cabal view
@@ -1,5 +1,5 @@ name: shaker-version: 0.1+version: 0.2 cabal-version: >= 1.8 build-type: Custom license: BSD3@@ -54,7 +54,7 @@ [@% ~Compile@] Switch to continuous mode and will compile the project when sources are modified. . [@% ~Clean Compile@] Switch to continuous mode and will clean and compile the project when sources are modified.-extra-source-files: README.textile TODO testsuite/tests/resources/invalidMain/dist/setup-config+extra-source-files: README TODO testsuite/tests/resources/invalidMain/dist/setup-config testsuite/tests/resources/invalidMain/src/Main.hs testsuite/tests/resources/invalidMain/tests/Dummy.hs testsuite/tests/resources/invalidMain/invalidMain.cabal@@ -73,11 +73,11 @@ source-repository this type: git location: git://github.com/bonnefoa/Shaker.git - tag: 0.1+ tag: 0.2 Executable shaker Main-Is: Shaker.hs- ghc-options: -Wall+ ghc-options: -Wall -fno-warn-orphans hs-source-dirs: src other-modules: Shaker.Parser@@ -116,6 +116,7 @@ Executable test hs-source-dirs: src, testsuite/tests main-is: RunTest.hs+ ghc-options: -Wall -fno-warn-orphans other-modules: Shaker.RegexTest Shaker.Properties
src/Shaker/Action/Compile.hs view
@@ -1,62 +1,35 @@ module Shaker.Action.Compile( runCompile ,runFullCompile+ ,ghcCompile ) where -import Data.List +import Shaker.SourceHelper import GHC import DynFlags import GHC.Paths-import Shaker.Io import Shaker.Type-import Control.Monad.Trans import Control.Monad.Reader --- |Run haskell compilation on given file input +-- | Run haskell compilation on given CompileInput list runCompile :: Plugin-runCompile = asks compileInputs >>= mapM runSingleCompileInput >> return ()--runSingleCompileInput :: CompileInput -> Shaker IO()-runSingleCompileInput (CompileInput sourceDir desc targetInput procFlags strflags inputTargetFiles) = do- lift $ putStrLn "-------------------------------------"- lift $ putStrLn $ concat [" --------- ", desc," ---------"]- targetFiles <- checkTargetFiles inputTargetFiles - lift $ putStrLn $ concat [" --------- ", "Compiling target : "++ show targetFiles," ---------"]- lift $ defaultErrorHandler defaultDynFlags $ - runGhc (Just libdir) $ do- dflags <- getSessionDynFlags- (newFlags,_,_) <- parseDynamicFlags dflags (map noLoc strflags)- _ <- setSessionDynFlags $ procFlags $ setSourceAndTarget sourceDir targetInput newFlags- target <- mapM (`guessTarget` Nothing) targetFiles- setTargets target- _ <- load LoadAllTargets- return ()+runCompile = asks compileInputs >>= mapM (lift . runSingleCompileInput ) >> return () +-- | Run haskell compilation on all haskell files runFullCompile :: Plugin-runFullCompile = setCompileInputForAllHsSources >>= \a -> - runSingleCompileInput a >> - return () ---- | Fill the target files to all files in listenerInput if empty-checkTargetFiles :: [String] -> Shaker IO([String])-checkTargetFiles [] = do - (ListenerInput fli _) <- asks listenerInput - files <- lift $ recurseMultipleListFiles fli- lift $ filterM (\a -> not `liftM` isFileContainingMain a) files-checkTargetFiles l = return l--setSourceAndTarget :: [String] -> String ->DynFlags -> DynFlags-setSourceAndTarget sources target dflags = dflags{- importPaths = sources- ,objectDir = Just target- ,hiDir = Just target- }+runFullCompile = do+ cpList <- asks compileInputs + let cpIn = mergeCompileInputsSources cpList+ cfFlList <- lift $ constructCompileFileList cpIn+ let newInp = runReader (setAllHsFilesAsTargets cpIn >>= removeFileWithMain) cfFlList+ lift $ runSingleCompileInput newInp -setCompileInputForAllHsSources :: Shaker IO CompileInput-setCompileInputForAllHsSources = do - (cpIn:_) <- asks compileInputs- filePaths <- lift $ recurseMultipleListFiles $ map (\a -> FileListenInfo a defaultExclude defaultHaskellPatterns ) (cfSourceDirs cpIn)- toExcludeFiles <- lift $ filterM isFileContainingMain filePaths- return $ cpIn {cfTargetFiles = filePaths \\ toExcludeFiles , cfDescription ="Full compilation" }+runSingleCompileInput :: CompileInput -> IO()+runSingleCompileInput cplInp = do+ putStrLn $ concat [" --------- ", cfDescription cplInp," ---------"]+ putStrLn $ concat [" --------- ", "Compiling target : "++ show (cfTargetFiles cplInp) ," ---------"]+ _ <- defaultErrorHandler defaultDynFlags $ + runGhc (Just libdir) $ ghcCompile cplInp + return ()
src/Shaker/Action/Quickcheck.hs view
@@ -1,6 +1,13 @@ module Shaker.Action.Quickcheck where +{-+import OccName (occNameString)+import Name (nameOccName)+import Var (varName)+import Data.List+import Data.Maybe+import Data.Dynamic (fromDynamic) import GHC import DynFlags import GHC.Paths@@ -8,10 +15,12 @@ import Shaker.Type import Control.Monad.Trans import Control.Monad.Reader-{--runQuickcheck :: Shaker IO String+import Unsafe.Coerce+++runQuickcheck :: Plugin runQuickcheck = do- (CompileInput sourceDir targetInput procFlags strflags) <- asks compileInput + (CompileInput sourceDir targetInput procFlags strflags) <- asks compileInputs (ListenerInput fli _) <- asks listenerInput targetFiles <- lift $ recurseMultipleListFiles fli lift $ defaultErrorHandler defaultDynFlags $ @@ -22,39 +31,62 @@ target <- mapM (`guessTarget` Nothing) targetFiles setTargets target _ <- load LoadAllTargets- modSum <- getModSummary $ mkModuleName "B"- p <- parseModule modSum- t <- typecheckModule p- d <- desugarModule t- l <- loadModule d- n <- getNamesInScope- c <- return $ coreModule d- g <- getModuleGraph- mapM showModule g - print $ showSDoc ( ppr (parsedSource d,"/n-----/n", typecheckedSource d) res )--}+ return()+runTest :: IO()+runTest = do + _ <- getTestFunc+ return() ++getTestFunc :: IO()+getTestFunc = + defaultErrorHandler defaultDynFlags $ do+ runGhc (Just libdir) $ do+ dflags <- getSessionDynFlags+ (newFlags,_,_) <- parseDynamicFlags dflags (map noLoc ["-itestsuite/tests","-isrc/","-package ghc"])+ _ <- setSessionDynFlags newFlags+ target <- guessTarget "Shaker.RunTest" Nothing+ setTargets [target]+ _ <- load LoadAllTargets+ m <- findModule (mkModuleName "Shaker.RunTest") Nothing+ setContext [] [m]+ dynCompileExpr("runAll")++ --compileExpr ("runAll")+ --(unsafeCoerce value) ++example :: IO[String] example = defaultErrorHandler defaultDynFlags $ do runGhc (Just libdir) $ do dflags <- getSessionDynFlags (newFlags,_,_) <- parseDynamicFlags dflags (map noLoc ["-itestsuite/tests","-isrc/","-package ghc"])- setSessionDynFlags newFlags+ _ <- setSessionDynFlags newFlags target <- guessTarget "Shaker.CliTest" Nothing setTargets [target]- load LoadAllTargets- modSum <- getModSummary $ mkModuleName "Shaker.CliTest"- p <- parseModule modSum- -- liftIO $ putStrLn $ show p - t <- typecheckModule p--- liftIO $ putStrLn $ show t - d <- desugarModule t- l <- loadModule d- n <- getNamesInScope- c <- return $ coreModule d- g <- getModuleGraph- mapM showModule g - return $ (parsedSource d,"/n-----/n", typecheckedSource d)+ _ <- load LoadAllTargets+ modulesList <- getModuleGraph+ tyThingsList <- getTyThingsFromModuleSummary modulesList+-- return $ showPpr gra+ return $ getQuickcheckFunction tyThingsList+ +getTyThingsFromModuleSummary :: (GhcMonad m) => [ModSummary] -> m [TyThing]+getTyThingsFromModuleSummary modSummaries = do+ modulesInfo <- mapM getModuleInfo modules+ return $ concat $ map modInfoTyThings $ catMaybes modulesInfo+ where modules = map ms_mod modSummaries+ +getQuickcheckFunction :: [TyThing] -> [String]+getQuickcheckFunction tyMap = filter ("prop_" `isPrefixOf`) nameList+ where idList = catMaybes $ map tyThingToId tyMap+ varList = map varName idList+ occList = map nameOccName varList+ nameList = map occNameString occList++tyThingToId :: TyThing -> Maybe Id+tyThingToId (AnId tyId) = Just tyId+tyThingToId _ = Nothing+ setSourceAndTarget :: [String] -> String ->DynFlags -> DynFlags setSourceAndTarget sources target dflags = dflags{ importPaths = sources@@ -62,3 +94,5 @@ ,hiDir = Just target } ++-}
src/Shaker/Action/Standard.hs view
@@ -13,7 +13,7 @@ putStrLn "Following actions are available : " print $ M.keys commands putStrLn "use ~[actionName] for continuous launch"-+ runExit :: Plugin runExit = lift $ putStrLn "Exiting"
src/Shaker/Cabal/CabalInfo.hs view
@@ -1,9 +1,8 @@ -- | Allow to use cabal configuration (generated via the configure action of cabal). -- Source directories and compilation options will be reused by Shaker. module Shaker.Cabal.CabalInfo- where+ where -import Shaker.Io(FileListenInfo(..),defaultHaskellPatterns,defaultExclude) import Shaker.Type import Shaker.Config import Distribution.Simple.Configure (getPersistBuildConfig)@@ -136,6 +135,4 @@ return cplInput {cfTargetFiles = newTargets} | otherwise = return cplInput where oldTargets = cfTargetFiles cplInput--
src/Shaker/Config.hs view
@@ -4,12 +4,9 @@ import Shaker.Type import Shaker.PluginConfig-import Shaker.Io(FileListenInfo(..),defaultHaskellPatterns) import Shaker.Cli(InputState(..))-import DynFlags import Control.Concurrent - defaultInput ::ShakerInput defaultInput = ShakerInput { compileInputs = [defaultCompileInput],@@ -17,37 +14,6 @@ pluginMap = defaultPluginMap, commandMap = defaultCommandMap }---- | Default compilation argument.--- Wall is activated by default-defaultCompileInput :: CompileInput-defaultCompileInput = CompileInput {- cfSourceDirs= ["src/","testsuite/tests/"]- ,cfDescription = "Default Compilation"- ,cfCompileTarget = "target" - ,cfDynFlags = defaultCompileFlags - ,cfCommandLineFlags = ["-Wall"]- ,cfTargetFiles = []-}---- | default dynamics flags--- the sources are expected to be in src as described in <http://www.haskell.org/haskellwiki/structure_of_a_haskell_project>--- the result of compilation (.o and .hi) are placed in the target/ directory--- there is no main linkage by default to allow faster compilation feedback-defaultCompileFlags :: (DynFlags -> DynFlags)-defaultCompileFlags = \a-> a {- verbosity = 1- ,ghcLink = NoLink-} ---- | The default Listener configuration--- Listened sources are all haskell sources in src/ and testsuite/--- The default delay is 2 sec-defaultListenerInput :: ListenerInput -defaultListenerInput = ListenerInput {- fileListenInfo= [FileListenInfo "src/" [] defaultHaskellPatterns, FileListenInfo "testsuite/" [] defaultHaskellPatterns ]- ,delay = 2000000- } defaultInputState :: IO InputState defaultInputState = do
src/Shaker/Io.hs view
@@ -8,29 +8,18 @@ ,FileListenInfo(..) ,isFileContaining ,isFileContainingMain+ ,isFileContainingTH ,defaultHaskellPatterns ,defaultExclude ) where -import qualified Data.ByteString.Lazy.Char8 as L +import qualified Data.ByteString.Char8 as L import Control.Monad import System.Directory import Data.List import Shaker.Regex-import System.Time---- | Represents directory to listen -data FileListenInfo = FileListenInfo{- dir :: FilePath -- ^ location of the listened directory- ,ignore :: [String] -- ^ ignore patterns- ,include :: [String] -- ^include patterns- }- deriving (Show,Eq)---- |Agregate a FilePath with its modification time-data FileInfo = FileInfo FilePath ClockTime - deriving (Show,Eq)+import Shaker.Type -- |Get the tuples of (newFiles,modifiedFiles) from given list of directory listModifiedAndCreatedFiles :: [FileListenInfo] -> [FileInfo] -> IO ([FileInfo],[FileInfo])@@ -64,6 +53,7 @@ recurseMultipleListFiles flis = liftM concat $ mapM recurseListFiles flis -- | Recursively list all files+-- All non matching files are excluded recurseListFiles :: FileListenInfo -> IO [FilePath] recurseListFiles fli@(FileListenInfo inputDir _ _) = do curDir <- canonicalizePath inputDir@@ -73,6 +63,9 @@ curListFiles <- listFiles fli return $ curListFiles ++ concat sub +isFileContainingTH :: FilePath -> IO Bool+isFileContainingTH fp = isFileContaining fp (L.pack "$(" `L.isInfixOf`)+ isFileContainingMain :: FilePath -> IO Bool isFileContainingMain fp = isFileContaining fp (L.pack "main" `L.isPrefixOf`) @@ -81,16 +74,9 @@ byStr <- L.readFile fp return $ any pat $ L.lines byStr - convertToFullPath :: FilePath -> [FilePath] -> [FilePath] convertToFullPath absDir = map (\a-> concat [absDir, "/",a]) removeDotDirectory :: [String] -> [String] removeDotDirectory = filter (not . isSuffixOf "." ) --defaultHaskellPatterns :: [String]-defaultHaskellPatterns = [".*\\.hs$"]--defaultExclude :: [String]-defaultExclude = [".*Setup\\.hs$"]
src/Shaker/Type.hs view
@@ -1,12 +1,13 @@ module Shaker.Type where -import DynFlags(DynFlags)-import Shaker.Io(FileListenInfo)+import DynFlags import qualified Data.Map as M import Control.Monad.Reader+import System.Time(ClockTime) type Shaker = ReaderT ShakerInput +type CompileM = Reader CompileInput -- | Duration define the life span of an action data Duration = @@ -55,12 +56,62 @@ fileListenInfo :: [FileListenInfo] -- ^ The files to listen ,delay :: Int -- ^ Delay beetween 2 check in microsecond }++-- | Represents directory to listen +data FileListenInfo = FileListenInfo{+ dir :: FilePath -- ^ location of the listened directory+ ,ignore :: [String] -- ^ ignore patterns+ ,include :: [String] -- ^include patterns+ }+ deriving (Show,Eq)++-- |Agregate a FilePath with its modification time+data FileInfo = FileInfo FilePath ClockTime + deriving (Show,Eq) -- | Represents the mapping beetween an action and the function to execute type PluginMap = M.Map Action Plugin -- | Represents the mapping between the command-line input and the action type CommandMap = M.Map String Action -- | Represents an action of shaker-type Plugin = Shaker IO()+type Plugin = Shaker IO() +-- * Default data++-- | Default compilation argument.+-- Wall is activated by default+defaultCompileInput :: CompileInput+defaultCompileInput = CompileInput {+ cfSourceDirs= ["src/","testsuite/tests/"]+ ,cfDescription = "Default Compilation"+ ,cfCompileTarget = "target" + ,cfDynFlags = defaultCompileFlags + ,cfCommandLineFlags = ["-Wall"]+ ,cfTargetFiles = []+}++-- | default dynamics flags+-- the sources are expected to be in src as described in <http://www.haskell.org/haskellwiki/structure_of_a_haskell_project>+-- the result of compilation (.o and .hi) are placed in the target/ directory+-- there is no main linkage by default to allow faster compilation feedback+defaultCompileFlags :: (DynFlags -> DynFlags)+defaultCompileFlags = \a-> a {+ verbosity = 1+ ,ghcLink = NoLink+} ++-- | The default Listener configuration+-- Listened sources are all haskell sources in src/ and testsuite/+-- The default delay is 2 sec+defaultListenerInput :: ListenerInput +defaultListenerInput = ListenerInput {+ fileListenInfo= [FileListenInfo "src/" [] defaultHaskellPatterns, FileListenInfo "testsuite/" [] defaultHaskellPatterns ]+ ,delay = 2000000+ }++defaultHaskellPatterns :: [String]+defaultHaskellPatterns = [".*\\.hs$"]++defaultExclude :: [String]+defaultExclude = [".*Setup\\.hs$"]
testsuite/tests/Shaker/Cabal/CabalInfoTest.hs view
@@ -6,16 +6,11 @@ import Shaker.Action.Compile import Shaker.Action.Clean import Shaker.Type-import Shaker.Io+import Shaker.CommonTest import Test.HUnit-import Control.Exception import Shaker.Cabal.CabalInfo-import GHC(DynFlags(DynFlags))-import DynFlags(- DynFlags, verbosity, ghcLink, packageFlags, outputFile, hiDir, objectDir ,importPaths- ,PackageFlag (ExposePackage)- ,GhcLink (NoLink)- )+import DynFlags( DynFlags, packageFlags, importPaths ,PackageFlag (ExposePackage) , defaultDynFlags+ ) testParseCabalConfig :: Test testParseCabalConfig = TestCase $ runTestOnDirectory "testsuite/tests/resources/cabalTest" $ do @@ -26,7 +21,7 @@ cfCommandLineFlags cplLib == ["-Wall"] @? "command line flags should be -Wall, got " ++ show ( cfCommandLineFlags cplLib) cfTargetFiles cplLib == ["CabalTest"] @? "targetFiles should be CabalTest, got "++ show ( cfTargetFiles cplLib) cfTargetFiles cplExe == ["src/Main.hs"] @? "targetFiles should be src/Main.hs, got "++ show ( cfTargetFiles cplExe)- let dFlags = cfDynFlags cplExe DynFlags{}+ let dFlags = cfDynFlags cplExe defaultDynFlags importPaths dFlags == ["src"] @? "importPaths should be src, got "++ show (importPaths dFlags) packageFlags dFlags == [ExposePackage "ghc"] @? "Expected : ExposePackage ghc. No show instance so figure it yourself... (/me being lazy)" let (ListenerInput (flLib:[]) _) = listenerInput shIn@@ -56,10 +51,4 @@ length cplInps == 1 @? "Should have one compile input, got "++ show (length cplInps) let targs = cfTargetFiles cplInp targs == ["./noHsSource.hs"] @? "Expected [\"./noHsSource.hs\"] got " ++ show cplInp--runTestOnDirectory :: FilePath -> Assertion -> Assertion-runTestOnDirectory fp fun = do- oldDir <- getCurrentDirectory - setCurrentDirectory fp- finally fun (setCurrentDirectory oldDir)
testsuite/tests/Shaker/ConductorTest.hs view
@@ -1,5 +1,4 @@ module Shaker.ConductorTest where -import Shaker.Conductor
testsuite/tests/Shaker/Properties.hs view
@@ -4,7 +4,6 @@ import Control.Monad import Test.QuickCheck import System.Time-import Shaker.Type import Shaker.Io instance Arbitrary TimeDiff where