cabal-cache-1.2.0.0: src/HaskellWorks/CabalCache/Core.hs
module HaskellWorks.CabalCache.Core
( PackageInfo(..),
Tagged(..),
Presence(..),
getPackages,
relativePaths,
loadPlan,
mkCompilerContext,
) where
import Control.DeepSeq (NFData)
import Data.Aeson (eitherDecode)
import Effectful
import Effectful.Zoo.Core
import Effectful.Zoo.Core.Error.Static
import Effectful.Zoo.Core.Exception
import HaskellWorks.CabalCache.Error (DecodeError(..))
import HaskellWorks.Prelude
import System.FilePath ((<.>), (</>))
import qualified Data.ByteString.Lazy as LBS
import qualified Data.List as L
import qualified Data.Text as T
import qualified HaskellWorks.CabalCache.IO.Tar as IO
import qualified HaskellWorks.CabalCache.Types as Z
import qualified System.Directory as IO
import qualified System.Info as I
import qualified System.Process as IO
{- HLINT ignore "Monoid law, left identity" -}
type PackageDir = FilePath
type ConfPath = FilePath
type Library = FilePath
data Presence = Present | Absent deriving (Eq, Show, NFData, Generic)
data Tagged a t = Tagged
{ value :: a
, tag :: t
} deriving (Eq, Show, Generic, NFData)
data PackageInfo = PackageInfo
{ compilerId :: Z.CompilerId
, packageId :: Z.PackageId
, packageName :: Z.PackageName
, packageDir :: PackageDir
, confPath :: Tagged ConfPath Presence
, libs :: [Library]
} deriving (Show, Eq, Generic, NFData)
isPosix :: Bool
isPosix = I.os /= "mingw32"
{-# NOINLINE isPosix #-}
exeExt :: String
exeExt
| isPosix = ""
| otherwise = ".exe"
withExeExt :: FilePath -> FilePath
withExeExt = (<.> exeExt)
withExeExt' :: Text -> Text
withExeExt' = T.pack . withExeExt . T.unpack
findExecutable :: ()
=> r <: Error Text
=> r <: IOE
=> Text
-> Eff r Text
findExecutable exe =
liftIO (fmap T.pack <$> IO.findExecutable (T.unpack exe))
& onNothingM (throw (exe <> " is not in path"))
runGhcPkg :: ()
=> r <: Error Text
=> r <: IOE
=> Text
-> [Text]
-> Eff r Text
runGhcPkg cmdExe args = catchIO (liftIO $ T.pack <$> IO.readProcess (T.unpack cmdExe) (fmap T.unpack args) "") $
\(e :: IOError) -> throw $ "Unable to run " <> cmdExe <> " " <> T.unwords args <> ": " <> tshow e
verifyGhcPkgVersion :: ()
=> r <: Error Text
=> r <: IOE
=> Text
-> Text
-> Eff r Text
verifyGhcPkgVersion version cmdExe = do
stdout <- runGhcPkg cmdExe ["--version"]
if T.isSuffixOf (" " <> version) (mconcat (L.take 1 (T.lines stdout)))
then return cmdExe
else throw $ cmdExe <> " is not of version " <> version
mkCompilerContext :: ()
=> r <: Error Text
=> r <: IOE
=> Z.PlanJson
-> Eff r Z.CompilerContext
mkCompilerContext plan = do
compilerVersion <- T.stripPrefix "ghc-" plan.compilerId
& onNothing (throw @Text "No compiler version available in plan")
let versionedGhcPkgCmd = "ghc-pkg-" <> compilerVersion
ghcPkgCmdPath <- (findExecutable (withExeExt' versionedGhcPkgCmd) >>= verifyGhcPkgVersion compilerVersion)
& trap_ @Text (findExecutable (withExeExt' "ghc-pkg" ) >>= verifyGhcPkgVersion compilerVersion)
return (Z.CompilerContext [T.unpack ghcPkgCmdPath])
relativePaths :: FilePath -> PackageInfo -> [IO.TarGroup]
relativePaths basePath pInfo =
[ IO.TarGroup basePath $ mempty
<> pInfo.libs
<> [pInfo.packageDir]
, IO.TarGroup basePath $ mempty
<> ([pInfo.confPath] & L.filter (\c -> c.tag == Present) <&> (.value))
]
getPackages :: FilePath -> Z.PlanJson -> IO [PackageInfo]
getPackages basePath planJson = forM packages (mkPackageInfo basePath compilerId')
where compilerId' :: Text
compilerId' = planJson.compilerId
packages :: [Z.Package]
packages = planJson.installPlan
loadPlan :: ()
=> r <: Error DecodeError
=> r <: IOE
=> FilePath
-> Eff r Z.PlanJson
loadPlan resolvedBuildPath = do
lbs <- liftIO (LBS.readFile (resolvedBuildPath </> "cache" </> "plan.json"))
a <- eitherDecode lbs
& onLeft (throw . DecodeError . T.pack)
pure do a :: Z.PlanJson
-------------------------------------------------------------------------------
mkPackageInfo :: FilePath -> Z.CompilerId -> Z.Package -> IO PackageInfo
mkPackageInfo basePath cid pkg = do
let pid = pkg.id
let compilerPath = basePath </> T.unpack cid
let relativeConfPath = T.unpack cid </> "package.db" </> T.unpack pid <.> ".conf"
let absoluteConfPath = basePath </> relativeConfPath
let libPath = compilerPath </> "lib"
let relativeLibPath = T.unpack cid </> "lib"
let libPrefix = "libHS" <> pid
absoluteConfPathExists <- IO.doesFileExist absoluteConfPath
libFiles <- getLibFiles relativeLibPath libPath libPrefix
return PackageInfo
{ compilerId = cid
, packageId = pid
, packageName = pkg.name
, packageDir = T.unpack cid </> T.unpack pid
, confPath = Tagged relativeConfPath (bool Absent Present absoluteConfPathExists)
, libs = libFiles
}
getLibFiles :: FilePath -> FilePath -> Text -> IO [Library]
getLibFiles relativeLibPath libPath libPrefix = do
libExists <- IO.doesDirectoryExist libPath
if libExists
then fmap (relativeLibPath </>) . L.filter (L.isPrefixOf (T.unpack libPrefix)) <$> IO.listDirectory libPath
else pure []