cabal-ghci-0.2.0: Distribution/Dev/Interactive.hs
-- | Retrieve ghci options for your cabal project
module Distribution.Dev.Interactive (
-- * Installing into your .ghci
-- $install
-- * Arguments
-- $args
-- * Exported functions
cabalSet, packageOpts, loadCabal, lookForCabalFile, withOpts, LoadCabalRet(..)
) where
import Distribution.Text
import Distribution.Compiler
import Distribution.Verbosity
import Distribution.System
import Distribution.Package
import Distribution.PackageDescription
import Distribution.PackageDescription.Parse
import Distribution.PackageDescription.Configuration
import Distribution.Simple.PackageIndex
import Distribution.Simple.LocalBuildInfo hiding (compiler)
import Data.Version
import System.FilePath
import System.Directory
import System.Info
import Data.Maybe
import Data.Either
import Control.Exception
import Data.List
type Deps = [(PackageName, Version)]
-- | Return value for 'loadCabal'
data LoadCabalRet =
NoCabalFile | -- ^ No cabal file found
MissingDeps [Dependency] | -- ^ Missing dependencies
Pkg FilePath PackageDescription (Maybe LocalBuildInfo) -- ^ Successful loading and parsing of cabal file
deriving Show
compiler ∷ CompilerId
compiler = CompilerId buildCompilerFlavor compilerVersion
-- | Build a list of ghci options needed to load files from a cabal project
packageOpts
∷ FilePath -- ^ path to the .cabal file
→ PackageDescription -- ^ parsed package description
→ Maybe LocalBuildInfo -- ^ parsed build config
→ String -- ^ name of executable
→ Maybe [String]
packageOpts path pkg mlbi executable =
maybe Nothing (\bi → Just $ ghcOpts path bi (listDeps bi =<< mlbi)) $
listToMaybe $
if executable == ""
then allBuildInfo pkg
else
fmap buildInfo .
filter (\x → exeName x == executable) .
executables $ pkg
listDeps ∷ BuildInfo → LocalBuildInfo → Maybe Deps
listDeps bi lbi = sequence $ map find reqs
where
reqs = targetBuildDepends bi
db = installedPkgs lbi
find dep@(Dependency pkg _) = do
(fst . head → ver) ← return $ lookupDependency db dep
return (pkg, ver)
-- | GHC options for a 'BuildInfo'
ghcOpts ∷ FilePath → BuildInfo → Maybe Deps → [String]
ghcOpts path bi deps = concat $
maybe [] ((noPkgs:) . add "-package=" . map addDep) deps :
map ($ bi) [
hcOptions buildCompilerFlavor,
addf "-X" display . allExtensions,
addf "-i" (combine dir) . ("/dist/build/autogen":) . hsSourceDirs,
add "-optP" . cppOptions,
add "-optc" . ccOptions,
add "-optl" . ldOptions
-- TODO frameworks cSources otherModules extraLibs extraLibsDirs includes
]
where
dir = takeDirectory path
add s = map (s++)
addf ∷ String → (a → String) → [a] → [String]
addf s f = map ((s++) . f)
noPkgs = "-hide-all-packages"
addDep (PackageName pkg, showVersion → ver) = pkg ++ "-" ++ ver
-- | Load the current cabal project file and parse it
loadCabal
∷ FilePath -- ^ usually the current directory
→ FlagAssignment -- ^ list of cabal flag assignments
→ IO LoadCabalRet
loadCabal path flags = do
mCabalFile ← lookForCabalFile =<< canonicalizePath path
flip (maybe (return NoCabalFile))
mCabalFile $ \cabalFile → do
gdescr ← readPackageDescription normal cabalFile
mlbi ← loadCabalConfig (takeDirectory cabalFile `combine` "dist" `combine` "setup-config")
case finalizePackageDescription flags (const True)
buildPlatform compiler [] gdescr of
Left deps → return $ MissingDeps deps
Right (descr, _) → return $ Pkg cabalFile descr mlbi
maybeNth ∷ Int → [a] → Maybe a
maybeNth 0 (x:_) = Just x
maybeNth n (_:xs) = maybeNth (n-1) xs
maybeNth _ _ = Nothing
maybeRead s = case reads s of [(a, "")] → Just a; _ → Nothing
-- | Load the 'LocalBuildInfo' from dist/setup-config
loadCabalConfig ∷ FilePath → IO (Maybe LocalBuildInfo)
loadCabalConfig path =
-- TODO: don't ignore all exceptions
fmap (either ignore id) $ try $
fmap ((maybeRead =<<) . maybeNth 1 . lines) $ readFile path
where
ignore ∷ SomeException → Maybe a
ignore _ = Nothing
-- | Find a .cabal file in the path or any of it's parent directories
lookForCabalFile
∷ FilePath -- ^ canonicalised path
→ IO (Maybe FilePath)
lookForCabalFile "/" = return Nothing
lookForCabalFile path = do
files ← getDirectoryContents path
let cabals = filter (\f →
takeExtension f == ".cabal"
&& f /= ".cabal") files
case cabals of
[] → lookForCabalFile (takeDirectory path)
[a] → return $ Just $ combine path a
_ → return Nothing
-- | 'cabalSet' returns a list of ghci commands (seperated by newlines) that :set the 'packageOpts' of the current cabal project
cabalSet
∷ String -- ^ arguments seperated by spaces
→ IO String
cabalSet args =
withOpts (words args)
(\x → putStrLn x >> return "")
((\x → putStrLn x >> return x) .
unlines . (map (":set "++)) . map show )
-- | Generalised version of 'cabalSet'
withOpts
∷ [String] -- ^ List of cabal flag arguments and executable name
→ (String → IO a) -- ^ Error continuation. Recieves an error message.
→ ([String] → IO a) -- ^ Success continuation. Recieves a list of ghci arguments.
→ IO a
withOpts args err go = do
let (flags, executable) = parseArgs args
here ← getCurrentDirectory
ret ← loadCabal here flags
case ret of
NoCabalFile → err "Current directory is not a cabal project"
MissingDeps deps → err $ "Missing dependencies: " ++ unwords (map show deps)
Pkg path descr mlbi → do
let mopts = packageOpts path descr mlbi executable
case mopts of
Nothing → err (
if executable /= ""
then "No such executable in cabal file"
else "No library defined in cabal file")
Just opts → go opts
parseArgs ∷ [String] → (FlagAssignment, String)
parseArgs args =
(map (makeFlag . drop 2) . filter flag $ args,
fromMaybe "" . listToMaybe . filter (not . flag) $ args)
where
flag x = take 2 x == "-f"
makeFlag ∷ String → (FlagName, Bool)
makeFlag ('-':f) = (FlagName f, False)
makeFlag f = (FlagName f, True)
-- $install
-- @
-- $ head -n 4 >> ~/.ghci
-- :m + Distribution.Dev.Interactive
-- :def cabalset cabalSet
-- :cabalset -fcabal-ghci
-- :m - Distribution.Dev.Interactive
-- @
-- $args
-- [@-fflag@] enable flag
--
-- [@-f-flag@] disable flag
--
-- [@exec@] load options for the exec executable