Improve laminar nix-jobs
This commit is contained in:
parent
039210a6bb
commit
5bfce429db
4 changed files with 175 additions and 133 deletions
|
@ -28,8 +28,8 @@ let
|
||||||
git "push" "--all" "-f" mirror
|
git "push" "--all" "-f" mirror
|
||||||
jobMay <- lookupEnv "GL_OPTION_CI_JOB"
|
jobMay <- lookupEnv "GL_OPTION_CI_JOB"
|
||||||
whenJust jobMay $ \job -> do
|
whenJust jobMay $ \job -> do
|
||||||
say "Queuing job:"
|
jobName <- decodeUtf8 <$> (laminarc "queue" job |> captureTrim)
|
||||||
laminarc "queue" job
|
say [i|Queued job #{jobName}.\nSee https://ci.maralorn.de/jobs/#{T.replace ":" "/" jobName}|]
|
||||||
deployMay <- lookupEnv "GL_OPTION_WEB_DEPLOY"
|
deployMay <- lookupEnv "GL_OPTION_WEB_DEPLOY"
|
||||||
whenJust deployMay $ \deploy -> do
|
whenJust deployMay $ \deploy -> do
|
||||||
(maybe [] (\x -> ["-A", x]) -> target) <- lookupEnv "GL_OPTION_WEB_DEPLOY_NIX_TARGET"
|
(maybe [] (\x -> ["-A", x]) -> target) <- lookupEnv "GL_OPTION_WEB_DEPLOY_NIX_TARGET"
|
||||||
|
|
|
@ -33,7 +33,7 @@ in {
|
||||||
jobs = {
|
jobs = {
|
||||||
"nix-build.run" = pkgs.writeShellScript "nix-build" ''
|
"nix-build.run" = pkgs.writeShellScript "nix-build" ''
|
||||||
set -e
|
set -e
|
||||||
nix-jobs realize-here "$DERIVATION"
|
PATH=${lib.makeBinPath [ pkgs.laminar pkgs.nix ]}:$PATH nix-jobs realise-here "$DERIVATION"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,8 +9,10 @@ let
|
||||||
${setup}
|
${setup}
|
||||||
export HOME=$PWD
|
export HOME=$PWD
|
||||||
git clone git@localhost:kassandra2 kassandra
|
git clone git@localhost:kassandra2 kassandra
|
||||||
DRV=$(nix-instantiate kassandra/release.nix -A ${name} --add-root ./drv --indirect)
|
echo "Evaluating nix-expression."
|
||||||
nix-jobs realise "DERIVATION=$DRV"
|
drv=$(readlink -f $(nix-instantiate kassandra/release.nix -A ${name} --add-root ./drv --indirect))
|
||||||
|
echo "Evaluation done."
|
||||||
|
nix-jobs realise $drv
|
||||||
'';
|
'';
|
||||||
in {
|
in {
|
||||||
services.laminar.cfgFiles.jobs = {
|
services.laminar.cfgFiles.jobs = {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
{-# LANGUAGE TupleSections #-}
|
{-# LANGUAGE TupleSections #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
@ -6,40 +7,52 @@
|
||||||
{-# LANGUAGE ExtendedDefaultRules #-}
|
{-# LANGUAGE ExtendedDefaultRules #-}
|
||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# OPTIONS_GHC -Wall -Werror -Wno-missing-signatures -Wno-type-defaults -Wno-orphans #-}
|
{-# OPTIONS_GHC -Wall -Werror -Wno-missing-signatures -Wno-type-defaults -Wno-orphans #-}
|
||||||
|
|
||||||
import Control.Concurrent ( threadDelay )
|
import Control.Concurrent ( threadDelay )
|
||||||
import Control.Concurrent.Async ( forConcurrently )
|
import Control.Concurrent.Async ( forConcurrently_ )
|
||||||
import Control.Exception ( IOException
|
import Control.Concurrent.STM ( check )
|
||||||
, bracket
|
import Control.Exception ( bracket
|
||||||
, catch
|
, catch
|
||||||
, handle
|
, handle
|
||||||
|
, handleJust
|
||||||
|
, mapException
|
||||||
|
, throwIO
|
||||||
)
|
)
|
||||||
import Data.Bits ( Bits((.|.)) )
|
import Data.Bits ( Bits((.|.)) )
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
import Data.String.Interpolate ( i )
|
import Data.String.Interpolate ( i )
|
||||||
|
import Data.Text ( isInfixOf
|
||||||
|
, splitOn
|
||||||
|
, strip
|
||||||
|
)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Relude
|
import Relude
|
||||||
import Say ( say
|
import Say ( say
|
||||||
, sayErr
|
, sayErr
|
||||||
)
|
)
|
||||||
import Shh ( ExecArg
|
import Shh ( (&!>)
|
||||||
|
, ExecArg(..)
|
||||||
, ExecReference(Absolute)
|
, ExecReference(Absolute)
|
||||||
|
, Stream(StdOut)
|
||||||
, captureTrim
|
, captureTrim
|
||||||
, load
|
, load
|
||||||
, (|>)
|
, (|>)
|
||||||
)
|
)
|
||||||
import System.Directory ( doesFileExist
|
import System.Directory ( createDirectoryIfMissing
|
||||||
|
, doesFileExist
|
||||||
, removeFile
|
, removeFile
|
||||||
)
|
)
|
||||||
import System.Environment ( getArgs
|
import System.Environment ( getArgs )
|
||||||
, getEnv
|
|
||||||
)
|
|
||||||
import System.FSNotify ( Event(Removed)
|
import System.FSNotify ( Event(Removed)
|
||||||
, stopManager
|
|
||||||
, watchDir
|
, watchDir
|
||||||
, withManager
|
, withManager
|
||||||
)
|
)
|
||||||
|
import System.IO ( BufferMode(LineBuffering)
|
||||||
|
, hSetBuffering
|
||||||
|
)
|
||||||
|
import System.IO.Error
|
||||||
import System.Posix.Files ( groupReadMode
|
import System.Posix.Files ( groupReadMode
|
||||||
, otherReadMode
|
, otherReadMode
|
||||||
, ownerReadMode
|
, ownerReadMode
|
||||||
|
@ -58,191 +71,218 @@ load Absolute ["laminarc", "nix-store"]
|
||||||
|
|
||||||
data JobResult = Success | Failure deriving (Show, Read, Eq, Ord, Enum)
|
data JobResult = Success | Failure deriving (Show, Read, Eq, Ord, Enum)
|
||||||
|
|
||||||
|
data ReportLevel = None | Self | Children deriving (Show, Eq, Ord, Enum)
|
||||||
|
|
||||||
|
newtype JobException = JobException Text deriving (Show, Exception)
|
||||||
|
throw = throwIO . JobException
|
||||||
|
newtype WaitException = WaitException Text deriving (Show, Exception)
|
||||||
|
throwWait = throwIO . WaitException
|
||||||
|
|
||||||
instance Semigroup JobResult where
|
instance Semigroup JobResult where
|
||||||
Success <> Success = Success
|
Success <> Success = Success
|
||||||
_ <> _ = Failure
|
_ <> _ = Failure
|
||||||
|
|
||||||
instance Monoid JobResult where
|
instance Monoid JobResult where
|
||||||
mempty = Success
|
mempty = Success
|
||||||
-- 2 Entry points
|
|
||||||
-- 1. Realise i.e. "wait-for-and-trigger-build-if-necessary"
|
|
||||||
-- 2. Job i.e. "the actual realisation"
|
|
||||||
|
|
||||||
instance ExecArg Text where
|
instance ExecArg Text where
|
||||||
|
asArg = asArg . toString
|
||||||
|
asArgFromList = asArgFromList . fmap toString
|
||||||
|
|
||||||
|
whenSelf level = when (level >= Self)
|
||||||
|
whenChildren level = when (level >= Children)
|
||||||
|
levelPrec Children = Self
|
||||||
|
levelPrec _ = None
|
||||||
getDependenciesFromNix :: Text -> IO (Seq Text)
|
getDependenciesFromNix :: Text -> IO (Seq Text)
|
||||||
getDependenciesFromNix derivationName = do
|
getDependenciesFromNix derivationName = do
|
||||||
everythingToDo <- nixStoreRealiseDryRun derivationName
|
everythingToDo <- nixStoreRealiseDryRun derivationName
|
||||||
pure (Seq.filter (/= derivationName) everythingToDo)
|
pure (Seq.filter (/= derivationName) everythingToDo)
|
||||||
|
|
||||||
nixStoreRealiseDryRun :: Text -> IO (Seq Text)
|
nixStoreRealiseDryRun :: Text -> IO (Seq Text)
|
||||||
nixStoreRealiseDryRun derivationName =
|
nixStoreRealiseDryRun derivationName = do
|
||||||
process <$> (nix_store "-r" derivationName "--dry-run" |> captureTrim)
|
process
|
||||||
|
<$> (nix_store "-r" derivationName "--dry-run" &!> StdOut |> captureTrim)
|
||||||
where
|
where
|
||||||
process =
|
process =
|
||||||
fromList
|
fromList
|
||||||
|
. drop 1
|
||||||
. dropWhile (/= "these derivations will be built:")
|
. dropWhile (/= "these derivations will be built:")
|
||||||
. fmap T.strip
|
. fmap strip
|
||||||
. lines
|
. lines
|
||||||
. decodeUtf8
|
. decodeUtf8
|
||||||
|
|
||||||
job :: Text -> IO ()
|
job :: Text -> IO ()
|
||||||
job derivationName = do
|
job derivationName = do
|
||||||
say [i|Initiating realisation for #{derivationName}.|]
|
say [i|Building #{derivationName}.|]
|
||||||
pathInfo <- getPathInfo derivationName
|
|
||||||
let setResult result = do
|
let setResult result = do
|
||||||
writeFileText (resultPath pathInfo) (show result)
|
createDirectoryIfMissing True resultDir
|
||||||
removeFile (runningPath pathInfo)
|
writeFileText (resultPath derivationName) (show result)
|
||||||
ensureDeps True derivationName >>= \case
|
removeFile (runningPath derivationName)
|
||||||
Success -> do
|
ensureDeps Children derivationName
|
||||||
say [i|All dependencies provided, starting build for #{derivationName}.|]
|
catch
|
||||||
catch
|
(nixStoreRealise derivationName)
|
||||||
(nixStoreRealise derivationName)
|
(\(err :: SomeException) -> do
|
||||||
(\(err :: SomeException) -> do
|
setResult Failure
|
||||||
setResult Failure
|
sayErr [i|nix-build failed with error #{err}.|]
|
||||||
sayErr [i|nix-build failed with error #{err}.|]
|
exitFailure
|
||||||
exitFailure
|
)
|
||||||
)
|
setResult Success
|
||||||
setResult Success
|
say [i|Build for #{derivationName} successful.|]
|
||||||
say [i|Build for #{derivationName} successful. Finishing.|]
|
|
||||||
Failure -> do
|
|
||||||
sayErr
|
|
||||||
[i|Couldn‘t build #{derivationName} because of failing dependency.|]
|
|
||||||
|
|
||||||
nixStoreRealise :: Text -> IO ()
|
nixStoreRealise :: Text -> IO ()
|
||||||
nixStoreRealise = nix_store "-r"
|
nixStoreRealise = nix_store "-r"
|
||||||
|
|
||||||
ensureDeps :: Bool -> Text -> IO JobResult
|
ensureDeps :: ReportLevel -> Text -> IO ()
|
||||||
ensureDeps topLevel derivationName = do
|
ensureDeps level derivationName = do
|
||||||
dependencies <- getDependenciesFromNix derivationName
|
dependencies <- getDependenciesFromNix derivationName
|
||||||
when topLevel $ forM_ dependencies $ \dep ->
|
whenChildren level $ forM_ dependencies $ \dep ->
|
||||||
say [i|Requiring build of #{dep}.|]
|
say [i|Requiring build of #{dep}.|]
|
||||||
fold <$> forConcurrently dependencies (realise topLevel)
|
forConcurrently_ dependencies (realise $ levelPrec level)
|
||||||
|
`catch` \(JobException e) ->
|
||||||
|
throw [i|#{e}\nFailed dependency for #{derivationName}|]
|
||||||
|
|
||||||
-- Nothing means failing to acquire lock on the derivation name for starting the job.
|
-- Nothing means failing to acquire lock on the derivation name for starting the job.
|
||||||
tryQueue :: PathInfo -> IO (Maybe Text)
|
tryQueue :: Text -> IO (Maybe Text)
|
||||||
tryQueue pathInfo@(_, _, derivationName) = getRunningJob pathInfo >>= \case
|
tryQueue derivationName = getRunningJob derivationName >>= \case
|
||||||
Nothing -> handle handleIOException $ bracket openNewFile closeFd $ \fd -> do
|
Nothing -> do
|
||||||
jobName <-
|
createDirectoryIfMissing True runningDir
|
||||||
decodeUtf8
|
handleJust (\x -> if isAlreadyExistsError x then Just x else Nothing)
|
||||||
<$> ( laminarc "queue" "nix-build" ([i|DERIVATION=#{derivationName}|] :: Text)
|
(const (pure Nothing))
|
||||||
|> captureTrim
|
$ bracket openNewFile closeFd
|
||||||
)
|
$ \fd -> do
|
||||||
writeCount <- fdWrite fd (toString jobName)
|
jobName <-
|
||||||
when (writeCount == 0) $ sayErr
|
decodeUtf8
|
||||||
[i|Wrote 0 bytes of jobName "#{jobName}" to #{runningPath pathInfo}|]
|
<$> ( laminarc "queue"
|
||||||
pure (Just jobName)
|
"nix-build"
|
||||||
|
([i|DERIVATION=#{derivationName}|] :: Text)
|
||||||
|
|> captureTrim
|
||||||
|
)
|
||||||
|
when (T.null jobName) $ throw [i|Laminarc returned an empty jobName.|]
|
||||||
|
writeCount <- fdWrite fd (toString jobName)
|
||||||
|
when (writeCount == 0)
|
||||||
|
$ throw
|
||||||
|
[i|Wrote 0 bytes of jobName "#{jobName}" to #{runningPath derivationName}|]
|
||||||
|
pure . Just $ jobName
|
||||||
Just _ -> pure Nothing
|
Just _ -> pure Nothing
|
||||||
where
|
where
|
||||||
openNewFile = openFd (runningPath pathInfo)
|
openNewFile = openFd (runningPath derivationName)
|
||||||
WriteOnly
|
WriteOnly
|
||||||
(Just defaultMode)
|
(Just defaultMode)
|
||||||
defaultFileFlags { exclusive = True }
|
defaultFileFlags { exclusive = True }
|
||||||
defaultMode =
|
defaultMode =
|
||||||
ownerReadMode .|. ownerWriteMode .|. groupReadMode .|. otherReadMode
|
ownerReadMode .|. ownerWriteMode .|. groupReadMode .|. otherReadMode
|
||||||
handleIOException :: IOException -> IO (Maybe Text)
|
|
||||||
handleIOException _ = pure Nothing
|
|
||||||
|
|
||||||
-- Nothing means a dependency failed.
|
-- Nothing means a dependency failed.
|
||||||
queueJobWithLaminarc :: Bool -> PathInfo -> IO (Maybe Text)
|
queueJobWithLaminarc :: ReportLevel -> Text -> IO Text
|
||||||
queueJobWithLaminarc topLevel pathInfo@(_, _, derivationName) =
|
queueJobWithLaminarc level derivationName = tryQueue derivationName >>= maybe
|
||||||
tryQueue pathInfo >>= \case
|
(ensureRunningJob level derivationName)
|
||||||
Nothing -> ensureRunningJob topLevel pathInfo
|
(\jobName -> jobName
|
||||||
a -> do
|
<$ say [i|Job #{jobName} started for #{derivationName}. Waiting ...|]
|
||||||
say [i|Queued build job ${jobName} for #{derivationName}.|]
|
)
|
||||||
pure a
|
|
||||||
|
|
||||||
ensureRunningJob :: Bool -> PathInfo -> IO (Maybe Text)
|
ensureRunningJob :: ReportLevel -> Text -> IO Text
|
||||||
ensureRunningJob topLevel pathInfo@(_, _, derivationName) =
|
ensureRunningJob level derivationName = getRunningJob derivationName >>= maybe
|
||||||
getRunningJob pathInfo >>= \case
|
(queueJobWithLaminarc level derivationName)
|
||||||
Nothing -> do
|
(\jobName -> jobName <$ whenSelf
|
||||||
ensureDeps False derivationName >>= \case
|
level
|
||||||
Success -> queueJobWithLaminarc topLevel pathInfo
|
(say [i|Job #{jobName} running for #{derivationName}. Waiting ...|])
|
||||||
Failure -> do
|
)
|
||||||
sayErr
|
|
||||||
[i|Could not realise #{derivationName} because of failed dependency.|]
|
|
||||||
pure Nothing
|
|
||||||
Just jobName -> do
|
|
||||||
when topLevel
|
|
||||||
$ say [i|Waiting for job #{jobName} running for #{derivationName}|]
|
|
||||||
pure (Just jobName)
|
|
||||||
|
|
||||||
|
drvBasename derivationName =
|
||||||
|
fromMaybe derivationName . viaNonEmpty last $ splitOn "/" derivationName
|
||||||
|
|
||||||
type PathInfo = (Text, Text, Text)
|
workspace, resultDir, runningDir :: String
|
||||||
|
workspace = "/var/lib/laminar/run/nix-build/workspace"
|
||||||
getPathInfo :: Text -> IO PathInfo
|
resultDir = [i|#{workspace}/completed-jobs|]
|
||||||
getPathInfo derivationName = do
|
runningDir = [i|#{workspace}/running-jobs|]
|
||||||
workSpace <- toText <$> getEnv "WORKSPACE"
|
runningPath :: Text -> String
|
||||||
(workSpace, , derivationName)
|
runningPath p = [i|#{runningDir}/#{drvBasename p}|]
|
||||||
. decodeUtf8
|
resultPath :: Text -> String
|
||||||
<$> (nix_store "-q" "--hash" derivationName |> captureTrim)
|
resultPath p = [i|#{resultDir}/#{drvBasename p}|]
|
||||||
|
|
||||||
runningDir :: PathInfo -> String
|
|
||||||
runningDir (workSpace, _, _) = [i|#{workSpace}/running-jobs|]
|
|
||||||
runningPath :: PathInfo -> String
|
|
||||||
runningPath (workSpace, drvHash, _) = [i|#{workSpace}/running-jobs/#{drvHash}|]
|
|
||||||
resultPath :: PathInfo -> String
|
|
||||||
resultPath (workSpace, drvHash, _) =
|
|
||||||
[i|#{workSpace}/completed-jobs/#{drvHash}|]
|
|
||||||
|
|
||||||
-- Nothing means there is no running Job.
|
-- Nothing means there is no running Job.
|
||||||
getRunningJob :: PathInfo -> IO (Maybe Text)
|
getRunningJob :: Text -> IO (Maybe Text)
|
||||||
getRunningJob p = do
|
getRunningJob p = do
|
||||||
let path = runningPath p
|
let path = runningPath p
|
||||||
pathExists <- doesFileExist path
|
pathExists <- doesFileExist path
|
||||||
if pathExists
|
if pathExists
|
||||||
then catch (Just <$> readFileText path)
|
then
|
||||||
(\(_ :: IOException) -> getRunningJob p)
|
handleJust (guard . isDoesNotExistError) (const $ pure Nothing)
|
||||||
|
$ Just
|
||||||
|
<$> readFileText path
|
||||||
else pure Nothing
|
else pure Nothing
|
||||||
|
|
||||||
realise :: Bool -> Text -> IO JobResult
|
realise :: ReportLevel -> Text -> IO ()
|
||||||
realise topLevel derivationName = do
|
realise level derivationName = do
|
||||||
pathInfo <- getPathInfo derivationName
|
ensureDeps level derivationName
|
||||||
jobNameMay <- ensureRunningJob topLevel pathInfo
|
jobName <- ensureRunningJob level derivationName
|
||||||
case jobNameMay of
|
handle
|
||||||
Just jobName -> do
|
(\(WaitException e) -> do
|
||||||
result <- waitForJob pathInfo
|
sayErr
|
||||||
case result of
|
[i|Retrying to find or create a job for #{derivationName} after waiting for job failed with error "#{e}" |]
|
||||||
Success -> when topLevel
|
realise level derivationName
|
||||||
$ say [i|#{derivationName} succesfully realised by job #{jobName}|]
|
)
|
||||||
Failure -> sayErr [i|#{derivationName} failed in job #{jobName}|]
|
$ do
|
||||||
pure result
|
waitForJob derivationName >>= \case
|
||||||
Nothing -> pure Failure
|
Success -> whenSelf level
|
||||||
|
$ say [i|Job #{jobName} completed build for #{derivationName}.|]
|
||||||
|
Failure -> throw [i|Job #{jobName} failed build #{derivationName}.|]
|
||||||
|
|
||||||
waitForJob :: PathInfo -> IO JobResult
|
waitForJob :: Text -> IO JobResult
|
||||||
waitForJob pathInfo = do
|
waitForJob derivationName = do
|
||||||
_ <- withManager $ \manager -> do
|
done <- newTVarIO False
|
||||||
_ <- watchDir manager
|
let finished = atomically (writeTVar done True)
|
||||||
(runningDir pathInfo)
|
getJob = go 0
|
||||||
fileDeleted
|
where
|
||||||
(const $ stopManager manager)
|
go count = do
|
||||||
maybeJob <- getRunningJob pathInfo
|
mayJob <- mapException (\(e :: JobException) -> (coerce e :: WaitException))
|
||||||
whenNothing_ maybeJob $ stopManager manager
|
<$> getRunningJob derivationName
|
||||||
forever $ threadDelay 1000000
|
if count < 50 && mayJob == Just ""
|
||||||
readFileText (resultPath pathInfo)
|
then threadDelay 100 >> go (count + 1)
|
||||||
>>= (\case
|
else pure mayJob
|
||||||
Nothing -> do
|
withManager $ \manager -> do
|
||||||
sayErr [i|Failed to parse result from #{resultPath pathInfo}|]
|
_ <- watchDir manager runningDir fileDeleted (const finished)
|
||||||
pure Failure
|
mayJob <- getJob
|
||||||
Just a -> pure a
|
whenNothing_ mayJob finished
|
||||||
|
whenJust mayJob $ \jobName -> do
|
||||||
|
runningJobs <-
|
||||||
|
fmap strip
|
||||||
|
. lines
|
||||||
|
. decodeUtf8
|
||||||
|
<$> (laminarc "show-running" |> captureTrim)
|
||||||
|
unless (any (`isInfixOf` jobName) runningJobs) $ do
|
||||||
|
handleJust (guard . isDoesNotExistError) (const pass)
|
||||||
|
$ removeFile (runningPath derivationName)
|
||||||
|
throwWait
|
||||||
|
[i|File #{runningPath derivationName} is stale. It contains the job name "#{jobName}", but running jobs are only #{runningJobs}. Deleting File.|]
|
||||||
|
atomically $ readTVar done >>= check
|
||||||
|
resultText <-
|
||||||
|
handleJust
|
||||||
|
(guard . isDoesNotExistError)
|
||||||
|
(const $ throwWait
|
||||||
|
[i|Job result file #{resultPath derivationName} does not exist.|]
|
||||||
)
|
)
|
||||||
. readMaybe
|
$ readFile (resultPath derivationName)
|
||||||
. toString
|
maybe
|
||||||
|
(throwWait [i|Failed to parse result from #{resultPath derivationName}.|])
|
||||||
|
pure
|
||||||
|
. readMaybe
|
||||||
|
. toString
|
||||||
|
$ resultText
|
||||||
where
|
where
|
||||||
fileDeleted (Removed a _ _) | a == runningPath pathInfo = True
|
fileDeleted (Removed a _ _) | a == runningPath derivationName = True
|
||||||
fileDeleted _ = False
|
fileDeleted _ = False
|
||||||
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
|
hSetBuffering stdout LineBuffering
|
||||||
whenNotNullM missingExecutables $ \x -> do
|
whenNotNullM missingExecutables $ \x -> do
|
||||||
sayErr [i|Missing executables #{show x}|]
|
sayErr [i|Missing executables #{show x}|]
|
||||||
exitFailure
|
exitFailure
|
||||||
args <- fmap toText <$> getArgs
|
args <- fmap toText <$> getArgs
|
||||||
case args of
|
case args of
|
||||||
["realise-here", derivationName] -> job derivationName
|
["realise-here", derivationName] -> job derivationName
|
||||||
["realise" , derivationName] -> realise True derivationName >>= \case
|
["realise" , derivationName] -> realise Children derivationName
|
||||||
Success -> exitSuccess
|
|
||||||
Failure -> exitFailure
|
|
||||||
_ ->
|
_ ->
|
||||||
sayErr "Usage: realise-here <derivationName> | realise <derivationName>"
|
sayErr "Usage: realise-here <derivationName> | realise <derivationName>"
|
||||||
|
|
Loading…
Reference in a new issue