packages feed

hix-0.9.0: lib/Hix/Managed/Lower/Candidates.hs

module Hix.Managed.Lower.Candidates where

import qualified Data.Set as Set
import qualified Data.Text as Text
import Distribution.Pretty (pretty)
import Distribution.Version (Version)
import Exon (exon)

import Hix.Class.Map ((!!))
import Hix.Data.Error (ErrorMessage (Client))
import Hix.Data.Monad (M)
import Hix.Data.Version (Major)
import qualified Hix.Data.VersionBounds
import Hix.Data.VersionBounds (VersionBounds)
import qualified Hix.Log as Log
import qualified Hix.Managed.Data.Lower
import Hix.Managed.Data.Lower (Lower (Lower))
import Hix.Managed.Data.Mutable (MutableDep, MutableVersions, depName)
import qualified Hix.Managed.Data.Mutation
import Hix.Managed.Data.Mutation (DepMutation (DepMutation))
import qualified Hix.Managed.Data.QueryDep
import Hix.Managed.Data.QueryDep (QueryDep)
import Hix.Managed.Handlers.AvailableVersions (AvailableVersionsHandlers (..))
import Hix.Monad (throwM)
import Hix.Pretty (showP)
import Hix.Version (allMajors, majorParts, majorsBefore, versionsBetween, versionsFrom)

logNoVersions ::
  MutableDep ->
  [Version] ->
  Maybe a ->
  M (Maybe a)
logNoVersions package allVersions mutation = do
  when (isNothing mutation) do
    Log.debug [exon|Available versions: #{Text.intercalate ", " (show . pretty <$> allVersions)}|]
    Log.warn [exon|No suitable version found for '##{package}'.|]
  pure mutation

specifiedLower :: VersionBounds -> Maybe (Int, Int)
specifiedLower = majorParts <=< (.lower)

specifiedUpper :: VersionBounds -> Maybe (Int, Int)
specifiedUpper = majorParts <=< (.upper)

prefix :: Int -> Int -> Text
prefix s m = [exon|#{show s}.#{show m}|]

candidates ::
  AvailableVersionsHandlers ->
  QueryDep ->
  Bool ->
  ([Version] -> Maybe (NonEmpty Major)) ->
  M (Maybe (DepMutation Lower))
candidates versions dep retract selection = do
  allVersions <- versions.all (depName package)
  let
    result = do
      majors <- selection (sort allVersions)
      pure DepMutation {package, retract, mutation = Lower {majors}}
  logNoVersions package allVersions result
  pure result
  where
    package = dep.package

data InitConfig =
  InitBeforeUpper Int Int
  |
  InitAll
  deriving stock (Eq, Show, Generic)

initConfig :: VersionBounds -> InitConfig
initConfig version =
  case specifiedUpper version of
    Just (s, m) -> InitBeforeUpper s m
    _ -> InitAll

logInitConfig :: MutableDep -> InitConfig -> M ()
logInitConfig package conf =
  Log.verbose [exon|Choosing versions for '##{package}' from #{msg conf}.|]
  where
    msg = \case
      InitBeforeUpper s m ->
        [exon|all majors before the specified upper bound #{prefix s m}|]
      InitAll ->
        "all majors"

selectionInit :: InitConfig -> [Version] -> Maybe (NonEmpty Major)
selectionInit = \case
  InitBeforeUpper s m ->
    nonEmpty . reverse . majorsBefore s m
  InitAll ->
    nonEmpty . reverse . allMajors

candidatesInit ::
  AvailableVersionsHandlers ->
  Set MutableDep ->
  QueryDep ->
  M (Maybe (DepMutation Lower))
candidatesInit versions pre dep
  | Set.member dep.package pre
  = pure Nothing
  | otherwise
  = do
    logInitConfig dep.package conf
    candidates versions dep False (selectionInit conf)
  where
    conf = initConfig dep.bounds

data OptimizeConfig =
  OptimizeMajorsBefore Int Int
  |
  OptimizeNoBound
  deriving stock (Eq, Show, Generic)

optimizeConfig ::
  Maybe Version ->
  VersionBounds ->
  OptimizeConfig
optimizeConfig initial version =
  case majorParts =<< (version.lower <|> initial) of
    Just (s, m) -> OptimizeMajorsBefore s m
    Nothing -> OptimizeNoBound

logOptimizeConfig ::
  MutableDep ->
  OptimizeConfig ->
  M ()
logOptimizeConfig package = \case
  OptimizeMajorsBefore s m ->
    Log.verbose [exon|Choosing versions for '##{package}' from all majors before #{prefix s m}.|]
  OptimizeNoBound ->
    throwM (Client [exon|'##{package}' has no initial lower bound. Please run '.#lower.init' first.|])

selectionOptimize :: OptimizeConfig -> [Version] -> Maybe (NonEmpty Major)
selectionOptimize = \case
  OptimizeMajorsBefore s m -> do
    nonEmpty . reverse . majorsBefore s m
  OptimizeNoBound ->
    const Nothing

candidatesOptimize ::
  AvailableVersionsHandlers ->
  MutableVersions ->
  QueryDep ->
  M (Maybe (DepMutation Lower))
candidatesOptimize versions initial dep = do
  logOptimizeConfig dep.package conf
  candidates versions dep False (selectionOptimize conf)
  where
    conf = optimizeConfig (join (initial !! dep.package)) dep.bounds

data StabilizeConfig =
  StabilizeFromVersion Version (Maybe Version)
  |
  StabilizeNoBound InitConfig
  deriving stock (Eq, Show, Generic)

stabilizeConfig :: VersionBounds -> Maybe Version -> StabilizeConfig
stabilizeConfig version initialBound =
  case version.lower of
    Just v -> StabilizeFromVersion v (initialBound <|> version.upper)
    Nothing | Just v <- initialBound -> StabilizeFromVersion v (initialBound <|> version.upper)
    Nothing -> StabilizeNoBound (initConfig version)

logStabilizeConfig :: MutableDep -> StabilizeConfig -> M ()
logStabilizeConfig package = \case
  StabilizeFromVersion v Nothing ->
    Log.verbose [exon|Choosing versions for '##{package}' after the current version #{showP v}.|]
  StabilizeFromVersion l (Just u) ->
    Log.verbose [exon|Choosing versions for '##{package}' between the current version #{showP l} and the initial version #{showP u}.|]
  StabilizeNoBound conf ->
    logInitConfig package conf

selectionStabilize :: StabilizeConfig -> [Version] -> Maybe (NonEmpty Major)
selectionStabilize = \case
  StabilizeFromVersion l Nothing ->
    nonEmpty . versionsFrom l
  StabilizeFromVersion l (Just u) ->
    nonEmpty . versionsBetween l u
  StabilizeNoBound conf ->
    selectionInit conf

candidatesStabilize ::
  AvailableVersionsHandlers ->
  QueryDep ->
  Maybe Version ->
  M (Maybe (DepMutation Lower))
candidatesStabilize versions dep initialBound = do
  logStabilizeConfig dep.package conf
  candidates versions dep (isRetract conf) (selectionStabilize conf)
  where
    conf = stabilizeConfig dep.bounds initialBound

    isRetract = \case
      StabilizeFromVersion _ _ -> True
      StabilizeNoBound _ -> False