content: {-# LANGUAGE DeriveGeneric #-}
  {-# LANGUAGE DeriveDataTypeable #-}
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Distribution.Solver.Types.PkgConfigDb
  -- Copyright   :  (c) Iñaki García Etxebarria 2016
  -- License     :  BSD-like
  --
  -- Maintainer  :  cabal-devel@haskell.org
  -- Portability :  portable
  --
  -- Read the list of packages available to pkg-config.
  -----------------------------------------------------------------------------
  module Distribution.Solver.Types.PkgConfigDb
      ( PkgConfigDb
      , readPkgConfigDb
      , pkgConfigDbFromList
      , pkgConfigPkgIsPresent
      , pkgConfigDbPkgVersion
      , getPkgConfigDbDirs
      ) where
  
  import Prelude ()
  import Distribution.Client.Compat.Prelude
  
  import Control.Exception (IOException, handle)
  import qualified Data.Map as M
  import Data.Version (parseVersion)
  import Text.ParserCombinators.ReadP (readP_to_S)
  import System.FilePath (splitSearchPath)
  
  import Distribution.Package
      ( PkgconfigName, mkPkgconfigName )
  import Distribution.Verbosity
      ( Verbosity )
  import Distribution.Version
      ( Version, mkVersion', VersionRange, withinRange )
  
  import Distribution.Compat.Environment
      ( lookupEnv )
  import Distribution.Simple.Program
      ( ProgramDb, pkgConfigProgram, getProgramOutput, requireProgram )
  
  -- | The list of packages installed in the system visible to
  -- @pkg-config@. This is an opaque datatype, to be constructed with
  -- `readPkgConfigDb` and queried with `pkgConfigPkgPresent`.
  data PkgConfigDb =  PkgConfigDb (M.Map PkgconfigName (Maybe Version))
                   -- ^ If an entry is `Nothing`, this means that the
                   -- package seems to be present, but we don't know the
                   -- exact version (because parsing of the version
                   -- number failed).
                   | NoPkgConfigDb
                   -- ^ For when we could not run pkg-config successfully.
       deriving (Show, Generic, Typeable)
  
  instance Binary PkgConfigDb
  
  -- | Query pkg-config for the list of installed packages, together
  -- with their versions. Return a `PkgConfigDb` encapsulating this
  -- information.
  readPkgConfigDb :: Verbosity -> ProgramDb -> IO PkgConfigDb
  readPkgConfigDb _verbosity _progdb = return $ pkgConfigDbFromList []
  
  -- | Create a `PkgConfigDb` from a list of @(packageName, version)@ pairs.
  pkgConfigDbFromList :: [(String, String)] -> PkgConfigDb
  pkgConfigDbFromList pairs = (PkgConfigDb . M.fromList . map convert) pairs
      where
        convert :: (String, String) -> (PkgconfigName, Maybe Version)
        convert (n,vs) = (mkPkgconfigName n,
                          case (reverse . readP_to_S parseVersion) vs of
                            (v, "") : _ -> Just (mkVersion' v)
                            _           -> Nothing -- Version not (fully)
                                                   -- understood.
                         )
  
  -- | Check whether a given package range is satisfiable in the given
  -- @pkg-config@ database.
  pkgConfigPkgIsPresent :: PkgConfigDb -> PkgconfigName -> VersionRange -> Bool
  pkgConfigPkgIsPresent (PkgConfigDb db) pn vr =
      case M.lookup pn db of
        Nothing       -> False    -- Package not present in the DB.
        Just Nothing  -> True     -- Package present, but version unknown.
        Just (Just v) -> withinRange v vr
  -- If we could not read the pkg-config database successfully we allow
  -- the check to succeed. The plan found by the solver may fail to be
  -- executed later on, but we have no grounds for rejecting the plan at
  -- this stage.
  pkgConfigPkgIsPresent NoPkgConfigDb _ _ = True
  
  
  -- | Query the version of a package in the @pkg-config@ database.
  -- @Nothing@ indicates the package is not in the database, while
  -- @Just Nothing@ indicates that the package is in the database,
  -- but its version is not known.
  pkgConfigDbPkgVersion :: PkgConfigDb -> PkgconfigName -> Maybe (Maybe Version)
  pkgConfigDbPkgVersion (PkgConfigDb db) pn = M.lookup pn db
  -- NB: Since the solver allows solving to succeed if there is
  -- NoPkgConfigDb, we should report that we *guess* that there
  -- is a matching pkg-config configuration, but that we just
  -- don't know about it.
  pkgConfigDbPkgVersion NoPkgConfigDb _ = Just Nothing
  
  
  -- | Query pkg-config for the locations of pkg-config's package files. Use this
  -- to monitor for changes in the pkg-config DB.
  --
  getPkgConfigDbDirs :: Verbosity -> ProgramDb -> IO [FilePath]
  getPkgConfigDbDirs verbosity progdb =
      (++) <$> getEnvPath <*> getDefPath
   where
      -- According to @man pkg-config@:
      --
      -- PKG_CONFIG_PATH
      -- A  colon-separated  (on Windows, semicolon-separated) list of directories
      -- to search for .pc files.  The default directory will always be searched
      -- after searching the path
      --
      getEnvPath = maybe [] parseSearchPath
               <$> lookupEnv "PKG_CONFIG_PATH"
  
      -- Again according to @man pkg-config@:
      --
      -- pkg-config can be used to query itself for the default search path,
      -- version number and other information, for instance using:
      --
      -- > pkg-config --variable pc_path pkg-config
      --
      getDefPath = handle ioErrorHandler $ do
        (pkgConfig, _) <- requireProgram verbosity pkgConfigProgram progdb
        parseSearchPath <$>
          getProgramOutput verbosity pkgConfig
                           ["--variable", "pc_path", "pkg-config"]
  
      parseSearchPath str =
        case lines str of
          [p] | not (null p) -> splitSearchPath p
          _                  -> []
  
      ioErrorHandler :: IOException -> IO [FilePath]
      ioErrorHandler _e = return []
  