packages feed

cabal-dev-0.8: src/Distribution/Dev/CabalInstall.hs

{-# LANGUAGE TemplateHaskell #-}
module Distribution.Dev.CabalInstall
       ( findOnPath
       , program
       , getFeatures
       , CabalFeatures
       , needsQuotes
       , hasOnlyDependencies
       , configDir
       , CabalCommand(..)
       , Option(..)
       , OptionName(..)
       , ArgType(..)
       , matchLongOption
       , commandToString
       , stringToCommand
       , allCommands
       , commandOptions
       , supportsLongOption
       , supportedOptions
       , getUserConfig
       )
where

import Data.Maybe ( fromMaybe )
import Control.Applicative ( (<$>), pure )
import System.FilePath ( (</>) )
import System.Environment ( getEnvironment )
import Distribution.Version ( Version(..), withinRange
                            , earlierVersion, orLaterVersion )
import Distribution.Verbosity ( Verbosity )
import Distribution.Simple.Program ( Program( programFindVersion
                                            )
                                   , ConfiguredProgram
                                   , emptyProgramConfiguration
                                   , findProgramVersion
                                   , programLocation
                                   , programVersion
                                   , requireProgram
                                   , getProgramOutput
                                   , simpleProgram
                                   )
import Distribution.Simple.Utils ( debug )
import Distribution.Text ( display, simpleParse )

import System.Directory ( getAppUserDataDirectory )

import Distribution.Dev.InterrogateCabalInstall
    ( Option(..), OptionName(..), ArgType(..) )
import Distribution.Dev.TH.DeriveCabalCommands
    ( deriveCabalCommands )

-- XXX This is duplicated in Setup.hs
-- |Definition of the cabal-install program
program :: Maybe String -> Program
program p =
    (simpleProgram $ fromMaybe "cabal" p) { programFindVersion =
                                  findProgramVersion "--numeric-version" id
                            }

-- |Find cabal-install on the user's PATH
findOnPath :: Verbosity -> Maybe FilePath -> IO ConfiguredProgram
findOnPath v ci = do
  (cabal, _) <- requireProgram v (program ci) emptyProgramConfiguration
  debug v $ concat [ "Using cabal-install "
                   , maybe "(unknown version)" display $ programVersion cabal
                   , " at "
                   , show (programLocation cabal)
                   ]
  return cabal

-- |Parse the Cabal library version from the output of cabal --version
parseVersionOutput :: String -> Either String Version
parseVersionOutput str =
    case lines str of
      []      -> Left "No version string provided."
      [_]     -> Left "Could not find Cabal version line."
      (_:ln:_) -> case simpleParse ((words ln)!!2) of
                   Just v  -> Right v
                   Nothing -> Left $ err ln
        where err line = "Could not parse Cabal verison.\n"
                         ++ "(simpleParse "++show line++")"

-- |The information necessary to properly invoke cabal-install
data CabalFeatures = CabalFeatures { cfLibVersion :: Version
                                   , cfExeVersion :: Version
                                   }

mkVer :: [Int] -> Version
mkVer l = Version l []

-- |Extract the features of this cabal-install executable
getFeatures :: Verbosity -> ConfiguredProgram ->
               IO (Either String CabalFeatures)
getFeatures v cabal = do
  case programVersion cabal of
    Nothing -> return $ Left "Failed to find cabal-install version"
    Just exeVer -> do
      verRes <- parseVersionOutput <$> getProgramOutput v cabal ["--version"]
      case verRes of
        Left err -> return $ Left $ "Detecting cabal-install's Cabal: " ++ err
        Right libVer -> return $ Right $
                        CabalFeatures { cfLibVersion = libVer
                                      , cfExeVersion = exeVer
                                      }

-- |Does the cabal-install configuration file use quoted paths in the
-- install-dirs section?
needsQuotes :: CabalFeatures -> Bool
needsQuotes = (`withinRange` earlierVersion (mkVer [1,10])) . cfLibVersion

-- |Does this cabal-install executable support the --dependencies-only
-- flag to install?
hasOnlyDependencies :: CabalFeatures -> Bool
hasOnlyDependencies =
  (`withinRange` orLaterVersion (mkVer [0, 10])) . cfExeVersion

$(deriveCabalCommands)

supportsLongOption :: CabalCommand -> String -> Bool
supportsLongOption cc s = any ((`matchLongOption` s) . optionName) $ supportedOptions cc

optionName :: Option -> OptionName
optionName (Option n _) = n

supportedOptions :: CabalCommand -> [Option]
supportedOptions cc = commonOptions ++ commandOptions cc

matchLongOption :: OptionName -> String -> Bool
matchLongOption (Short _) = const False
matchLongOption (LongOption s) = (== s)

commonOptions :: [Option]
commonOptions = [Option (LongOption "config-file") Req]

-- |What is the configuration directory for this cabal-install executable?

-- XXX: This needs to do something different for certain platforms for
-- new versions of cabal-install (look at the tickets on creswick's
-- cabal-dev repo)
configDir :: CabalFeatures -> IO FilePath
configDir _ = getAppUserDataDirectory "cabal"

getUserConfig :: CabalFeatures -> IO FilePath
getUserConfig cf = do
  env <- lookup "CABAL_CONFIG" <$> getEnvironment
  case env of
    Nothing -> (</> "config") <$> configDir cf
    Just f  -> pure f