ghc-options-0.1.0.0: src/Language/Haskell/GhcOpts/Cabal.hs
{-# LANGUAGE CPP #-}
module Language.Haskell.GhcOpts.Cabal
( getPackageGhcOpts
, mkCabalConfig
, findCabalFile
) where
import Language.Haskell.GhcOpts.Types
import System.Posix.Files (getFileStatus, modificationTime)
import Control.Exception (IOException, catch)
import Control.Monad (when)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (execStateT, modify)
import Data.Char (isSpace)
import Data.List (foldl', nub, sort, find, isPrefixOf, isSuffixOf)
#if __GLASGOW_HASKELL__ < 709
import Control.Applicative ((<$>))
import Data.Monoid (Monoid(..))
#endif
import Distribution.Package (PackageIdentifier(..), PackageName)
import Distribution.PackageDescription (PackageDescription(..), Executable(..), TestSuite(..), Benchmark(..), emptyHookedBuildInfo, buildable, libBuildInfo)
import Distribution.PackageDescription.Parse (readPackageDescription)
import Distribution.Simple.Configure (configure)
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..), ComponentLocalBuildInfo(..),
Component(..), ComponentName(..),
#if __GLASGOW_HASKELL__ < 707
allComponentsBy,
#endif
componentBuildInfo, foldComponent)
import Distribution.Simple.Compiler (PackageDB(..))
import Distribution.Simple.Command (CommandParse(..), commandParseArgs)
import Distribution.Simple.GHC (componentGhcOptions)
import Distribution.Simple.Program (defaultProgramConfiguration)
import Distribution.Simple.Program.Db (lookupProgram)
import Distribution.Simple.Program.Types (ConfiguredProgram(programVersion), simpleProgram)
import Distribution.Simple.Program.GHC (GhcOptions(..), renderGhcOptions)
import Distribution.Simple.Setup (ConfigFlags(..), defaultConfigFlags, configureCommand, toFlag)
#if __GLASGOW_HASKELL__ >= 709
import Distribution.Utils.NubList
import qualified Distribution.Simple.GHC as GHC(configure)
#endif
import Distribution.Verbosity (silent)
import Distribution.Version (Version(..))
import System.IO.Error (ioeGetErrorString)
import System.Directory (getCurrentDirectory, doesFileExist, getDirectoryContents)
import System.FilePath (takeDirectory, splitFileName, (</>))
import Language.Haskell.GhcOpts.Utils
componentName :: Component -> ComponentName
componentName =
foldComponent (const CLibName)
(CExeName . exeName)
(CTestName . testName)
(CBenchName . benchmarkName)
getComponentLocalBuildInfo :: LocalBuildInfo -> ComponentName -> ComponentLocalBuildInfo
#if __GLASGOW_HASKELL__ >= 707
getComponentLocalBuildInfo lbi cname = getLocalBuildInfo cname $ componentsConfigs lbi
where getLocalBuildInfo cname' ((cname'', clbi, _):cfgs) =
if cname' == cname'' then clbi else getLocalBuildInfo cname' cfgs
getLocalBuildInfo _ [] = error $ "internal error: missing config"
#else
getComponentLocalBuildInfo lbi CLibName =
case libraryConfig lbi of
Nothing -> error $ "internal error: missing library config"
Just clbi -> clbi
getComponentLocalBuildInfo lbi (CExeName name) =
case lookup name (executableConfigs lbi) of
Nothing -> error $ "internal error: missing config for executable " ++ name
Just clbi -> clbi
getComponentLocalBuildInfo lbi (CTestName name) =
case lookup name (testSuiteConfigs lbi) of
Nothing -> error $ "internal error: missing config for test suite " ++ name
Just clbi -> clbi
getComponentLocalBuildInfo lbi (CBenchName name) =
case lookup name (testSuiteConfigs lbi) of
Nothing -> error $ "internal error: missing config for benchmark " ++ name
Just clbi -> clbi
#endif
#if __GLASGOW_HASKELL__ >= 707
-- TODO: Fix callsites so we don't need `allComponentsBy`. It was taken from
-- http://hackage.haskell.org/package/Cabal-1.16.0.3/docs/src/Distribution-Simple-LocalBuildInfo.html#allComponentsBy
-- since it doesn't exist in Cabal 1.18.*
--
-- | Obtains all components (libs, exes, or test suites), transformed by the
-- given function. Useful for gathering dependencies with component context.
allComponentsBy :: PackageDescription
-> (Component -> a)
-> [a]
allComponentsBy pkg_descr f =
[ f (CLib lib) | Just lib <- [library pkg_descr]
, buildable (libBuildInfo lib) ]
++ [ f (CExe exe) | exe <- executables pkg_descr
, buildable (buildInfo exe) ]
++ [ f (CTest tst) | tst <- testSuites pkg_descr
, buildable (testBuildInfo tst)
, testEnabled tst ]
++ [ f (CBench bm) | bm <- benchmarks pkg_descr
, buildable (benchmarkBuildInfo bm)
, benchmarkEnabled bm ]
#endif
stackifyFlags :: ConfigFlags -> Maybe StackConfig -> ConfigFlags
stackifyFlags cfg Nothing = cfg
stackifyFlags cfg (Just si) = cfg { configDistPref = toFlag dist
, configPackageDBs = pdbs
}
where
pdbs = [Nothing, Just GlobalPackageDB] ++ pdbs'
pdbs' = Just . SpecificPackageDB <$> stackDbs si
dist = stackDist si
-- via: https://groups.google.com/d/msg/haskell-stack/8HJ6DHAinU0/J68U6AXTsasJ
-- cabal configure --package-db=clear --package-db=global --package-db=$(stack path --snapshot-pkg-db) --package-db=$(stack path --local-pkg-db)
-- returns the right 'dist' directory in the case of a sandbox
getDistDir :: FilePath -> IO FilePath
getDistDir path = do
let dir = takeDirectory path </> "dist"
contents <- getDirectoryContentsIfExists dir
return $ case find ("dist-sandbox-" `isPrefixOf`) contents of
Just sbdir -> dir </> sbdir
Nothing -> dir
getPackageGhcOpts :: FilePath -> Maybe StackConfig -> [String] -> IO (Either String [String])
getPackageGhcOpts path mbStack opts = do
-- putStrLn $ "getPackageGhcOpts: path = " ++ path
getPackageGhcOpts' `catch` (\e -> return $ Left $ "Cabal error: " ++ ioeGetErrorString (e :: IOException))
where
getPackageGhcOpts' :: IO (Either String [String])
getPackageGhcOpts' = do
genPkgDescr <- readPackageDescription silent path
distDir <- getDistDir path
let programCfg = defaultProgramConfiguration
let initCfgFlags = (defaultConfigFlags programCfg)
{ configDistPref = toFlag distDir
-- TODO: figure out how to find out this flag
, configUserInstall = toFlag True
-- configure with --enable-tests to include test dependencies/modules
, configTests = toFlag True
-- configure with --enable-benchmarks to include benchmark dependencies/modules
, configBenchmarks = toFlag True
}
let initCfgFlags' = stackifyFlags initCfgFlags mbStack
cfgFlags <- flip execStateT initCfgFlags' $ do
let sandboxConfig = takeDirectory path </> "cabal.sandbox.config"
exists <- lift $ doesFileExist sandboxConfig
when exists $ do
sandboxPackageDb <- lift $ getSandboxPackageDB sandboxConfig
modify $ \x -> x { configPackageDBs = [Just sandboxPackageDb] }
let cmdUI = configureCommand programCfg
case commandParseArgs cmdUI True opts of
CommandReadyToGo (modFlags, _) -> modify modFlags
CommandErrors (e:_) -> error e
_ -> return ()
localBuildInfo <- configure (genPkgDescr, emptyHookedBuildInfo) cfgFlags
let pkgDescr = localPkgDescr localBuildInfo
let baseDir = fst . splitFileName $ path
case getGhcVersion localBuildInfo of
Nothing -> return $ Left "GHC is not configured"
Just _ -> do
let mbLibName = pkgLibName pkgDescr
let ghcOpts' = foldl' mappend mempty $ map (getComponentGhcOptions localBuildInfo) $ flip allComponentsBy (\c -> c) . localPkgDescr $ localBuildInfo
-- FIX bug in GhcOptions' `mappend`
ghcOpts = ghcOpts' { ghcOptExtra = overNubListR (filter (/= "-Werror")) $ ghcOptExtra ghcOpts'
, ghcOptPackageDBs = sort $ nub (ghcOptPackageDBs ghcOpts')
, ghcOptPackages = overNubListR (filter (\(_, pkgId, _) -> Just (pkgName pkgId) /= mbLibName)) $ (ghcOptPackages ghcOpts')
, ghcOptSourcePath = overNubListR (map (baseDir </>)) (ghcOptSourcePath ghcOpts')
}
-- putStrLn "configuring"
(ghcInfo,_,_) <- GHC.configure silent Nothing Nothing defaultProgramConfiguration
return $ Right $ renderGhcOptions ghcInfo ghcOpts
pkgLibName :: PackageDescription -> Maybe PackageName
pkgLibName pkgDescr = if hasLibrary pkgDescr
then Just $ pkgName . package $ pkgDescr
else Nothing
hasLibrary :: PackageDescription -> Bool
hasLibrary = maybe False (\_ -> True) . library
getComponentGhcOptions :: LocalBuildInfo -> Component -> GhcOptions
getComponentGhcOptions lbi comp =
componentGhcOptions silent lbi bi clbi (buildDir lbi)
where bi = componentBuildInfo comp
clbi = getComponentLocalBuildInfo lbi (componentName comp)
getGhcVersion :: LocalBuildInfo -> Maybe Version
getGhcVersion lbi = let db = withPrograms lbi
in do ghc <- lookupProgram (simpleProgram "ghc") db
programVersion ghc
getSandboxPackageDB :: FilePath -> IO PackageDB
getSandboxPackageDB sandboxPath = do
contents <- readFile sandboxPath
return $ SpecificPackageDB $ extractValue . parse $ contents
where
pkgDbKey = "package-db:"
parse = head . filter (pkgDbKey `isPrefixOf`) . lines
extractValue = fst . break isSpace . dropWhile isSpace . drop (length pkgDbKey)
findCabalFile :: Maybe FilePath -> IO (Maybe FilePath)
findCabalFile f = go =<< maybe getCurrentDirectory (return . takeDirectory) f
where
go dir = do
allFiles <- getDirectoryContents dir
let mbCabalFile = find isCabalFile allFiles
case mbCabalFile of
Just cabalFile -> return $ Just $ dir </> cabalFile
Nothing ->
let parentDir = takeDirectory dir in
if parentDir == dir then return Nothing else go parentDir
isCabalFile :: FilePath -> Bool
isCabalFile path = cabalExtension `isSuffixOf` path &&
length path > length cabalExtension
where
cabalExtension = ".cabal"
--------------------------------------------------------------------------------
mkCabalConfig :: FilePath -> [String] -> IO CabalConfig
--------------------------------------------------------------------------------
mkCabalConfig path opts = do
fileStatus <- getFileStatus path
return CabalConfig { cabalConfigPath = path
, cabalConfigOpts = opts
, cabalConfigLastUpdatedAt = modificationTime fileStatus
}