diff options
author | Roman Smrž <roman.smrz@seznam.cz> | 2025-05-24 21:17:13 +0200 |
---|---|---|
committer | Roman Smrž <roman.smrz@seznam.cz> | 2025-05-27 21:24:14 +0200 |
commit | 4f1121a15d65d5defa7c6e477ed5124b934c461f (patch) | |
tree | d60117c855f4b77a905b0eadb6a538fa0fc018f7 | |
parent | a5f20f40840a0cbc1580261bff3d3a7fd2cdc29b (diff) |
Evaluate jobs with all checkouts in the Eval monad
-rw-r--r-- | src/Command/Extract.hs | 2 | ||||
-rw-r--r-- | src/Command/JobId.hs | 2 | ||||
-rw-r--r-- | src/Command/Log.hs | 2 | ||||
-rw-r--r-- | src/Command/Run.hs | 15 | ||||
-rw-r--r-- | src/Config.hs | 17 | ||||
-rw-r--r-- | src/Eval.hs | 78 | ||||
-rw-r--r-- | src/Job.hs | 22 | ||||
-rw-r--r-- | src/Job/Types.hs | 10 |
8 files changed, 71 insertions, 77 deletions
diff --git a/src/Command/Extract.hs b/src/Command/Extract.hs index 4336b29..8a0a035 100644 --- a/src/Command/Extract.hs +++ b/src/Command/Extract.hs @@ -78,7 +78,7 @@ cmdExtract (ExtractCommand ExtractOptions {..} ExtractArguments {..}) = do _ -> return False forM_ extractArtifacts $ \( ref, ArtifactName aname ) -> do - jid@(JobId ids) <- either (tfail . textEvalError) return =<< + jid@(JobId ids) <- either (tfail . textEvalError) (return . jobId) =<< liftIO (runEval (evalJobReference ref) einput) let jdir = joinPath $ (storageDir :) $ ("jobs" :) $ map (T.unpack . textJobIdPart) ids diff --git a/src/Command/JobId.hs b/src/Command/JobId.hs index 429e2a0..173f543 100644 --- a/src/Command/JobId.hs +++ b/src/Command/JobId.hs @@ -52,7 +52,7 @@ cmdJobId :: JobIdCommand -> CommandExec () cmdJobId (JobIdCommand JobIdOptions {..} ref) = do einput <- getEvalInput out <- getOutput - JobId ids <- either (tfail . textEvalError) return =<< + JobId ids <- either (tfail . textEvalError) (return . jobId) =<< liftIO (runEval (evalJobReference ref) einput) outputMessage out $ textJobId $ JobId ids diff --git a/src/Command/Log.hs b/src/Command/Log.hs index 5d8c9d4..25bfc06 100644 --- a/src/Command/Log.hs +++ b/src/Command/Log.hs @@ -37,7 +37,7 @@ instance Command LogCommand where cmdLog :: LogCommand -> CommandExec () cmdLog (LogCommand ref) = do einput <- getEvalInput - jid <- either (tfail . textEvalError) return =<< + jid <- either (tfail . textEvalError) (return . jobId) =<< liftIO (runEval (evalJobReference ref) einput) output <- getOutput storageDir <- getStorageDir diff --git a/src/Command/Run.hs b/src/Command/Run.hs index ce1ea4a..c122cf6 100644 --- a/src/Command/Run.hs +++ b/src/Command/Run.hs @@ -135,16 +135,17 @@ argumentJobSource names = do config <- either fail return =<< loadConfigForCommit =<< getCommitTree commit return ( config, Just commit ) - cidPart <- case jobsetCommit of - Just commit -> (: []) . JobIdTree Nothing "" . treeId <$> getCommitTree commit + jobtree <- case jobsetCommit of + Just commit -> (: []) <$> getCommitTree commit Nothing -> return [] + let cidPart = map (JobIdTree Nothing "" . treeId) jobtree jobsetJobsEither <- fmap Right $ forM names $ \name -> case find ((name ==) . jobName) (configJobs config) of Just job -> return job Nothing -> tfail $ "job ‘" <> textJobName name <> "’ not found" oneshotJobSource . (: []) =<< cmdEvalWith (\ei -> ei { eiCurrentIdRev = cidPart ++ eiCurrentIdRev ei }) - (evalJobSet [] JobSet {..}) + (evalJobSet (map ( Nothing, ) jobtree) JobSet {..}) loadJobSetFromRoot :: (MonadIO m, MonadFail m) => JobRoot -> Commit -> m DeclaredJobSet loadJobSetFromRoot root commit = case root of @@ -163,7 +164,7 @@ rangeSource base tip = do tree <- getCommitTree commit cmdEvalWith (\ei -> ei { eiCurrentIdRev = JobIdTree Nothing (treeSubdir tree) (treeId tree) : eiCurrentIdRev ei - }) . evalJobSet [] =<< loadJobSetFromRoot root commit + }) . evalJobSet [ ( Nothing, tree) ] =<< loadJobSetFromRoot root commit oneshotJobSource jobsets @@ -188,7 +189,7 @@ watchBranchSource branch = do { eiCurrentIdRev = JobIdTree Nothing (treeSubdir tree) (treeId tree) : eiCurrentIdRev einputBase } either (fail . T.unpack . textEvalError) return =<< - flip runEval einput . evalJobSet [] =<< loadJobSetFromRoot root commit + flip runEval einput . evalJobSet [ ( Nothing, tree ) ] =<< loadJobSetFromRoot root commit nextvar <- newEmptyTMVarIO atomically $ putTMVar tmvar $ Just ( jobsets, JobSource nextvar ) go cur nextvar @@ -218,7 +219,7 @@ watchTagSource pat = do { eiCurrentIdRev = JobIdTree Nothing (treeSubdir tree) (treeId tree) : eiCurrentIdRev einputBase } jobset <- either (fail . T.unpack . textEvalError) return =<< - flip runEval einput . evalJobSet [] =<< loadJobSetFromRoot root (tagObject tag) + flip runEval einput . evalJobSet [ ( Nothing, tree ) ] =<< loadJobSetFromRoot root (tagObject tag) nextvar <- newEmptyTMVarIO atomically $ putTMVar tmvar $ Just ( [ jobset ], JobSource nextvar ) go nextvar @@ -305,7 +306,7 @@ cmdRun (RunCommand RunOptions {..} args) = do case jobsetJobsEither jobset of Right jobs -> do - outs <- runJobs mngr output commit jobs + outs <- runJobs mngr output jobs let findJob name = snd <$> find ((name ==) . jobName . fst) outs statuses = map findJob names forM_ (outputTerminal output) $ \tout -> do diff --git a/src/Config.hs b/src/Config.hs index ade2216..4327193 100644 --- a/src/Config.hs +++ b/src/Config.hs @@ -13,7 +13,6 @@ import Control.Monad.Combinators import Control.Monad.IO.Class import Data.ByteString.Lazy qualified as BS -import Data.Either import Data.List import Data.Map qualified as M import Data.Maybe @@ -79,11 +78,11 @@ parseJob :: Text -> Node Pos -> Parser DeclaredJob parseJob name node = flip (withMap "Job") node $ \j -> do let jobName = JobName name jobId = jobName - ( jobContainingCheckout, jobOtherCheckout ) <- partitionEithers <$> choice + jobCheckout <- choice [ parseSingleCheckout =<< j .: "checkout" , parseMultipleCheckouts =<< j .: "checkout" , withNull "no checkout" (return []) =<< j .: "checkout" - , return [ Left $ JobCheckout Nothing Nothing ] + , return [ JobCheckout Nothing Nothing Nothing ] ] jobRecipe <- choice [ cabalJob =<< j .: "cabal" @@ -93,18 +92,18 @@ parseJob name node = flip (withMap "Job") node $ \j -> do jobUses <- maybe (return []) parseUses =<< j .:? "uses" return Job {..} -parseSingleCheckout :: Node Pos -> Parser [ Either JobCheckout ( JobRepo Declared, JobCheckout ) ] +parseSingleCheckout :: Node Pos -> Parser [ JobCheckout Declared ] parseSingleCheckout = withMap "checkout definition" $ \m -> do jcSubtree <- fmap T.unpack <$> m .:? "subtree" jcDestination <- fmap T.unpack <$> m .:? "dest" - let checkout = JobCheckout {..} - m .:? "repo" >>= \case - Nothing -> return [ Left checkout ] + jcRepo <- m .:? "repo" >>= \case + Nothing -> return Nothing Just name -> do revision <- m .:? "revision" - return [ Right (( RepoName name, revision ), checkout ) ] + return $ Just ( RepoName name, revision ) + return [ JobCheckout {..} ] -parseMultipleCheckouts :: Node Pos -> Parser [ Either JobCheckout ( JobRepo Declared, JobCheckout ) ] +parseMultipleCheckouts :: Node Pos -> Parser [ JobCheckout Declared ] parseMultipleCheckouts = withSeq "checkout definitions" $ fmap concat . mapM parseSingleCheckout cabalJob :: Node Pos -> Parser [CreateProcess] diff --git a/src/Eval.hs b/src/Eval.hs index 05381dd..97aba2f 100644 --- a/src/Eval.hs +++ b/src/Eval.hs @@ -12,7 +12,6 @@ import Control.Monad import Control.Monad.Except import Control.Monad.Reader -import Data.Bifunctor import Data.List import Data.Maybe import Data.Text (Text) @@ -51,57 +50,62 @@ commonPrefix _ _ = [] isDefaultRepoMissingInId :: DeclaredJob -> Eval Bool isDefaultRepoMissingInId djob - | [] <- jobContainingCheckout djob = return False + | all (isJust . jcRepo) (jobCheckout djob) = return False | otherwise = asks (not . any matches . eiCurrentIdRev) where matches (JobIdName _) = False matches (JobIdCommit rname _) = isNothing rname matches (JobIdTree rname _ _) = isNothing rname -collectOtherRepos :: DeclaredJobSet -> DeclaredJob -> Eval [ (( Maybe RepoName, Maybe Text ), FilePath ) ] +collectOtherRepos :: DeclaredJobSet -> DeclaredJob -> Eval [ ( Maybe ( RepoName, Maybe Text ), FilePath ) ] collectOtherRepos dset decl = do let dependencies = map fst $ jobUses decl dependencyRepos <- forM dependencies $ \name -> do jobs <- either (throwError . OtherEvalError . T.pack) return $ jobsetJobsEither dset job <- maybe (throwError $ OtherEvalError $ "job ‘" <> textJobName name <> "’ not found") return . find ((name ==) . jobName) $ jobs - return $ jobOtherCheckout job + return $ jobCheckout job missingDefault <- isDefaultRepoMissingInId decl - let checkouts = concat - [ if missingDefault then map (( Nothing, Nothing ), ) $ jobContainingCheckout decl else [] - , map (first (first Just)) $ jobOtherCheckout decl - , map (first (first Just)) $ concat dependencyRepos - ] + let checkouts = + (if missingDefault then id else (filter (isJust . jcRepo))) $ + concat + [ jobCheckout decl + , concat dependencyRepos + ] let commonSubdir reporev = joinPath $ foldr1 commonPrefix $ - map (maybe [] splitDirectories . jcSubtree . snd) . filter ((reporev ==) . fst) $ checkouts - return $ map (\r -> ( r, commonSubdir r )) . nub . map fst $ checkouts + map (maybe [] splitDirectories . jcSubtree) . filter ((reporev ==) . jcRepo) $ checkouts + return $ map (\r -> ( r, commonSubdir r )) . nub . map jcRepo $ checkouts evalJob :: [ ( Maybe RepoName, Tree ) ] -> DeclaredJobSet -> DeclaredJob -> Eval Job evalJob revisionOverrides dset decl = do EvalInput {..} <- ask otherRepos <- collectOtherRepos dset decl - otherRepoTrees <- forM otherRepos $ \(( mbname, mbrev ), commonPath ) -> do - ( mbname, ) . ( commonPath, ) <$> case lookup mbname revisionOverrides of - Just tree -> return tree - Nothing -> case maybe eiContainingRepo (flip lookup eiOtherRepos) mbname of - Just repo -> do - commit <- readCommit repo (fromMaybe "HEAD" mbrev) + otherRepoTrees <- forM otherRepos $ \( mbrepo, commonPath ) -> do + ( mbrepo, ) . ( commonPath, ) <$> do + case lookup (fst <$> mbrepo) revisionOverrides of + Just tree -> return tree + Nothing -> do + repo <- evalRepo (fst <$> mbrepo) + commit <- readCommit repo (fromMaybe "HEAD" $ join $ snd <$> mbrepo) getSubtree (Just commit) commonPath =<< getCommitTree commit - Nothing -> throwError $ OtherEvalError $ "repo ‘" <> maybe "" textRepoName mbname <> "’ not defined" - otherCheckout <- forM (jobOtherCheckout decl) $ \(( name, _ ), checkout ) -> do - (, checkout ) <$> case snd <$> lookup (Just name) otherRepoTrees of - Just tree -> return tree - Nothing -> throwError $ OtherEvalError $ "repo ‘" <> textRepoName name <> "’ not defined" - - let otherRepoIds = map (\( name, ( subtree, tree )) -> JobIdTree name subtree (treeId tree)) otherRepoTrees + checkouts <- forM (jobCheckout decl) $ \dcheckout -> do + return dcheckout + { jcRepo = + fromMaybe (error $ "expecting repo in either otherRepoTrees or revisionOverrides: " <> show (textRepoName . fst <$> jcRepo dcheckout)) $ + msum + [ snd <$> lookup (jcRepo dcheckout) otherRepoTrees + , lookup (fst <$> jcRepo dcheckout) revisionOverrides + ] + } + + let otherRepoIds = map (\( repo, ( subtree, tree )) -> JobIdTree (fst <$> repo) subtree (treeId tree)) otherRepoTrees return Job { jobId = JobId $ reverse $ reverse otherRepoIds ++ JobIdName (jobId decl) : eiCurrentIdRev , jobName = jobName decl - , jobContainingCheckout = jobContainingCheckout decl - , jobOtherCheckout = otherCheckout + , jobCheckout = checkouts , jobRecipe = jobRecipe decl , jobArtifacts = jobArtifacts decl , jobUses = jobUses decl @@ -126,23 +130,23 @@ evalRepo (Just name) = asks (lookup name . eiOtherRepos) >>= \case Nothing -> throwError $ OtherEvalError $ "repo ‘" <> textRepoName name <> "’ not defined" -canonicalJobName :: [ Text ] -> Config -> Eval JobId -canonicalJobName (r : rs) config = do +canonicalJobName :: [ Text ] -> Config -> Maybe Tree -> Eval Job +canonicalJobName (r : rs) config mbDefaultRepo = do let name = JobName r dset = JobSet Nothing $ Right $ configJobs config case find ((name ==) . jobName) (configJobs config) of Just djob -> do otherRepos <- collectOtherRepos dset djob ( overrides, rs' ) <- (\f -> foldM f ( [], rs ) otherRepos) $ - \( overrides, crs ) (( mbname, _ ), path ) -> do - ( tree, crs' ) <- readTreeFromIdRef crs path =<< evalRepo mbname - return ( ( mbname, tree ) : overrides, crs' ) + \( overrides, crs ) ( mbrepo, path ) -> do + ( tree, crs' ) <- readTreeFromIdRef crs path =<< evalRepo (fst <$> mbrepo) + return ( ( fst <$> mbrepo, tree ) : overrides, crs' ) case rs' of (r' : _) -> throwError $ OtherEvalError $ "unexpected job ref part ‘" <> r' <> "’" _ -> return () - jobId <$> evalJob overrides dset djob + evalJob (maybe id ((:) . ( Nothing, )) mbDefaultRepo $ overrides) dset djob Nothing -> throwError $ OtherEvalError $ "job ‘" <> r <> "’ not found" -canonicalJobName [] _ = throwError $ OtherEvalError "expected job name" +canonicalJobName [] _ _ = throwError $ OtherEvalError "expected job name" readTreeFromIdRef :: [ Text ] -> FilePath -> Repo -> Eval ( Tree, [ Text ] ) readTreeFromIdRef (r : rs) subdir repo = do @@ -153,17 +157,17 @@ readTreeFromIdRef (r : rs) subdir repo = do Nothing -> throwError $ OtherEvalError $ "failed to resolve ‘" <> r <> "’ to a commit or tree in " <> T.pack (show repo) readTreeFromIdRef [] _ _ = throwError $ OtherEvalError $ "expected commit or tree reference" -canonicalCommitConfig :: [ Text ] -> Repo -> Eval JobId +canonicalCommitConfig :: [ Text ] -> Repo -> Eval Job canonicalCommitConfig rs repo = do ( tree, rs' ) <- readTreeFromIdRef rs "" repo config <- either fail return =<< loadConfigForCommit tree local (\ei -> ei { eiCurrentIdRev = JobIdTree Nothing "" (treeId tree) : eiCurrentIdRev ei }) $ - canonicalJobName rs' config + canonicalJobName rs' config (Just tree) -evalJobReference :: JobRef -> Eval JobId +evalJobReference :: JobRef -> Eval Job evalJobReference (JobRef rs) = asks eiJobRoot >>= \case JobRootRepo defRepo -> do canonicalCommitConfig rs defRepo JobRootConfig config -> do - canonicalJobName rs config + canonicalJobName rs config Nothing @@ -182,8 +182,8 @@ runManagedJob JobManager {..} tid cancel job = bracket acquire release $ \case writeTVar jmRunningTasks . M.delete tid =<< readTVar jmRunningTasks -runJobs :: JobManager -> Output -> Maybe Commit -> [ Job ] -> IO [ ( Job, TVar (JobStatus JobOutput) ) ] -runJobs mngr@JobManager {..} tout commit jobs = do +runJobs :: JobManager -> Output -> [ Job ] -> IO [ ( Job, TVar (JobStatus JobOutput) ) ] +runJobs mngr@JobManager {..} tout jobs = do results <- atomically $ do forM jobs $ \job -> do tid <- reserveTaskId mngr @@ -221,7 +221,7 @@ runJobs mngr@JobManager {..} tout commit jobs = do runManagedJob mngr tid (return JobCancelled) $ do liftIO $ atomically $ writeTVar outVar JobRunning liftIO $ outputEvent tout $ JobStarted (jobId job) - prepareJob jmDataDir commit job $ \checkoutPath jdir -> do + prepareJob jmDataDir job $ \checkoutPath jdir -> do updateStatusFile (jdir </> "status") outVar JobDone <$> runJob job uses checkoutPath jdir @@ -288,20 +288,10 @@ updateStatusFile path outVar = void $ liftIO $ forkIO $ loop Nothing jobStorageSubdir :: JobId -> FilePath jobStorageSubdir (JobId jidParts) = "jobs" </> joinPath (map (T.unpack . textJobIdPart) (jidParts)) -prepareJob :: (MonadIO m, MonadMask m, MonadFail m) => FilePath -> Maybe Commit -> Job -> (FilePath -> FilePath -> m a) -> m a -prepareJob dir mbCommit job inner = do +prepareJob :: (MonadIO m, MonadMask m, MonadFail m) => FilePath -> Job -> (FilePath -> FilePath -> m a) -> m a +prepareJob dir job inner = do withSystemTempDirectory "minici" $ \checkoutPath -> do - case mbCommit of - Just commit -> do - tree <- getCommitTree commit - forM_ (jobContainingCheckout job) $ \(JobCheckout mbsub dest) -> do - subtree <- maybe return (getSubtree mbCommit . makeRelative (treeSubdir tree)) mbsub $ tree - checkoutAt subtree $ checkoutPath </> fromMaybe "" dest - Nothing -> do - when (not $ null $ jobContainingCheckout job) $ do - fail $ "no containing repository, can't do checkout" - - forM_ (jobOtherCheckout job) $ \( tree, JobCheckout mbsub dest ) -> do + forM_ (jobCheckout job) $ \(JobCheckout tree mbsub dest) -> do subtree <- maybe return (getSubtree Nothing . makeRelative (treeSubdir tree)) mbsub $ tree checkoutAt subtree $ checkoutPath </> fromMaybe "" dest diff --git a/src/Job/Types.hs b/src/Job/Types.hs index 1ac329e..4024317 100644 --- a/src/Job/Types.hs +++ b/src/Job/Types.hs @@ -16,8 +16,7 @@ data Evaluated data Job' d = Job { jobId :: JobId' d , jobName :: JobName - , jobContainingCheckout :: [ JobCheckout ] - , jobOtherCheckout :: [ ( JobRepo d, JobCheckout ) ] + , jobCheckout :: [ JobCheckout d ] , jobRecipe :: [ CreateProcess ] , jobArtifacts :: [ ( ArtifactName, Pattern ) ] , jobUses :: [ ( JobName, ArtifactName ) ] @@ -41,11 +40,12 @@ textJobName (JobName name) = name type family JobRepo d :: Type where - JobRepo Declared = ( RepoName, Maybe Text ) + JobRepo Declared = Maybe ( RepoName, Maybe Text ) JobRepo Evaluated = Tree -data JobCheckout = JobCheckout - { jcSubtree :: Maybe FilePath +data JobCheckout d = JobCheckout + { jcRepo :: JobRepo d + , jcSubtree :: Maybe FilePath , jcDestination :: Maybe FilePath } |