stack-1.6.1: src/Stack/GhcPkg.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# OPTIONS -fno-warn-unused-do-bind #-}
-- | Functions for the GHC package database.
module Stack.GhcPkg
(getGlobalDB
,findGhcPkgField
,createDatabase
,unregisterGhcPkgId
,getCabalPkgVer
,ghcPkgExeName
,ghcPkgPathEnvVar
,mkGhcPackagePath)
where
import Stack.Prelude
import qualified Data.ByteString.Char8 as S8
import Data.List
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Path (parent, mkRelFile, (</>))
import Path.Extra (toFilePathNoTrailingSep)
import Path.IO
import Stack.Constants
import Stack.Types.Build
import Stack.Types.GhcPkgId
import Stack.Types.PackageIdentifier
import Stack.Types.Compiler
import Stack.Types.PackageName
import Stack.Types.Version
import System.FilePath (searchPathSeparator)
import System.Process.Read
-- | Get the global package database
getGlobalDB :: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride -> WhichCompiler -> m (Path Abs Dir)
getGlobalDB menv wc = do
logDebug "Getting global package database location"
-- This seems like a strange way to get the global package database
-- location, but I don't know of a better one
bs <- ghcPkg menv wc [] ["list", "--global"] >>= either throwIO return
let fp = S8.unpack $ stripTrailingColon $ firstLine bs
liftIO $ resolveDir' fp
where
stripTrailingColon bs
| S8.null bs = bs
| S8.last bs == ':' = S8.init bs
| otherwise = bs
firstLine = S8.takeWhile (\c -> c /= '\r' && c /= '\n')
-- | Run the ghc-pkg executable
ghcPkg :: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride
-> WhichCompiler
-> [Path Abs Dir]
-> [String]
-> m (Either ReadProcessException S8.ByteString)
ghcPkg menv wc pkgDbs args = do
eres <- go
case eres of
Left _ -> do
mapM_ (createDatabase menv wc) pkgDbs
go
Right _ -> return eres
where
go = tryProcessStdout Nothing menv (ghcPkgExeName wc) args'
args' = packageDbFlags pkgDbs ++ args
-- | Create a package database in the given directory, if it doesn't exist.
createDatabase :: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride -> WhichCompiler -> Path Abs Dir -> m ()
createDatabase menv wc db = do
exists <- doesFileExist (db </> $(mkRelFile "package.cache"))
unless exists $ do
-- ghc-pkg requires that the database directory does not exist
-- yet. If the directory exists but the package.cache file
-- does, we're in a corrupted state. Check for that state.
dirExists <- doesDirExist db
args <- if dirExists
then do
logWarn $ T.pack $ concat
[ "The package database located at "
, toFilePath db
, " is corrupted (missing its package.cache file)."
]
logWarn "Proceeding with a recache"
return ["--package-db", toFilePath db, "recache"]
else do
-- Creating the parent doesn't seem necessary, as ghc-pkg
-- seems to be sufficiently smart. But I don't feel like
-- finding out it isn't the hard way
ensureDir (parent db)
return ["init", toFilePath db]
eres <- tryProcessStdout Nothing menv (ghcPkgExeName wc) args
case eres of
Left e -> do
logError $ T.pack $ "Unable to create package database at " ++ toFilePath db
throwIO e
Right _ -> return ()
-- | Get the name to use for "ghc-pkg", given the compiler version.
ghcPkgExeName :: WhichCompiler -> String
ghcPkgExeName Ghc = "ghc-pkg"
ghcPkgExeName Ghcjs = "ghcjs-pkg"
-- | Get the environment variable to use for the package DB paths.
ghcPkgPathEnvVar :: WhichCompiler -> Text
ghcPkgPathEnvVar Ghc = "GHC_PACKAGE_PATH"
ghcPkgPathEnvVar Ghcjs = "GHCJS_PACKAGE_PATH"
-- | Get the necessary ghc-pkg flags for setting up the given package database
packageDbFlags :: [Path Abs Dir] -> [String]
packageDbFlags pkgDbs =
"--no-user-package-db"
: map (\x -> "--package-db=" ++ toFilePath x) pkgDbs
-- | Get the value of a field of the package.
findGhcPkgField
:: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride
-> WhichCompiler
-> [Path Abs Dir] -- ^ package databases
-> String -- ^ package identifier, or GhcPkgId
-> Text
-> m (Maybe Text)
findGhcPkgField menv wc pkgDbs name field = do
result <-
ghcPkg
menv
wc
pkgDbs
["field", "--simple-output", name, T.unpack field]
return $
case result of
Left{} -> Nothing
Right lbs ->
fmap (stripCR . T.decodeUtf8) $ listToMaybe $ S8.lines lbs
-- | Get the version of the package
findGhcPkgVersion :: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride
-> WhichCompiler
-> [Path Abs Dir] -- ^ package databases
-> PackageName
-> m (Maybe Version)
findGhcPkgVersion menv wc pkgDbs name = do
mv <- findGhcPkgField menv wc pkgDbs (packageNameString name) "version"
case mv of
Just !v -> return (parseVersion v)
_ -> return Nothing
unregisterGhcPkgId :: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride
-> WhichCompiler
-> CompilerVersion 'CVActual
-> Path Abs Dir -- ^ package database
-> GhcPkgId
-> PackageIdentifier
-> m ()
unregisterGhcPkgId menv wc cv pkgDb gid ident = do
eres <- ghcPkg menv wc [pkgDb] args
case eres of
Left e -> logWarn $ T.pack $ show e
Right _ -> return ()
where
-- TODO ideally we'd tell ghc-pkg a GhcPkgId instead
args = "unregister" : "--user" : "--force" :
(case cv of
GhcVersion v | v < $(mkVersion "7.9") ->
[packageIdentifierString ident]
_ -> ["--ipid", ghcPkgIdString gid])
-- | Get the version of Cabal from the global package database.
getCabalPkgVer :: (MonadUnliftIO m, MonadLogger m)
=> EnvOverride -> WhichCompiler -> m Version
getCabalPkgVer menv wc = do
logDebug "Getting Cabal package version"
mres <- findGhcPkgVersion
menv
wc
[] -- global DB
cabalPackageName
maybe (throwIO $ Couldn'tFindPkgId cabalPackageName) return mres
-- | Get the value for GHC_PACKAGE_PATH
mkGhcPackagePath :: Bool -> Path Abs Dir -> Path Abs Dir -> [Path Abs Dir] -> Path Abs Dir -> Text
mkGhcPackagePath locals localdb deps extras globaldb =
T.pack $ intercalate [searchPathSeparator] $ concat
[ [toFilePathNoTrailingSep localdb | locals]
, [toFilePathNoTrailingSep deps]
, [toFilePathNoTrailingSep db | db <- reverse extras]
, [toFilePathNoTrailingSep globaldb]
]