packages feed

buildbox 1.0.0.1 → 1.0.1.0

raw patch · 5 files changed

+169/−63 lines, 5 filesdep +bytestringPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: bytestring

API changes (from Hackage documentation)

+ BuildBox.Command.System: systemTeeLog :: Bool -> String -> Log -> Build (ExitCode, Log, Log)
+ BuildBox.Command.System: systemTeeLogIO :: Bool -> String -> Log -> IO (ExitCode, Log, Log)
+ BuildBox.Data.Log: (<|) :: Line -> Log -> Log
+ BuildBox.Data.Log: (><) :: Log -> Log -> Log
+ BuildBox.Data.Log: (|>) :: Log -> Line -> Log
+ BuildBox.Data.Log: empty :: Log
+ BuildBox.Data.Log: firstLines :: Int -> Log -> Log
+ BuildBox.Data.Log: fromString :: String -> Log
+ BuildBox.Data.Log: instance Show Log
+ BuildBox.Data.Log: lastLines :: Int -> Log -> Log
+ BuildBox.Data.Log: null :: Log -> Bool
+ BuildBox.Data.Log: toString :: Log -> String
+ BuildBox.Data.Log: type Line = ByteString
+ BuildBox.Data.Log: type Log = Seq Line
- BuildBox.Build: ErrorSystemCmdFailed :: String -> ExitCode -> String -> String -> BuildError
+ BuildBox.Build: ErrorSystemCmdFailed :: String -> ExitCode -> Log -> Log -> BuildError
- BuildBox.Build: buildErrorStderr :: BuildError -> String
+ BuildBox.Build: buildErrorStderr :: BuildError -> Log
- BuildBox.Build: buildErrorStdout :: BuildError -> String
+ BuildBox.Build: buildErrorStdout :: BuildError -> Log

Files

BuildBox/Build/Base.hs view
@@ -8,7 +8,10 @@ import System.IO import System.IO.Error import System.Exit+import BuildBox.Data.Log		(Log)+import qualified BuildBox.Data.Log	as Log + -- | The builder monad encapsulates and IO action that can fail with an error,  --   and also read some global configuration info. type Build a 	= ErrorT BuildError (ReaderT BuildConfig IO) a@@ -23,8 +26,8 @@ 	| ErrorSystemCmdFailed 		{ buildErrorCmd 	:: String 		, buildErrorCode	:: ExitCode-		, buildErrorStdout	:: String-		, buildErrorStderr	:: String }+		, buildErrorStdout	:: Log+		, buildErrorStderr	:: Log } 		 	-- | Some other IO action failed. 	| ErrorIOError IOError@@ -47,14 +50,14 @@ 		, text "    command: " <> (text $ buildErrorCmd err) 		, text "  exit code: " <> (text $ show $ buildErrorCode err) 		, blank-		, if (not $ null $ buildErrorStdout err)+		, if (not $ Log.null $ buildErrorStdout err) 		   then vcat 	[ text "-- stdout (last 10 lines) ------------------------------------------------------"-				, vcat $ map text $ reverse $ take 10 $ reverse $ lines $ buildErrorStdout err]+				, text $ Log.toString $ Log.lastLines 10 $ buildErrorStdout err] 		   else text "" 		, blank-		, if (not $ null $ buildErrorStderr err)+		, if (not $ Log.null $ buildErrorStderr err) 		   then vcat	[ text "-- stderr (last 10 lines) ------------------------------------------------------"-				, vcat $ map text $ reverse $ take 10 $ reverse $ lines $ buildErrorStderr err]+				, text $ Log.toString $ Log.lastLines 10 $ buildErrorStderr err] 		   else text "" 		 		, 		  text "--------------------------------------------------------------------------------" ]@@ -64,6 +67,9 @@  	ErrorCheckFailed expected prop 	 -> text "Check failure: " <> (text $ show prop) <> (text " expected ") <> (text $ show expected)++instance Show BuildError where+ show err = render $ ppr err   -- BuildConfig ------------------------------------------------------------------------------------
BuildBox/Command/System.hs view
@@ -18,18 +18,23 @@ 	, ssystemOut 	, qssystemOut 	, systemTee+	, systemTeeLog 	, ssystemTee+	, systemTeeIO  	-- * The real function-	, systemTeeIO)+	, systemTeeLogIO) where import BuildBox.Command.System.Internals import BuildBox.Build import Control.Concurrent-import System.Process	hiding (system) import System.Exit import System.IO import Control.Monad+import Data.ByteString.Char8		(ByteString)+import BuildBox.Data.Log		(Log)+import System.Process			hiding (system)+import qualified BuildBox.Data.Log	as Log  debug :: Bool debug	= False@@ -42,7 +47,7 @@ -- | Run a system command, returning its exit code. system :: String -> Build ExitCode system cmd- = do	(code, _, _)		<- systemTee True cmd ""+ = do	(code, _, _)		<- systemTeeLog True cmd Log.empty 	return code  @@ -50,16 +55,16 @@ --   If the exit code is `ExitFailure` then throw an error in the `Build` monad. ssystem :: String -> Build () ssystem cmd- = do	(code, strOut, strErr)	<- systemTee True cmd ""+ = do	(code, logOut, logErr)	<- systemTeeLog True cmd Log.empty  	when (code /= ExitSuccess)-	 $ throw $ ErrorSystemCmdFailed cmd code strOut strErr+	 $ throw $ ErrorSystemCmdFailed cmd code logOut logErr   -- | Quietly run a system command, returning its exit code. qsystem :: String -> Build ExitCode qsystem cmd- = do	(code, _, _)		<- systemTee False cmd ""+ = do	(code, _, _)		<- systemTeeLog False cmd Log.empty 	return code  @@ -67,10 +72,10 @@ --   If the exit code is `ExitFailure` then throw an error in the `Build` monad. qssystem :: String -> Build () qssystem cmd- = do	(code, strOut, strErr)	<- systemTee False cmd ""+ = do	(code, logOut, logErr)	<- systemTeeLog False cmd Log.empty  	when (code /= ExitSuccess)-	 $ throw $ ErrorSystemCmdFailed cmd code strOut strErr+	 $ throw $ ErrorSystemCmdFailed cmd code logOut logErr 	  -- | Run a successful system command, returning what it wrote to its @stdout@.@@ -79,11 +84,11 @@ --   then throw an error in the `Build` monad. ssystemOut :: String -> Build String ssystemOut cmd- = do	(code, strOut, strErr)	<- systemTee True cmd ""-	when (code /= ExitSuccess || (not $ null strErr))-	 $ throw $ ErrorSystemCmdFailed cmd code strOut strErr+ = do	(code, logOut, logErr)	<- systemTeeLog True cmd Log.empty+	when (code /= ExitSuccess || (not $ Log.null logErr))+	 $ throw $ ErrorSystemCmdFailed cmd code logOut logErr -	return strOut+	return $ Log.toString logOut  -- | Quietly run a successful system command, returning what it wrote to its @stdout@. --   If anything was written to @stderr@ then treat that as failure. @@ -91,46 +96,53 @@ --   then throw an error in the `Build` monad. qssystemOut :: String -> Build String qssystemOut cmd- = do	(code, strOut, strErr)	<- systemTee False cmd ""-	when (code /= ExitSuccess || (not $ null strErr))-	 $ throw $ ErrorSystemCmdFailed cmd code strOut strErr+ = do	(code, logOut, logErr)	<- systemTeeLog False cmd Log.empty+	when (code /= ExitSuccess || (not $ Log.null logErr))+	 $ throw $ ErrorSystemCmdFailed cmd code logOut logErr -	return strOut+	return $ Log.toString logOut    -- Tee versions -----------------------------------------------------------------------------------  -- | Like `systemTeeIO`, but in the `Build` monad.-systemTee -	:: Bool 	-	-> String	-	-> String	-	-> Build (ExitCode, String, String)-+systemTee :: Bool -> String -> String -> Build (ExitCode, String, String) systemTee tee cmd strIn  = do	logSystem cmd 	io $ systemTeeIO tee cmd strIn +-- | Like `systemTeeLogIO`, but in the `Build` monad.+systemTeeLog :: Bool -> String -> Log -> Build (ExitCode, Log, Log)+systemTeeLog tee cmd logIn+ = do	logSystem cmd+	io $ systemTeeLogIO tee cmd logIn + -- | Like `systemTeeIO`, but in the `Build` monad and throw an error if it returns `ExitFailure`. ssystemTee  :: Bool -> String -> String -> Build () ssystemTee tee cmd strIn  = do	logSystem cmd-	(code, strOut, strErr)	<- systemTee tee cmd strIn+	(code, logOut, logErr)	<- systemTeeLog tee cmd (Log.fromString strIn) 	when (code /= ExitSuccess)-	 $ throw $ ErrorSystemCmdFailed cmd code strOut strErr+	 $ throw $ ErrorSystemCmdFailed cmd code logOut logErr -	 +-- | Like `systemTeeLogIO`, but with strings.+systemTeeIO :: Bool -> String -> String -> IO (ExitCode, String, String)+systemTeeIO tee cmd strIn+ = do	(code, logOut, logErr)	<- systemTeeLogIO tee cmd $ Log.fromString strIn+	return	(code, Log.toString logOut, Log.toString logErr)++ -- | Run a system command, returning its `ExitCode` and what was written to @stdout@ and @stderr@.-systemTeeIO +systemTeeLogIO 	:: Bool 	-- ^ Whether @stdout@ and @stderr@ should be forwarded to the parent process. 	-> String 	-- ^ Command to run.-	-> String	-- ^ What to pass to the command's @stdin@.-	-> IO (ExitCode, String, String)+	-> Log		-- ^ What to pass to the command's @stdin@.+	-> IO (ExitCode, Log, Log) -systemTeeIO tee cmd strIn+systemTeeLogIO tee cmd logIn  = do	trace $ "systemTeeIO " ++ show tee ++ ": " ++ cmd  	-- Create some new pipes for the process to write its stdout and stderr to.@@ -147,7 +159,7 @@ 			, close_fds	= False }  	-- Push input into in handle-	hPutStr hInWrite strIn+	hPutStr hInWrite $ Log.toString logIn 			 	-- To implement the tee-like behavior we'll fork some threads that read lines from the 	-- processes stdout and stderr and write them to these channels. @@ -186,22 +198,20 @@ 	-- Get what was written to its stdout and stderr. 	--	getChanContents is a lazy read, so don't pull from the channel after 	--	seeing a Nothing else we'll block forever.-	strOut		<- liftM (concat . slurpUntilNothing) $ getChanContents chanOutAcc-	strErr		<- liftM (concat . slurpUntilNothing) $ getChanContents chanErrAcc+	logOut		<- slurpChan chanOutAcc Log.empty+	logErr		<- slurpChan chanErrAcc Log.empty -	trace $ "systemTeeIO stdout: " ++ strOut-	trace $ "systemTeeIO stderr: " ++ strErr+	trace $ "systemTeeIO stdout: " ++ Log.toString logOut+	trace $ "systemTeeIO stderr: " ++ Log.toString logErr  	trace $ "systemTeeIO: All done"-	code `seq` strOut `seq` strErr `seq` -		return	(code, strOut, strErr)--+	code `seq` logOut `seq` logErr `seq` +		return	(code, logOut, logErr) -slurpUntilNothing :: [Maybe a] -> [a]-slurpUntilNothing xx- = case xx of-	[]		-> []-	Nothing : _	-> []-	Just x  : xs	-> x : slurpUntilNothing xs+slurpChan :: Chan (Maybe ByteString) -> Log -> IO Log+slurpChan !chan !ll+ = do	mStr	<- readChan chan+	case mStr of+	 Nothing	-> return ll+	 Just str	-> slurpChan chan (ll Log.|> str) 
BuildBox/Command/System/Internals.hs view
@@ -1,15 +1,18 @@-{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE PatternGuards, BangPatterns #-} module BuildBox.Command.System.Internals 	( streamIn 	, streamOuts) where import System.IO-import Control.Concurrent	+import Control.Concurrent+import Data.ByteString.Char8		(ByteString)+import qualified Data.ByteString.Char8	as BS	 + -- | Continually read lines from a handle and write them to this channel. --   When the handle hits EOF then write `Nothing` to the channel.-streamIn  :: Handle -> Chan (Maybe String) -> IO ()-streamIn hRead chan+streamIn  :: Handle -> Chan (Maybe ByteString) -> IO ()+streamIn !hRead !chan  = do	eof	<- hIsEOF hRead 	if eof 	 then do@@ -17,18 +20,23 @@ 		return () 		 	 else do-		str	<- hGetLine hRead-		writeChan chan (Just (str ++ "\n"))+		str	<- BS.hGetLine hRead+		writeChan chan (Just str) 		streamIn hRead chan   -- | Continually read lines from some channels and write them to handles. --   When all the channels return `Nothing` then we're done. --   When we're done, signal this fact on the semaphore.-streamOuts :: [(Chan (Maybe String), (Maybe Handle), QSem)] -> IO ()-streamOuts chans +streamOuts :: [(Chan (Maybe ByteString), (Maybe Handle), QSem)] -> IO ()+streamOuts !chans   = streamOuts' False [] chans +	-- There doesn't seem to be a way to perform a unix-style "select" on channels.+	-- We want to wait until any of the channels becomes available for reading.+	-- We're doing this just by polling them each in turn, and waiting a bit+	--	if none of them had any data.+		  where	-- we're done. 	streamOuts' _ []   []	 		= return ()@@ -38,11 +46,11 @@ 	 = 	streamOuts' False [] prev  	streamOuts' False prev []-	 = do	yield+	 = do	threadDelay 100000 		streamOuts' False [] prev  	-- try to read from the current chan.-	streamOuts' active prev (x@(chan, mHandle, qsem) : rest)+	streamOuts' !active !prev (!x@(!chan, !mHandle, !qsem) : rest) 	 = isEmptyChan chan >>= \empty ->  	   if empty  	    then streamOuts' active (prev ++ [x]) rest@@ -55,7 +63,8 @@  		 Just str  		  | Just h	<- mHandle-		  -> do	hPutStr h str+		  -> do	BS.hPutStr h str+			hPutChar   h '\n' 			streamOuts' True (prev ++ [x]) rest  		  | otherwise
+ BuildBox/Data/Log.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE TypeSynonymInstances #-}++-- | When the output of a command is long, keeping it as a `String` is a bad idea.+module BuildBox.Data.Log+	( Log+	, Line+	, empty+	, null+	, toString+	, fromString+	, (<|)+	, (|>)+	, (><)+	, firstLines+	, lastLines)+where+import Data.ByteString.Char8		(ByteString)+import Data.Sequence			(Seq)+import qualified Data.ByteString.Char8 	as BS+import qualified Data.Sequence		as Seq+import qualified Data.Foldable		as F+import Prelude				hiding (null)++-- | A sequence of lines, without newline charaters on the end.+type Log 	= Seq Line+type Line	= ByteString++instance Show Log where+ show	= toString+	++-- | O(1) No logs here.+empty :: Log+empty = Seq.empty++-- | O(1) Check if the log is empty.+null :: Log -> Bool+null  = Seq.null++-- | O(n) Convert a `Log` to a `String`.+toString :: Log -> String+toString ll	+	= BS.unpack +	$ BS.intercalate (BS.pack "\n") +	$ F.toList (ll Seq.>< Seq.singleton BS.empty) +		-- hack to get the last "\n" on the end.+++-- | O(n) Convert a `String` to a `Log`.+fromString :: String -> Log+fromString str	+	= Seq.fromList +	$ BS.splitWith (== '\n')+	$ BS.pack str+++-- | O(1) Add a `Line` to the start of a `Log`.+(<|):: Line -> Log -> Log+(<|)	= (Seq.<|)++-- | O(1) Add a `Line` to the end of a `Log`.+(|>)	:: Log -> Line -> Log+(|>)	= (Seq.|>)++-- | O(log(min(n1,n2))) Concatenate two `Log`s.+(><)	:: Log -> Log -> Log+(><)	= (Seq.><)+++-- | O(n) Take the first m lines from a log+firstLines :: Int -> Log -> Log+firstLines m ll+	= Seq.take m ll++-- | O(n) Take the last m lines from a log+lastLines :: Int -> Log -> Log+lastLines m ll+	= Seq.drop (Seq.length ll - m) ll+	
buildbox.cabal view
@@ -1,5 +1,5 @@ Name:                buildbox-Version:             1.0.0.1+Version:             1.0.1.0 License:             BSD3 License-file:        LICENSE Author:              Ben Lippmeier@@ -32,12 +32,14 @@         unix                 == 2.4.*,         pretty               == 1.0.*,         old-locale           == 1.0.*,-        containers           == 0.3.*+        containers           == 0.3.*,+        bytestring           == 0.9.*    ghc-options:         -Wall    Exposed-modules:+        BuildBox.Data.Log         BuildBox.Benchmark.Base         BuildBox.Benchmark.Compare         BuildBox.Benchmark.Pretty