packages feed

hackage-db-2.1.3: src/Distribution/Hackage/DB/Path.hs

{- |
   Module      :  Distribution.Hackage.DB.Path
   License     :  BSD3
   Maintainer  :  simons@cryp.to
   Stability   :  provisional
   Portability :  portable

   Find the location of the local Hackage database that is maintained by running
   @cabal update@.
 -}

module Distribution.Hackage.DB.Path where

import Distribution.Hackage.DB.Errors

import Control.Exception
import System.Directory
import System.Environment (lookupEnv)
import System.FilePath

-- |
-- Determines the /state/ directory (which e.g. holds the hackage tarball)
-- cabal-install uses via the following logic:
--
-- 1. If the @CABAL_DIR@ environment variable is set, its content is used as the
--    cabal state directory
-- 2. If @~/.cabal@ (see 'getAppUserDataDirectory') exists, use that.
-- 3. Otherwise, use @${XDG_CACHE_HOME}/cabal@ (see @'getXdgDirectory' 'XdgCache'@)
--    which is the new directory cabal-install can use starting with
--    version @3.10.*@.
--
-- This logic is mostly equivalent to what upstream cabal-install is
-- [doing](https://github.com/haskell/cabal/blob/0ed12188525335ac9759dc957d49979ab09382a1/cabal-install/src/Distribution/Client/Config.hs#L594-L610)
-- with the following exception:
-- The state directory can freely be configured to use a different location
-- in the cabal-install configuration file. hackage-db doesn't parse this
-- configuration file, so differing state directories are ignored.
cabalStateDir :: IO FilePath
cabalStateDir = do
  envCabal <- lookupEnv "CABAL_DIR"
  dotCabal <- getAppUserDataDirectory "cabal"
  dotCabalExists <- doesDirectoryExist dotCabal
  case envCabal of
    Just dir -> pure dir
    Nothing ->
      if dotCabalExists
      then pure dotCabal
      else getXdgDirectory XdgCache "cabal"

cabalTarballDir :: String -> IO FilePath
cabalTarballDir repo = do
  csd <- cabalStateDir
  return $ joinPath [csd, "packages", repo]

hackageTarballDir :: IO FilePath
hackageTarballDir = cabalTarballDir "hackage.haskell.org"

-- | Determine the default path of the Hackage database, which typically
-- resides in @$HOME\/.cabal\/packages\/hackage.haskell.org\/@.
-- Running the command @cabal update@ or @cabal v2-update@ will keep the index
-- up-to-date.
--
-- See 'cabalStateDir' on how @hackage-db@ searches for the cabal state directory.
hackageTarball :: IO FilePath
hackageTarball = do
  htd <- hackageTarballDir
  let idx00 = htd </> "00-index.tar"
      idx01 = htd </> "01-index.tar"
  -- Using 'msum' here would be nice, but unfortunetaly there was no reliable
  -- MonadPlus instance for IO in pre 8.x versions of GHC. So we use the ugly
  -- code for sake of portability.
  have01 <- doesFileExist idx01
  if have01 then return idx01 else do
    have00 <- doesFileExist idx00
    if have00 then return idx00 else
      throwIO NoHackageTarballFound