packages feed

hix-0.7.2: lib/Hix/Managed/Lower/Auto.hs

module Hix.Managed.Lower.Auto where

import qualified Data.Set as Set

import Hix.Class.Map (nToMaybe, nZipR)
import Hix.Data.Monad (M)
import Hix.Managed.Data.BuildConfig (BuildConfig)
import qualified Hix.Managed.Data.EnvState
import Hix.Managed.Data.EnvState (EnvState (EnvState))
import qualified Hix.Managed.Data.LowerConfig
import Hix.Managed.Data.LowerConfig (LowerConfig)
import Hix.Managed.Data.Mutable (MutableVersions)
import qualified Hix.Managed.Data.ProjectContext
import Hix.Managed.Data.ProjectContext (ProjectContext)
import Hix.Managed.Data.ProjectResult (ProjectResult)
import Hix.Managed.Data.Query (Query (Query))
import qualified Hix.Managed.Data.QueryDep
import Hix.Managed.Data.StageContext (StageContext (StageContext, query))
import Hix.Managed.Data.StageState (BuildStatus (Failure, Success))
import Hix.Managed.Diff (reifyVersionChanges)
import Hix.Managed.Flow (Flow, evalStageState, runStage_, stageError)
import Hix.Managed.Handlers.Build (BuildHandlers)
import Hix.Managed.Lower.Init (lowerInitStage)
import Hix.Managed.Lower.Optimize (lowerOptimize)
import Hix.Managed.Lower.Stabilize (stabilizeIfPossible, stabilizeStage, validateCurrent)
import Hix.Managed.Process (processProject)

suggestStabilize :: Flow ()
suggestStabilize = stageError "Re-run with --stabilize to attempt to fix the bounds."

-- | This skips building with initial bounds because it only runs after LowerInit failed, which means that we have no
-- initial bounds.
stabilizeInitFailure ::
  BuildHandlers ->
  LowerConfig ->
  BuildConfig ->
  Flow ()
stabilizeInitFailure handlers conf buildConf
  | conf.stabilize
  = stabilizeStage handlers buildConf
  | otherwise
  = suggestStabilize

-- TODO needs some more work
pristineBounds :: EnvState -> Query -> Maybe Query
pristineBounds EnvState {versions, initial} (Query query) =
  Query <$> nonEmpty (restrictQuery (nToMaybe pristine nameIfJust))
  where
    nameIfJust name = \case
      Just _ -> Just name
      Nothing -> Nothing

    restrictQuery (Set.fromList -> names) =
      filter (flip Set.member names . (.package)) (toList query)

    pristine :: MutableVersions
    pristine = nZipR justEqual (reifyVersionChanges initial) (reifyVersionChanges versions)

    justEqual (Just (Just ini)) (Just current) | ini == current = Just current
    justEqual _ _ = Nothing

-- | Return the current 'Query' without those deps whose current versions differ from their initial ones, i.e. that have
-- been optimized before.
pristineBoundsQuery :: Flow (Maybe Query)
pristineBoundsQuery =
  evalStageState \ env StageContext {query} -> pristineBounds env query

-- | Run Optimize, but only consider the deps that have not been optimized before.
--
-- TODO this could use a flag for forcing optimization of all (query) deps.
optimizePristineBounds ::
  BuildHandlers ->
  BuildConfig ->
  Flow ()
optimizePristineBounds handlers conf = do
  pristineBoundsQuery >>= \case
    Just newQuery ->
      runStage_ "auto-optimize" \ context ->
        lowerOptimize handlers conf context {query = newQuery}
    Nothing -> unit

postInit ::
  BuildHandlers ->
  LowerConfig ->
  BuildConfig ->
  Flow ()
postInit handlers conf buildConf =
  validateCurrent >>= \case
    Success -> optimizePristineBounds handlers buildConf
    Failure | conf.stabilize -> stabilizeIfPossible handlers buildConf
            | otherwise -> suggestStabilize

lowerAutoStages ::
  BuildHandlers ->
  LowerConfig ->
  BuildConfig ->
  Flow ()
lowerAutoStages handlers conf buildConf =
  lowerInitStage handlers conf buildConf >>= \case
    Success | conf.initOnly -> unit
            | otherwise -> postInit handlers conf buildConf
    Failure -> stabilizeInitFailure handlers conf buildConf

lowerAutoMain ::
  LowerConfig ->
  BuildHandlers ->
  ProjectContext ->
  M ProjectResult
lowerAutoMain conf handlers project =
  processProject handlers project (lowerAutoStages handlers conf project.build)