{-# LANGUAGE LambdaCase, ScopedTypeVariables, TypeApplications #-} module Main where import Control.Applicative ((<$>)) import Control.Exception (SomeException, try) import Control.Monad import Data.List import Data.List.Split (splitOn) import Data.Semigroup ((<>)) import Options.Applicative import System.Directory (removeDirectoryRecursive, getCurrentDirectory, createDirectoryIfMissing) import System.Exit (ExitCode(..)) import System.FilePath (FilePath, ()) import System.FilePath.Glob () import System.Process main :: IO () main = testStackage =<< execParser opts where opts = info (options <**> helper) (fullDesc <> progDesc "Tests the Haskell-tools framework with stackage packages." <> header "ht-test-stackage: a tester utility for Haskell-tools") options :: Parser StackageTestConfig options = StackageTestConfig <$> noload <*> noretest <*> noclean <*> result <*> srcdir <*> logdir <*> inputfile <*> snapshot where noload = switch (long "no-load" <> help "Don't download the package, use the existing sources.") noretest = switch (long "no-retest" <> help "Don't run the test on packages already in the results file.") noclean = switch (long "no-clean" <> help "Keep the refactored files.") srcdir = strOption (long "src-dir" <> short 't' <> metavar "PATH" <> showDefault <> value "stackage-test" <> help "The directory where the downloaded sources should be stored.") logdir = strOption (long "log-dir" <> short 'l' <> metavar "PATH" <> showDefault <> value "stackage-test-logs" <> help "The directory where the log files should be stored.") snapshot = optional (strOption (long "snapshot" <> metavar "SNAPSHOT" <> help "The stackage snapshots identifier.")) result = strOption (long "result" <> short 'r' <> metavar "FILE" <> showDefault <> value ("results.csv") <> help "The text file where the results are stored in csv format.") inputfile = strArgument (metavar "FILE") data StackageTestConfig = StackageTestConfig { noLoad :: Bool , noRetest :: Bool , noClean :: Bool , resultFile :: FilePath , sourceDirectory :: FilePath , logDirectory :: FilePath , inputFile :: FilePath , stackageSnapshot :: Maybe String } testStackage :: StackageTestConfig -> IO () testStackage config = do packages <- lines <$> readFile (inputFile config) alreadyTested <- if noRetest config then do appendFile (resultFile config) "" map (head . splitOn ";") . filter (not . null) . lines <$> readFile (resultFile config) else writeFile (resultFile config) "" >> return [] let filteredPackages = packages \\ alreadyTested createDirectoryIfMissing False (logDirectory config) mapM_ testAndEvaluate filteredPackages where testAndEvaluate p = do (res, problem) <- testPackage (noLoad config) (noClean config) (sourceDirectory config) (logDirectory config) (stackageSnapshot config) p appendFile (resultFile config) (p ++ ";" ++ show res ++ " ; " ++ problem ++ "\n") testPackage :: Bool -> Bool -> FilePath -> FilePath -> Maybe String -> String -> IO (Result, String) testPackage noLoad noClean sourceDirectory logDirectory resolver pack = do baseDir <- getCurrentDirectory let pkgLoc = baseDir sourceDirectory pack buildLogPath = baseDir logDirectory (pack ++ "-build-log.txt") refLogPath = baseDir logDirectory (pack ++ "-refact-log.txt") reloadLogPath = baseDir logDirectory (pack ++ "-reload-log.txt") res <- runCommands (if noClean then return () else cleanup pkgLoc) $ init ++ load ++ [ Left ("stack init" ++ (maybe "" (" --resolver="++) resolver) ++ " > " ++ buildLogPath ++ " 2>&1", pkgLoc, BuildFailure) , Left ("stack build --test --no-run-tests --bench --no-run-benchmarks --ghc-options=\"-w\" > " ++ buildLogPath ++ " 2>&1", pkgLoc, BuildFailure) -- correct rts option handling (on windows) requires stack 1.4 , Left ("stack exec --RTS -- ht-refact +RTS -M4G -RTS --no-watch --ghc-options=\"-w\" " ++ " --execute=\"ProjectOrganizeImports\" . > " ++ refLogPath ++ " 2>&1", pkgLoc, RefactError) , Left ("stack build > " ++ reloadLogPath ++ " 2>&1", pkgLoc, WrongCodeError) ] problem <- case res of RefactError -> map (\case '\n' -> ' '; c -> c) <$> readFile refLogPath WrongCodeError -> map (\case '\n' -> ' '; c -> c) <$> readFile reloadLogPath _ -> return "" return (res, problem) where load = if noLoad then [] else [ Right $ (either (\(_ :: SomeException) -> return ()) return =<<) $ try (removeDirectoryRecursive (sourceDirectory pack)) , Left ("cabal get -d " ++ sourceDirectory ++ " " ++ pack, ".", GetFailure) ] init = [ Right (forM_ [sourceDirectory,logDirectory] (createDirectoryIfMissing True)) ] cleanup pkgLoc = either (print @SomeException) return =<< try (removeDirectoryRecursive pkgLoc) data Result = GetFailure | BuildFailure | RefactError | WrongCodeError | OK deriving Show runCommands :: IO () -> [Either (String, FilePath, Result) (IO ())] -> IO Result runCommands final [] = final >> return OK runCommands final (Left (cmd,wd,failRes) : rest) = do baseDir <- getCurrentDirectory let sh = shell cmd (_, _, _, pr) <- createProcess sh{ cwd = Just (baseDir wd) } exitCode <- waitForProcess pr case exitCode of ExitSuccess -> runCommands final rest ExitFailure _ -> final >> return failRes runCommands final (Right act : rest) = act >> runCommands final rest