minici 0.1.5 → 0.1.6
raw patch · 9 files changed
+149/−17 lines, 9 files
Files
- CHANGELOG.md +5/−0
- minici.cabal +2/−1
- src/Command/JobId.hs +39/−0
- src/Config.hs +5/−5
- src/Eval.hs +57/−0
- src/Job.hs +2/−2
- src/Job/Types.hs +9/−0
- src/Main.hs +2/−0
- src/Repo.hs +28/−9
CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for MiniCI +## 0.1.6 -- 2025-03-30++* Added `jobid` command resolving job reference to canonical ID+* Fix copying of used artifacts to appropriate working directory+ ## 0.1.5 -- 2025-03-20 * Accept job file path on command line
minici.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: minici-version: 0.1.5+version: 0.1.6 synopsis: Minimalist CI framework to run checks on local machine description: Runs defined jobs, for example to build and test a project, for each git@@ -49,6 +49,7 @@ other-modules: Command Command.Checkout+ Command.JobId Command.Run Config Eval
+ src/Command/JobId.hs view
@@ -0,0 +1,39 @@+module Command.JobId (+ JobIdCommand,+) where++import Control.Monad.IO.Class++import Data.Text (Text)+import Data.Text qualified as T+import Data.Text.IO qualified as T++import Command+import Eval+import Job.Types+++data JobIdCommand = JobIdCommand JobRef++instance Command JobIdCommand where+ commandName _ = "jobid"+ commandDescription _ = "Resolve job reference to canonical job ID"++ type CommandArguments JobIdCommand = Text++ commandUsage _ = T.pack $ unlines $+ [ "Usage: minici jobid <job ref>"+ ]++ commandInit _ _ = JobIdCommand . JobRef . T.splitOn "."+ commandExec = cmdJobId+++cmdJobId :: JobIdCommand -> CommandExec ()+cmdJobId (JobIdCommand ref) = do+ config <- getConfig+ einput <- getEvalInput+ JobId ids <- either (tfail . textEvalError) return =<<+ liftIO (runEval (evalJobReference config ref) einput)++ liftIO $ T.putStrLn $ T.intercalate "." $ map textJobIdPart ids
src/Config.hs view
@@ -157,14 +157,14 @@ Left $ prettyPosWithSource pos contents err Right conf -> Right conf -loadConfigForCommit :: MonadIO m => Commit -> m (Either String Config)-loadConfigForCommit commit = do- readCommittedFile commit configFileName >>= return . \case+loadConfigForCommit :: MonadIO m => Tree -> m (Either String Config)+loadConfigForCommit tree = do+ readCommittedFile tree configFileName >>= return . \case Just content -> either (\_ -> Left $ "failed to parse " <> configFileName) Right $ parseConfig content Nothing -> Left $ configFileName <> " not found" -loadJobSetForCommit :: MonadIO m => Commit -> m DeclaredJobSet-loadJobSetForCommit commit = toJobSet <$> loadConfigForCommit commit+loadJobSetForCommit :: (MonadIO m, MonadFail m) => Commit -> m DeclaredJobSet+loadJobSetForCommit commit = return . toJobSet =<< loadConfigForCommit =<< getCommitTree commit where toJobSet configEither = JobSet { jobsetCommit = Just commit
src/Eval.hs view
@@ -1,18 +1,23 @@ module Eval ( EvalInput(..), EvalError(..), textEvalError,+ Eval, runEval, evalJob, evalJobSet,+ evalJobReference, ) where import Control.Monad import Control.Monad.Except+import Control.Monad.Reader import Data.Bifunctor+import Data.List import Data.Text (Text) import Data.Text qualified as T +import Config import Job.Types import Repo @@ -27,6 +32,13 @@ 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 :: EvalInput -> DeclaredJob -> Except EvalError Job evalJob EvalInput {..} decl = do otherCheckout <- forM (jobOtherCheckout decl) $ \( DeclaredJobRepo name, revision, checkout ) -> do@@ -52,3 +64,48 @@ } where runExceptStr = first (T.unpack . textEvalError) . runExcept+++canonicalJobName :: [ Text ] -> Config -> Eval [ JobIdPart ]+canonicalJobName (r : rs) config = do+ einput <- ask+ let name = JobName r+ case find ((name ==) . jobName) (configJobs config) of+ Just djob -> do+ job <- either throwError return $ runExcept $ evalJob einput djob+ let repos = nub $ map (\( EvaluatedJobRepo 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 config+canonicalCommitConfig [] _ = throwError $ OtherEvalError "expected commit or tree reference"++evalJobReference :: Config -> JobRef -> Eval JobId+evalJobReference config (JobRef rs) =+ fmap JobId $ do+ asks eiContainingRepo >>= \case+ Just defRepo -> do+ canonicalCommitConfig rs defRepo+ Nothing -> do+ canonicalJobName rs config
src/Job.hs view
@@ -343,10 +343,10 @@ let target = adir </> T.unpack tname </> takeFileName path liftIO $ do createDirectoryIfMissing True $ takeDirectory target- copyFile (checkoutPath </> path) target+ copyFile path target return $ ArtifactOutput { aoutName = name- , aoutWorkPath = path+ , aoutWorkPath = makeRelative checkoutPath path , aoutStorePath = target }
src/Job/Types.hs view
@@ -68,3 +68,12 @@ | JobIdCommit CommitId | JobIdTree TreeId deriving (Eq, Ord)++newtype JobRef = JobRef [ Text ]+ deriving (Eq, Ord)++textJobIdPart :: JobIdPart -> Text+textJobIdPart = \case+ JobIdName name -> textJobName name+ JobIdCommit cid -> textCommitId cid+ JobIdTree tid -> textTreeId tid
src/Main.hs view
@@ -19,6 +19,7 @@ import Command import Command.Checkout+import Command.JobId import Command.Run import Config import Repo@@ -73,6 +74,7 @@ commands = ( SC $ Proxy @RunCommand) NE.:| [ SC $ Proxy @CheckoutCommand+ , SC $ Proxy @JobIdCommand ] lookupCommand :: String -> Maybe SomeCommandType
src/Repo.hs view
@@ -9,7 +9,8 @@ Tag(..), openRepo,- readCommit,+ readCommit, tryReadCommit,+ readTree, tryReadTree, readBranch, readTag, listCommits,@@ -63,6 +64,9 @@ , gitWatchedBranches :: MVar (Map Text [ TVar (Maybe Commit) ]) } +instance Show Repo where+ show GitRepo {..} = gitDir+ data DeclaredRepo = DeclaredRepo { repoName :: RepoName , repoPath :: FilePath@@ -164,11 +168,26 @@ return $ Commit {..} readCommit :: (MonadIO m, MonadFail m) => Repo -> Text -> m Commit-readCommit repo@GitRepo {..} ref = liftIO $ do- readProcessWithExitCode "git" [ "--git-dir=" <> gitDir, "rev-parse", "--verify", "--quiet", T.unpack ref <> "^{commit}" ] "" >>= \case- ( ExitSuccess, out, _ ) | cid : _ <- lines out -> mkCommit repo (CommitId $ BC.pack cid)- _ -> fail $ "revision `" <> T.unpack ref <> "' not found in `" <> gitDir <> "'"+readCommit repo@GitRepo {..} ref = maybe (fail err) return =<< tryReadCommit repo ref+ where err = "revision `" <> T.unpack ref <> "' not found in `" <> gitDir <> "'" +tryReadCommit :: (MonadIO m, MonadFail m) => Repo -> Text -> m (Maybe Commit)+tryReadCommit repo ref = sequence . fmap (mkCommit repo . CommitId) =<< tryReadObjectId repo "commit" ref++readTree :: (MonadIO m, MonadFail m) => Repo -> Text -> m Tree+readTree repo@GitRepo {..} ref = maybe (fail err) return =<< tryReadTree repo ref+ where err = "tree `" <> T.unpack ref <> "' not found in `" <> gitDir <> "'"++tryReadTree :: (MonadIO m, MonadFail m) => Repo -> Text -> m (Maybe Tree)+tryReadTree repo ref = return . fmap (Tree repo . TreeId) =<< tryReadObjectId repo "tree" ref++tryReadObjectId :: (MonadIO m, MonadFail m) => Repo -> Text -> Text -> m (Maybe ByteString)+tryReadObjectId GitRepo {..} otype ref = do+ liftIO (readProcessWithExitCode "git" [ "--git-dir=" <> gitDir, "rev-parse", "--verify", "--quiet", T.unpack ref <> "^{" <> T.unpack otype <> "}" ] "") >>= \case+ ( ExitSuccess, out, _ ) | oid : _ <- lines out -> return $ Just $ BC.pack oid+ _ -> return Nothing++ readCommitFromFile :: MonadIO m => Repo -> FilePath -> m (Maybe Commit) readCommitFromFile repo@GitRepo {..} path = liftIO $ do try @IO @IOException (BC.readFile $ gitDir </> path) >>= \case@@ -325,11 +344,11 @@ _ -> readCommit repo "HEAD" -readCommittedFile :: MonadIO m => Commit -> FilePath -> m (Maybe BL.ByteString)-readCommittedFile Commit {..} path = do- let GitRepo {..} = commitRepo+readCommittedFile :: MonadIO m => Tree -> FilePath -> m (Maybe BL.ByteString)+readCommittedFile Tree {..} path = do+ let GitRepo {..} = treeRepo liftIO $ withMVar gitLock $ \_ -> do- let cmd = (proc "git" [ "--git-dir=" <> gitDir, "cat-file", "blob", showCommitId commitId_ <> ":" <> path ])+ let cmd = (proc "git" [ "--git-dir=" <> gitDir, "cat-file", "blob", showTreeId treeId <> ":" <> path ]) { std_in = NoStream , std_out = CreatePipe }