packages feed

cabal-dev-0.9.2: src/Distribution/Dev/InvokeCabal.hs

{-# LANGUAGE CPP #-}
module Distribution.Dev.InvokeCabal
    ( actions
    , setup
    , extraArgs
    , cabalArgs
    )
where

import Control.Applicative ( (<$>) )
import Distribution.Verbosity ( Verbosity, showForCabal )
import Distribution.Simple.Program ( Program( programFindLocation )
                                   , ConfiguredProgram
                                   , emptyProgramConfiguration
                                   , locationPath
                                   , programLocation
                                   , requireProgram
                                   , runProgram
                                   , simpleProgram
                                   )
import Distribution.Simple.Utils ( writeUTF8File, debug, cabalVersion)
import Distribution.ParseUtils ( Field )
import Distribution.Version ( Version(..) )
import System.Console.GetOpt  ( OptDescr )

import Distribution.Dev.Command            ( CommandActions(..)
                                           , CommandResult(..)
                                           )
import Distribution.Dev.Flags              ( Config, getCabalConfig
                                           , getVerbosity, passthroughArgs
                                           , cfgCabalInstall, extraConfigFiles
                                           , useUserConfig
                                           )
import Distribution.Dev.InitPkgDb          ( initPkgDb )
import qualified Distribution.Dev.RewriteCabalConfig as R
import qualified Distribution.Dev.CabalInstall as CI
import Distribution.Dev.Sandbox            ( resolveSandbox
                                           , cabalConf
                                           , Sandbox
                                           , KnownVersion
                                           , PackageDbType(..)
                                           , getVersion
                                           , pkgConf
                                           , sandbox
                                           )
import Distribution.Dev.Utilities          ( ensureAbsolute )
import Distribution.Dev.MergeCabalConfig   ( mergeFields, removeFlaggedFields )

actions :: CI.CabalCommand -> CommandActions
actions cc = CommandActions
              { cmdDesc = "Invoke cabal-install with the development configuration"
              , cmdRun = \flgs _ args -> invokeCabal flgs cc args
              , cmdOpts = [] :: [OptDescr ()]
              , cmdPassFlags = True
              }

invokeCabal :: Config -> CI.CabalCommand -> [String] -> IO CommandResult
invokeCabal flgs cc args = do
  let v = getVerbosity flgs
  cabal <- CI.findOnPath v $ cfgCabalInstall flgs
  res <- cabalArgs cabal flgs cc
  case res of
    Left err -> return $ CommandError err
    Right args' -> do
             runProgram v cabal $ args' ++ args
             return CommandOk

cabalArgs :: ConfiguredProgram -> Config -> CI.CabalCommand -> IO (Either String [String])
cabalArgs cabal flgs cc = do
  let v = getVerbosity flgs
  s <- initPkgDb v =<< resolveSandbox flgs
  setup s cabal flgs cc

getUserConfigFields :: CI.CabalFeatures -> IO [Field]
getUserConfigFields fs =
    -- If we fail to read the file, then it could be that it doesn't yet
    -- exist, and it's OK to ignore.
    either (const []) id <$> (R.readConfigF =<< CI.getUserConfig fs)

-- XXX: this should return an error string instead of calling "error"
-- on failure.
getDevConfigFields :: Config -> IO [Field]
getDevConfigFields cfg = R.readConfigF_ =<< getCabalConfig cfg

setup :: Sandbox KnownVersion -> ConfiguredProgram -> Config ->
         CI.CabalCommand -> IO (Either String [String])
setup s cabal flgs cc = do
  let v = getVerbosity flgs
  cVer <- CI.getFeatures v cabal
  devFields <- getDevConfigFields flgs
  extraConfigs <- mapM R.readConfigF_ $ extraConfigFiles flgs
  let cfgOut = cabalConf s
  case cVer of
    Left err -> return $ Left err
    Right features -> do
      userFields <- if useUserConfig flgs
                    then getUserConfigFields features
                    else return []
      cabalHome <- CI.configDir features
      let rew = R.Rewrite cabalHome (sandbox s) (pkgConf s) (CI.needsQuotes features)
          cOut = show $ R.ppTopLevel $ concat $
                 R.rewriteCabalConfig rew $
                 removeFlaggedFields $
                 foldr (flip mergeFields) userFields (devFields:extraConfigs)

      writeUTF8File cfgOut cOut
      (gOpts, cOpts) <- extraArgs v cfgOut (getVersion s)
      let gFlags = map toArg gOpts
          cFlags = map toArg $ filter (CI.supportsLongOption cc . fst) cOpts
          args = concat
                 [ -- global cabal-install flags, as
                   -- generated by cabal-dev
                   gFlags

                 -- The cabal command name
                 , [ CI.commandToString cc ]

                 -- command-specific flags, as generated
                 -- by cabal-dev
                 , cFlags

                 -- Arguments that the user specified
                 -- that we pass through
                 , passthroughArgs flgs
                 ]

      debug v $ "Complete arguments to cabal-install: " ++ show args
      return $ Right args

toArg :: Option -> String
toArg (a, mb) = showString "--" .
                showString a $ maybe "" ('=':) mb

-- option name, value
type Option = (String, Maybe String)
type Options = [Option]

extraArgs :: Verbosity -> FilePath -> PackageDbType -> IO (Options, Options)
extraArgs v cfg pdb =
    do pdbArgs <- getPdbArgs
       return ([cfgFileArg], verbosityArg:pdbArgs)
    where
      longArg s = (,) s . Just
      cfgFileArg = longArg "config-file" cfg
      verbosityArg = longArg "verbose" $ showForCabal v
      withGhcPkg = longArg "with-ghc-pkg"
      getPdbArgs =
          case pdb of
            (GHC_6_8_Db loc) | needsGHC68Compat -> do
                     -- Make Cabal call the wrapper that removes the
                     -- bad argument to ghc-pkg 6.8
                     debug v $ "Using GHC 6.8 compatibility wrapper for Cabal shortcoming"
                     (ghcPkgCompat, _) <-
                         requireProgram v ghcPkgCompatProgram emptyProgramConfiguration
                     return $ [ longArg "ghc-pkg-options" $ toArg $ withGhcPkg loc
                              , withGhcPkg $ locationPath $
                                programLocation ghcPkgCompat
                              ]
            _ -> return []

-- XXX: this is very imprecise. Right now, we require a specific
-- version of Cabal, so this is ok (and is equivalent to True). Note
-- that this is the version of Cabal that THIS PROGRAM is being built
-- against, rather than the version that CABAL-INSTALL was built
-- against.
needsGHC68Compat :: Bool
needsGHC68Compat = cabalVersion < Version [1, 9] []

ghcPkgCompatProgram :: Program
ghcPkgCompatProgram  = p { programFindLocation =
                           \v -> do
                             res <- programFindLocation p v
                             case res of
                               Nothing -> return Nothing
                               Just loc -> Just `fmap` ensureAbsolute loc
                         }
    where
      p = simpleProgram "ghc-pkg-6_8-compat"