summaryrefslogtreecommitdiff
path: root/src/Eval.hs
blob: 1278c6f9ed894cab8803521749c4fe3dd2f0bde1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
module Eval (
    EvalInput(..),
    EvalError(..), textEvalError,
    Eval, runEval,

    evalJob,
    evalJobSet,
    evalJobReference,
) where

import Control.Monad
import Control.Monad.Except
import Control.Monad.Reader

import Data.List
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as T

import Config
import Job.Types
import Repo

data EvalInput = EvalInput
    { eiJobRoot :: JobRoot
    , eiRootPath :: FilePath
    , eiCurrentIdRev :: [ JobIdPart ]
    , eiContainingRepo :: Maybe Repo
    , eiOtherRepos :: [ ( RepoName, Repo ) ]
    }

data EvalError
    = OtherEvalError Text

textEvalError :: EvalError -> Text
textEvalError (OtherEvalError text) = text


type Eval a = ReaderT EvalInput (ExceptT EvalError IO) a

runEval :: Eval a -> EvalInput -> IO (Either EvalError a)
runEval action einput = runExceptT $ flip runReaderT einput action


evalJob :: DeclaredJob -> Eval Job
evalJob decl = do
    EvalInput {..} <- ask
    otherCheckout <- forM (jobOtherCheckout decl) $ \( name, revision, checkout ) -> do
        repo <- maybe (throwError $ OtherEvalError $ "repo `" <> textRepoName name <> "' not defined") return $
            lookup name eiOtherRepos
        return ( repo, revision, checkout )
    return Job
        { jobId = JobId $ reverse $ JobIdName (jobId decl) : eiCurrentIdRev
        , jobName = jobName decl
        , jobContainingCheckout = jobContainingCheckout decl
        , jobOtherCheckout = otherCheckout
        , jobRecipe = jobRecipe decl
        , jobArtifacts = jobArtifacts decl
        , jobUses = jobUses decl
        }

evalJobSet :: DeclaredJobSet -> Eval JobSet
evalJobSet decl = do
    jobs <- either (return . Left) (handleToEither . mapM evalJob) $ jobsetJobsEither decl
    return JobSet
        { jobsetCommit = jobsetCommit decl
        , jobsetJobsEither = jobs
        }
  where
    handleToEither = handleError (return . Left . T.unpack . textEvalError) . fmap Right


canonicalJobName :: [ Text ] -> Maybe Tree -> Config -> Eval [ JobIdPart ]
canonicalJobName (r : rs) mbTree config = do
    let name = JobName r
    case find ((name ==) . jobName) (configJobs config) of
        Just djob -> do
            job <- evalJob djob
            repos <- concat <$> sequence
                [ case mbTree of
                    Just _ -> return []
                    Nothing -> maybeToList <$> asks eiContainingRepo
                , return $ nub $ map (\( repo, _, _ ) -> repo) $ jobOtherCheckout job
                ]
            (JobIdName name :) <$> canonicalOtherCheckouts rs repos
        Nothing -> throwError $ OtherEvalError $ "job ‘" <> r <> "’ not found"
canonicalJobName [] _ _ = throwError $ OtherEvalError "expected job name"

canonicalOtherCheckouts :: [ Text ] -> [ Repo ] -> Eval [ JobIdPart ]
canonicalOtherCheckouts (r : rs) (repo : repos) = do
    tree <- tryReadCommit repo r >>= \case
        Just commit -> getCommitTree commit
        Nothing -> tryReadTree repo r >>= \case
            Just tree -> return tree
            Nothing -> throwError $ OtherEvalError $ "failed to resolve ‘" <> r <> "’ to a commit or tree in " <> T.pack (show repo)
    (JobIdTree (treeId tree) :) <$> canonicalOtherCheckouts rs repos
canonicalOtherCheckouts []       []       = return []
canonicalOtherCheckouts []       (_ : _ ) = throwError $ OtherEvalError $ "expected commit or tree reference"
canonicalOtherCheckouts (r : _)  []       = throwError $ OtherEvalError $ "unexpected job ref part ‘" <> r <> "’"

canonicalCommitConfig :: [ Text ] -> Repo -> Eval [ JobIdPart ]
canonicalCommitConfig (r : rs) repo = do
    tree <- tryReadCommit repo r >>= \case
        Just commit -> getCommitTree commit
        Nothing -> tryReadTree repo r >>= \case
            Just tree -> return tree
            Nothing -> throwError $ OtherEvalError $ "failed to resolve ‘" <> r <> "’ to a commit or tree in " <> T.pack (show repo)
    config <- either fail return =<< loadConfigForCommit tree
    (JobIdTree (treeId tree) :) <$> canonicalJobName rs (Just tree) config
canonicalCommitConfig [] _ = throwError $ OtherEvalError "expected commit or tree reference"

evalJobReference :: JobRef -> Eval JobId
evalJobReference (JobRef rs) =
    JobId <$> do
        asks eiJobRoot >>= \case
            JobRootRepo defRepo -> do
                canonicalCommitConfig rs defRepo
            JobRootConfig config -> do
                canonicalJobName rs Nothing config