packages feed

TBC-0.0.2: Test/Main.hs

{- Test By Convention: executable top-level.
 - Copyright   :  (C)opyright 2009-2011 {mwotton, peteg42} at gmail dot com
 - License     :  BSD3
 -}
module Main ( main ) where

-------------------------------------------------------------------
-- Dependencies.
-------------------------------------------------------------------

import Control.Monad ( unless, when )
import Data.List ( foldl', isSuffixOf )

import Distribution.PackageDescription ( GenericPackageDescription )
import Distribution.PackageDescription.Parse ( readPackageDescription )

import Distribution.Simple
import Distribution.Simple.Configure
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Program ( builtinPrograms, restoreProgramConfiguration )
import Distribution.Simple.Setup
  ( ConfigFlags(..), Flag(..), configPrograms, configVerbosity, defaultDistPref, fromFlag )
import Distribution.Simple.Utils ( defaultPackageDesc )

import System.Directory ( getCurrentDirectory, getDirectoryContents, setCurrentDirectory )
import System.Exit -- ( ExitFailure, exitWith )
import System.Environment ( getArgs, getProgName )
import System.FilePath -- ( takeDirectory ) -- FIXME

import qualified System.Console.GetOpt as GetOpt

import Test.TBC

-------------------------------------------------------------------
-- TBC-as-an-executable.
-------------------------------------------------------------------

-- | Find a @.cabal@ file that might apply to the current
-- directory. FIXME robustness? Windows? Efficiency?
-- Also want to dodge the ~/.cabal directory.
-- FIXME repair relPath
findCabal :: IO (Maybe (FilePath, FilePath, FilePath))
findCabal = getCurrentDirectory >>= searchUp ["."] . splitPath
  where
    searchUp :: [FilePath] -> [FilePath] -> IO (Maybe (FilePath, FilePath, FilePath))
    searchUp relPath path =
      do fs <- getDirectoryContents curdir
         case filter (".cabal" `isSuffixOf`) fs of
           [] -> case nextPath of
                   [] -> return Nothing
                   _  -> searchUp (last path : relPath) nextPath
           [cabal] -> return (Just (joinPath relPath, curdir, cabal))
           fs' -> error $ "FIXME: several cabal files found: " ++ show fs'
      where
        curdir = joinPath path
        nextPath = init path

----------------------------------------

-- | Program arguments.
data Options =
    Options
    { optVerbosity   :: Verbosity
    } deriving Show

defaultOptions :: Options
defaultOptions =
    Options
    { optVerbosity   = normal
    }

-- | FIXME use intToVerbosity
options :: [GetOpt.OptDescr (Options -> Options)]
options =
    [ GetOpt.Option ['v']     ["verbose"]
        (GetOpt.NoArg (\ opts -> opts { optVerbosity = deafening })) -- verbose
        "chatty output on stdout"
    ]

progOpts :: [String] -> IO (Options, [String])
progOpts argv =
  case GetOpt.getOpt GetOpt.Permute options argv of
      (o, n, []  ) -> return (foldl' (flip id) defaultOptions o, n)
      (_, _, errs) ->
        do progName <- getProgName
           ioError (userError (concat errs ++ GetOpt.usageInfo (header progName) options))
  where header progName = "Usage: " ++ progName ++ " [OPTION...] files..."

----------------------------------------

-- | FIXME infinite room for improvement.
-- FIXME Make use of the options
main :: IO ()
main =
 do (opts, tests) <- progOpts =<< getArgs

    debug (optVerbosity opts) $ "Options: " ++ show opts
    unless (null tests) $ putStrLn $ "Testing: " ++ show tests

    cabalLoc <- findCabal
    case cabalLoc of
      Nothing ->
        do putStrLn ".cabal file not found."
           exitWith (ExitFailure 1)
      Just (testPath, root, cabalFile) ->
        do
           info (optVerbosity opts) $
             "Running tests with Cabal file: '" ++ cabalFile ++ "' in directory: " ++ testPath
           -- Change to the project root directory
           setCurrentDirectory root
           when (optVerbosity opts >= deafening) $
             getCurrentDirectory >>= \s -> putStrLn $ "In directory: " ++ s

           -- FIXME assume the dist/ dir is with the .cabal file.
           -- No good evidence for this except it's the Simple thing to do.
           let distPref = defaultDistPref
               verbosity = optVerbosity opts -- fromFlag $ buildVerbosity flags
           localbuildinfo <- getBuildConfig hooks verbosity distPref
           let pkg_descr = localPkgDescr localbuildinfo

           let ts = case tests of
                      [] -> [ testPath ]
                      _  -> [ testPath </> t | t <- tests ]

           tbcCabal (optVerbosity opts) ts False pkg_descr localbuildinfo
  where hooks = simpleUserHooks -- error "FIXME Simple only for now."

----------------------------------------

-- Ripped from Cabal (Distribution.Simple). *sigh* Why not export more stuff?

-- | FIXME we assume the user isn't doing anything clever with
-- UserHooks. This info lies in Setup.hs, a bit beyond our reach.
getBuildConfig :: UserHooks -> Verbosity -> FilePath -> IO LocalBuildInfo
getBuildConfig hooks verbosity distPref = do
  lbi_wo_programs <- getPersistBuildConfig distPref
  -- Restore info about unconfigured programs, since it is not serialized
  let lbi = lbi_wo_programs {
    withPrograms = restoreProgramConfiguration
                     (builtinPrograms ++ hookedPrograms hooks)
                     (withPrograms lbi_wo_programs)
  }

  case pkgDescrFile lbi of
    Nothing -> return lbi
    Just pkg_descr_file -> do
      outdated <- checkPersistBuildConfigOutdated distPref pkg_descr_file
      if outdated
        then reconfigure pkg_descr_file lbi
        else return lbi

  where
    reconfigure :: FilePath -> LocalBuildInfo -> IO LocalBuildInfo
    reconfigure pkg_descr_file lbi = do
      notice verbosity $ pkg_descr_file ++ " has been changed. "
                      ++ "Re-configuring with most recently used options. " 
                      ++ "If this fails, please run configure manually.\n"
      let cFlags = configFlags lbi
      let cFlags' = cFlags {
            -- Since the list of unconfigured programs is not serialized,
            -- restore it to the same value as normally used at the beginning
            -- of a conigure run:
            configPrograms = restoreProgramConfiguration
                               (builtinPrograms ++ hookedPrograms hooks)
                               (configPrograms cFlags),

            -- Use the current, not saved verbosity level:
            configVerbosity = Flag verbosity
          }
      configureAction hooks cFlags' (extraConfigArgs lbi)

configureAction :: UserHooks -> ConfigFlags -> Args -> IO LocalBuildInfo
configureAction hooks flags args = do
                let distPref = fromFlag $ configDistPref flags
                pbi <- preConf hooks args flags

                (mb_pd_file, pkg_descr0) <- confPkgDescr

                --    get_pkg_descr (configVerbosity flags')
                --let pkg_descr = updatePackageDescription pbi pkg_descr0
                let epkg_descr = (pkg_descr0, pbi)

                --(warns, ers) <- sanityCheckPackage pkg_descr
                --errorOut (configVerbosity flags') warns ers

                localbuildinfo0 <- confHook hooks epkg_descr flags

                -- remember the .cabal filename if we know it
                -- and all the extra command line args
                let localbuildinfo = localbuildinfo0 {
                                       pkgDescrFile = mb_pd_file,
                                       extraConfigArgs = args
                                     }
                writePersistBuildConfig distPref localbuildinfo

                let pkg_descr = localPkgDescr localbuildinfo
                postConf hooks args flags pkg_descr localbuildinfo
                return localbuildinfo
              where
                verbosity = fromFlag (configVerbosity flags)
                confPkgDescr :: IO (Maybe FilePath, GenericPackageDescription)
                confPkgDescr = do
                  mdescr <- readDesc hooks
                  case mdescr of
                    Just descr -> return (Nothing, descr)
                    Nothing -> do
                      pdfile <- defaultPackageDesc verbosity
                      descr  <- readPackageDescription verbosity pdfile
                      return (Just pdfile, descr)