packages feed

haskell-updater-1.5.0.0: src/Distribution/Gentoo/Types.hs

{- |
   Module      : Distribution.Gentoo.Types

   General types needed for haskell-updater functionality
 -}

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module Distribution.Gentoo.Types
  ( RunModifier(..)
  , RawPMArgs
  , WithCmd(..)
  , PendingPackages(..)
  , Target(..)
  , RunHistory(..)
  , isInHistory
  , HistoryState(..)
  , historyState
  , isEmptyHistory
  , latestPending
  , LoopType(..)
  , ExtraRawArgs(..)
  , InvalidPkgs(..)
  , AllPkgs(..)
  , PackageSet(..)
  , MonadExit(..)
  ) where

import Control.Monad.Reader
import Control.Monad.State.Strict
import Data.Proxy
import qualified Data.Set as Set
import qualified Data.Sequence as Seq
import Data.Sequence (ViewR(EmptyR, (:>)), viewr)
import System.Exit (ExitCode(..), exitSuccess)
import qualified System.Exit as Exit
import System.IO (hPutStrLn, stderr)

import Distribution.Gentoo.Packages
import Distribution.Gentoo.PkgManager.Types
import Output

-- | Run-mode haskell-updater state
data RunModifier = RM { flags    :: [PMFlag]
                      , withCmd  :: WithCmd
                      , rawPMArgs :: RawPMArgs
                      , verbosity :: Verbosity
                      }
                   deriving (Eq, Ord, Show)

-- | Arguments to be passed when calling the package manager
type RawPMArgs = [String]

-- | How to handle the command for calling the package manager.
data WithCmd = PrintAndRun -- ^ Print the command /and/ run it (default)
             | PrintOnly -- ^ Only print the command
             | RunOnly -- ^ Only run the command
               deriving (Eq, Ord, Show, Read, Enum, Bounded)

-- | The set of packages that are currently broken and need to be rebuilt,
--   as reported by @ghc-pkg check@. These may or may not equate to the
--   'Target', depending on which mode @haskell-updater@ is running in.
data PendingPackages
    = InvalidPending InvalidPkgs -- ^ invalid (broken) installed Haskell packages
    | AllPending AllPkgs -- ^ all installed Haskell packages
    deriving (Show, Eq, Ord)

-- | The "targets" passed to the package manager, for instance package atoms
--   or set strings.
data Target
    = TargetInvalid InvalidPkgs -- ^ @--target=invalid@
    | TargetAll AllPkgs -- ^ @--target=all@
    | CustomTarget String -- ^ @--custom-target=...@
    deriving (Show, Eq, Ord)

-- | The history of every rebuild run that has been attempted, including the
--   state of pending packages /before/ any runs have been attempted.
data RunHistory m = RunHistory
    { initialState :: PendingPackages
    , runHistory :: Seq.Seq (PendingPackages, ExitArg m)
    }

deriving instance Show (ExitArg m) => Show (RunHistory m)
deriving instance Eq (ExitArg m) => Eq (RunHistory m)
deriving instance Ord (ExitArg m) => Ord (RunHistory m)

-- | Is the package set in the history somewhere?
isInHistory :: PackageSet s => RunHistory m -> s -> Bool
isInHistory (RunHistory pending0 runSeq) ps
    = getPkgs pending0 == pkgSet || pkgSet `elem` (getPkgs . fst <$> runSeq)
    where pkgSet = getPkgs ps

-- | The current state of the history, processed in a way that is useful
--   for looping algorithms.
data HistoryState m
      -- | Carries the initial pending packages state
    = NoRunsTried PendingPackages
      -- | Carries the initial pending packages state and the first history
      --   entry
    | OneRunTried PendingPackages (PendingPackages, ExitArg m)
      -- | Carries the last two history entries (oldest first)
    | MultipleRunsTried (PendingPackages, ExitArg m) (PendingPackages, ExitArg m)

deriving instance Show (ExitArg m) => Show (HistoryState m)
deriving instance Eq (ExitArg m) => Eq (HistoryState m)
deriving instance Ord (ExitArg m) => Ord (HistoryState m)

-- | Returns 'Nothing' if the first update run hasn't been carried out
historyState :: RunHistory m -> HistoryState m
historyState (RunHistory initialPending runSeq) = case viewr runSeq of
    EmptyR -> NoRunsTried initialPending
    (hs :> lastH) -> case viewr hs of
        EmptyR -> OneRunTried initialPending lastH
        (_ :> earlierH) -> MultipleRunsTried earlierH lastH

-- | Returns 'True' if the first update run hasn't been carried out
isEmptyHistory :: RunHistory m -> Bool
isEmptyHistory h = case historyState h of
    NoRunsTried _ -> True
    _ -> False

latestPending :: RunHistory m -> PendingPackages
latestPending h = case historyState h of
    NoRunsTried initialPending -> initialPending
    OneRunTried _ (lastPending, _) -> lastPending
    MultipleRunsTried _ (lastPending, _) -> lastPending

data LoopType
      -- | Loop until there are no pending packages left. Fails if the current
      --   'PendingPackages' matches any in the history, as this means one or
      --   more packages are failing due to reasons other than broken
      --   dependencies.
      --
      --   This is the default "classic" behavior of @haskell-updater@
    = UntilNoPending

      -- | Loop until there is no change in the current pending packages
      --   compared to the last run. Succeeds or fails based on the 'ExitCode'
      --   of the last run and the current run. This is useful for modes where
      --   it is possible to start out with no 'PendingPackages', but some may
      --   appear later as packages are updated and break their dependencies.
      --
      --   Used by e.g. @--mode=reinstall-atoms@.
    | UntilNoChange

      -- | Run once and do not loop. This is useful for modes where no
      --   valuable information can be gleaned by comparing 'PendingPackages'
      --   of separate runs.
      --
      --   Used by e.g. @--target=all@.
    | NoLoop
    deriving (Show, Eq, Ord, Enum, Bounded)

-- | Any hard-coded extra raw arguments to pass to the package manager, which
--   are needed for some @haskell-updater@ modes.
newtype ExtraRawArgs = ExtraRawArgs [String]
    deriving (Show, Eq, Ord)

newtype InvalidPkgs = InvalidPkgs (Set.Set Package)
    deriving (Show, Eq, Ord, Semigroup, Monoid)

newtype AllPkgs = AllPkgs (Set.Set Package)
    deriving (Show, Eq, Ord, Semigroup, Monoid)

class PackageSet t where
    getPkgs :: t -> Set.Set Package

instance PackageSet InvalidPkgs where
    getPkgs (InvalidPkgs ps) = ps

instance PackageSet AllPkgs where
    getPkgs (AllPkgs ps) = ps

instance PackageSet () where
    getPkgs () = Set.empty

instance PackageSet PendingPackages where
    getPkgs (InvalidPending p) = getPkgs p
    getPkgs (AllPending p) = getPkgs p

instance PackageSet (Set.Set Package) where
    getPkgs = id

-- | A class for monads that have some sort of exit feature. It must terminate
--   execution within the monad, optionally display a message, and return an
--   'ExitArg'. The most obvious example is @IO@ which has 'exitSuccess' and
--   more broadly, 'Exit.exitWith'. The 'ExitArg' for @IO@ is 'ExitCode'.
--
--   This is left open as a class for testing purposes.
class Monad m => MonadExit m where
    type ExitArg m
    success :: String -> m a
    die :: String -> m a
    exitWith :: ExitArg m -> m a
    isSuccess :: Proxy m -> ExitArg m -> Bool

instance MonadExit IO where
    type ExitArg IO = ExitCode
    success msg = do
        hPutStrLn stderr msg
        liftIO exitSuccess
    die msg = do
        hPutStrLn stderr ("ERROR: " ++ msg)
        exitWith (ExitFailure 1)
    exitWith = Exit.exitWith
    isSuccess _ = \case
        ExitSuccess -> True
        ExitFailure _ -> False

instance MonadExit m => MonadExit (StateT r m) where
    type ExitArg (StateT r m) = ExitArg m
    success = lift . success
    die = lift . die
    exitWith = lift . exitWith
    isSuccess (_ :: Proxy (StateT r m)) = isSuccess (Proxy :: Proxy m)

instance MonadExit m => MonadExit (ReaderT r m) where
    type ExitArg (ReaderT r m) = ExitArg m
    success = lift . success
    die = lift . die
    exitWith = lift . exitWith
    isSuccess (_ :: Proxy (ReaderT r m)) = isSuccess (Proxy :: Proxy m)

deriving instance MonadExit SayIO