packages feed

stack-2.11.1: src/Stack/Types/Build.hs

{-# LANGUAGE NoImplicitPrelude          #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE OverloadedStrings          #-}

-- | Build-specific types.

module Stack.Types.Build
  ( InstallLocation (..)
  , Installed (..)
  , psVersion
  , Task (..)
  , taskIsTarget
  , taskLocation
  , taskTargetIsMutable
  , LocalPackage (..)
  , Plan (..)
  , TestOpts (..)
  , BenchmarkOpts (..)
  , FileWatchOpts (..)
  , BuildOpts (..)
  , BuildSubset (..)
  , defaultBuildOpts
  , TaskType (..)
  , installLocationIsMutable
  , TaskConfigOpts (..)
  , BuildCache (..)
  , ConfigCache (..)
  , configureOpts
  , CachePkgSrc (..)
  , toCachePkgSrc
  , FileCacheInfo (..)
  , PrecompiledCache (..)
  ) where

import           Data.Aeson ( ToJSON, FromJSON )
import qualified Data.ByteString as S
import           Data.List as L
import qualified Data.Map as Map
import qualified Data.Text as T
import           Database.Persist.Sql
                   ( PersistField (..), PersistFieldSql (..)
                   , PersistValue (PersistText), SqlType (SqlString)
                   )
import           Path ( parent )
import           Stack.Prelude
import           Stack.Types.BuildOpts
                   ( BenchmarkOpts (..), BuildOpts (..), BuildSubset (..)
                   , FileWatchOpts (..), TestOpts (..), defaultBuildOpts
                   )
import           Stack.Types.ConfigureOpts ( ConfigureOpts, configureOpts )
import           Stack.Types.GhcPkgId ( GhcPkgId )
import           Stack.Types.IsMutable ( IsMutable (..) )
import           Stack.Types.Package
                   ( FileCacheInfo (..), InstallLocation (..), Installed (..)
                   , LocalPackage (..), Package (..), PackageSource (..)
                   , psVersion
                   )

-- | Package dependency oracle.
newtype PkgDepsOracle
  = PkgDeps PackageName
  deriving (Eq, NFData, Show, Typeable)

-- | Stored on disk to know whether the files have changed.
newtype BuildCache = BuildCache
  { buildCacheTimes :: Map FilePath FileCacheInfo
    -- ^ Modification times of files.
  }
  deriving (Eq, FromJSON, Generic, Show, ToJSON, Typeable)

instance NFData BuildCache

-- | Stored on disk to know whether the flags have changed.
data ConfigCache = ConfigCache
  { configCacheOpts :: !ConfigureOpts
    -- ^ All options used for this package.
  , configCacheDeps :: !(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.
  , configCacheComponents :: !(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.
  , configCacheHaddock :: !Bool
    -- ^ Are haddocks to be built?
  , configCachePkgSrc :: !CachePkgSrc
  , configCachePathEnvVar :: !Text
  -- ^ Value of the PATH env var, see
  -- <https://github.com/commercialhaskell/stack/issues/3138>
  }
  deriving (Data, Eq, Generic, Show, Typeable)

instance NFData ConfigCache

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

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

toCachePkgSrc :: PackageSource -> CachePkgSrc
toCachePkgSrc (PSFilePath lp) =
  CacheSrcLocal (toFilePath (parent (lpCabalFile lp)))
toCachePkgSrc PSRemote{} = CacheSrcUpstream

-- | A task to perform when building
data Task = Task
  { taskProvides        :: !PackageIdentifier -- FIXME turn this into a function on taskType?
    -- ^ the package/version to be built
  , taskType            :: !TaskType
    -- ^ the task type, telling us how to build this
  , taskConfigOpts      :: !TaskConfigOpts
  , taskBuildHaddock    :: !Bool
  , taskPresent         :: !(Map PackageIdentifier GhcPkgId)
    -- ^ GhcPkgIds of already-installed dependencies
  , taskAllInOne        :: !Bool
    -- ^ indicates that the package can be built in one step
  , taskCachePkgSrc     :: !CachePkgSrc
  , taskAnyMissing      :: !Bool
    -- ^ Were any of the dependencies missing? The reason this is necessary is...
    -- hairy. And as you may expect, a bug in Cabal. See:
    -- <https://github.com/haskell/cabal/issues/4728#issuecomment-337937673>.
    -- The problem is that Cabal may end up generating the same package ID for a
    -- dependency, even if the ABI has changed. As a result, without this field,
    -- Stack would think that a reconfigure is unnecessary, when in fact we _do_
    -- need to reconfigure. The details here suck. We really need proper hashes
    -- for package identifiers.
  , taskBuildTypeConfig :: !Bool
    -- ^ Is the build type of this package Configure. Check out
    -- ensureConfigureScript in Stack.Build.Execute for the motivation
  }
  deriving Show

-- | Given the IDs of any missing packages, produce the configure options
data TaskConfigOpts = TaskConfigOpts
  { tcoMissing :: !(Set PackageIdentifier)
    -- ^ Dependencies for which we don't yet have an GhcPkgId
  , tcoOpts    :: !(Map PackageIdentifier GhcPkgId -> ConfigureOpts)
    -- ^ Produce the list of options given the missing @GhcPkgId@s
  }

instance Show TaskConfigOpts where
  show (TaskConfigOpts missing f) = concat
    [ "Missing: "
    , show missing
    , ". Without those: "
    , show $ f Map.empty
    ]

-- | The type of a task, either building local code or something from the
-- package index (upstream)
data TaskType
  = TTLocalMutable LocalPackage
  | TTRemotePackage IsMutable Package PackageLocationImmutable
  deriving Show

taskIsTarget :: Task -> Bool
taskIsTarget t =
  case taskType t of
    TTLocalMutable lp -> lpWanted lp
    _ -> False

taskLocation :: Task -> InstallLocation
taskLocation task =
  case taskType task of
    TTLocalMutable _ -> Local
    TTRemotePackage Mutable _ _ -> Local
    TTRemotePackage Immutable _ _ -> Snap

taskTargetIsMutable :: Task -> IsMutable
taskTargetIsMutable task =
  case taskType task of
    TTLocalMutable _ -> Mutable
    TTRemotePackage mutable _ _ -> mutable

installLocationIsMutable :: InstallLocation -> IsMutable
installLocationIsMutable Snap = Immutable
installLocationIsMutable Local = Mutable

-- | A complete plan of what needs to be built and how to do it
data Plan = Plan
  { planTasks :: !(Map PackageName Task)
  , planFinals :: !(Map PackageName Task)
    -- ^ Final actions to be taken (test, benchmark, etc)
  , planUnregisterLocal :: !(Map GhcPkgId (PackageIdentifier, Text))
    -- ^ Text is reason we're unregistering, for display only
  , planInstallExes :: !(Map Text InstallLocation)
    -- ^ Executables that should be installed after successful building
  }
  deriving Show

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

instance NFData (PrecompiledCache Abs)

instance NFData (PrecompiledCache Rel)