packages feed

stack-3.9.1: src/Stack/Types/Cache.hs

{-# LANGUAGE NoImplicitPrelude   #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings   #-}

{-|
Module      : Stack.Types.Cache
License     : BSD-3-Clause
-}

module Stack.Types.Cache
  ( FileCache
  , BuildFileCache (..)
  , FileCacheInfo (..)
  , ConfigCache (..)
  , CachePkgSrc (..)
  , PrecompiledCache (..)
  , ConfigCacheType (..)
  , Action (..)
  ) where

import           Data.Aeson
                   ( ToJSON (..), FromJSON (..), (.=), (.:), object, withObject
                   )
import qualified Data.ByteString as S
import qualified Data.Text as T
import           Database.Persist.Sql
                   ( PersistField (..), PersistFieldSql (..), PersistValue (..)
                   , SqlType (..)
                   )
import           Stack.Prelude
import           Stack.Types.ConfigureOpts ( ConfigureOpts )
import           Stack.Types.GhcPkgId
                   ( GhcPkgId, ghcPkgIdToText, parseGhcPkgId )

-- | Type representing types of cache in the Stack project SQLite database.
data ConfigCacheType
  = ConfigCacheTypeConfig
    -- ^ Cabal configuration cache.
  | ConfigCacheTypeFlagLibrary GhcPkgId
    -- ^ Library Cabal flag cache.
  | ConfigCacheTypeFlagExecutable PackageIdentifier
    -- ^ Executable Cabal flag cache.
  deriving (Eq, Show)

instance PersistField ConfigCacheType where
  toPersistValue ConfigCacheTypeConfig = PersistText "config"
  toPersistValue (ConfigCacheTypeFlagLibrary v) =
    PersistText $ "lib:" <> ghcPkgIdToText v
  toPersistValue (ConfigCacheTypeFlagExecutable v) =
    PersistText $ "exe:" <> T.pack (packageIdentifierString v)
  fromPersistValue (PersistText t) =
    fromMaybe (Left $ "Unexpected ConfigCacheType value: " <> t) $
    config <|> fmap lib (T.stripPrefix "lib:" t) <|>
    fmap exe (T.stripPrefix "exe:" t)
   where
    config
      | t == "config" = Just (Right ConfigCacheTypeConfig)
      | otherwise = Nothing
    lib v = do
      ghcPkgId <- mapLeft tshow (parseGhcPkgId v)
      Right $ ConfigCacheTypeFlagLibrary ghcPkgId
    exe v = do
      pkgId <-
        maybe (Left $ "Unexpected ConfigCacheType value: " <> t) Right $
        parsePackageIdentifier (T.unpack v)
      Right $ ConfigCacheTypeFlagExecutable pkgId
  fromPersistValue _ = Left "Unexpected ConfigCacheType type"

instance PersistFieldSql ConfigCacheType where
  sqlType _ = SqlString

-- | Type representing actions for which the last time the action was performed
-- should be cached.
data Action
  = UpgradeCheck
  deriving (Eq, Ord, Show)

instance PersistField Action where
  toPersistValue UpgradeCheck = PersistInt64 1
  fromPersistValue (PersistInt64 1) = Right UpgradeCheck
  fromPersistValue x = Left $ T.pack $ "Invalid Action: " ++ show x

instance PersistFieldSql Action where
  sqlType _ = SqlInt64

-- | Type synonym representing caches of files and information about them
-- sufficient to identify if they have changed subsequently.
type FileCache = Map FilePath FileCacheInfo

-- | Type representing caches of information about files sufficient to identify
-- if they have changed subsequently. Stored on disk.
newtype BuildFileCache = BuildFileCache
  { fileCache :: FileCache
  }
  deriving (Eq, FromJSON, Generic, Show, ToJSON)

instance NFData BuildFileCache

-- | Type representing information about a file sufficient to identify if
-- it has changed subsequently.
newtype FileCacheInfo = FileCacheInfo
  { hash :: SHA256
    -- ^ SHA-256 hash of file contents.
  }
  deriving (Eq, Generic, Show)

instance NFData FileCacheInfo

-- Provided for storing the t'BuildFileCache' values in a file. But maybe
-- JSON/YAML isn't the right choice here, worth considering.
instance ToJSON FileCacheInfo where
  toJSON fileCacheInfo = object
    [ "hash" .= fileCacheInfo.hash
    ]

instance FromJSON FileCacheInfo where
  parseJSON = withObject "FileCacheInfo" $ \o -> FileCacheInfo
    <$> o .: "hash"

-- | Stored in the project's SQLite database to know whether the Cabal
-- configuration has changed or libarary or executable Cabal flags have changed.
data ConfigCache = ConfigCache
  { configureOpts :: !ConfigureOpts
    -- ^ All Cabal configure options used for this package.
  , deps :: !(Set GhcPkgId)
    -- ^ The GhcPkgIds of all of the dependencies. Since Cabal doesn't take
    -- the complete GhcPkgId (only a PackageIdentifier) in the configure
    -- options, just using the previous value is insufficient to know if
    -- dependencies have changed.
  , components :: !(Set S.ByteString)
    -- ^ The components to be built. It's a bit of a hack to include this in
    -- here, as it's not a configure option (just a build option), but this
    -- is a convenient way to force compilation when the components change.
  , buildHaddocks :: !Bool
    -- ^ Is Haddock documentation to be built?
  , pkgSrc :: !CachePkgSrc
    -- ^ The origin of the package's source code.
  , pathEnvVar :: !Text
    -- ^ Value of the PATH environment variable. See
    -- <https://github.com/commercialhaskell/stack/issues/3138>
  }
  deriving (Data, Eq, Generic, Show)

instance NFData ConfigCache

data CachePkgSrc
  = CacheSrcUpstream
  | CacheSrcLocal FilePath
  deriving (Data, Eq, Generic, Read, Show)

instance NFData CachePkgSrc

instance PersistField CachePkgSrc where
  toPersistValue CacheSrcUpstream = PersistText "upstream"
  toPersistValue (CacheSrcLocal fp) = PersistText ("local:" <> T.pack fp)
  fromPersistValue (PersistText t) =
    if t == "upstream"
      then Right CacheSrcUpstream
      else case T.stripPrefix "local:" t of
        Just fp -> Right $ CacheSrcLocal (T.unpack fp)
        Nothing -> Left $ "Unexpected CachePkgSrc value: " <> t
  fromPersistValue _ = Left "Unexpected CachePkgSrc type"

instance PersistFieldSql CachePkgSrc where
  sqlType _ = SqlString

-- | Information on a compiled package: the library .conf file (if relevant),
-- the sub-libraries (if present) and all of the executable paths.
data PrecompiledCache base = PrecompiledCache
  { library :: !(Maybe (Path base File))
    -- ^ .conf file inside the package database
  , subLibs :: ![Path base File]
    -- ^ .conf file inside the package database, for each of the sub-libraries
  , exes    :: ![Path base File]
    -- ^ Full paths to executables
  }
  deriving (Eq, Generic, Show)

instance NFData (PrecompiledCache Abs)

instance NFData (PrecompiledCache Rel)