packages feed

hix-0.9.0: lib/Hix/Managed/Flake.hs

module Hix.Managed.Flake where

import qualified Data.Aeson as Aeson
import Data.Aeson (FromJSON)
import qualified Data.ByteString.Char8 as ByteString
import Exon (exon)
import Path (Abs, Dir, Path, toFilePath)
import System.Exit (ExitCode (ExitFailure, ExitSuccess))
import System.Process.Typed (ProcessConfig, proc, readProcess, setWorkingDir)

import Hix.Data.Monad (M)
import qualified Hix.Log as Log
import Hix.Monad (eitherFatal)
import qualified Hix.Color as Color

outLines :: ([ByteString] -> a) -> ByteString -> a
outLines f bs = f (ByteString.lines bs)

runFlakeFor ::
  ∀ a stdin stdout stderr .
  (ByteString -> Either Text a) ->
  (ByteString -> Either Text a) ->
  Text ->
  Path Abs Dir ->
  [Text] ->
  (ProcessConfig () () () -> ProcessConfig stdin stdout stderr) ->
  M a
runFlakeFor processOutput processError desc cwd args confProc = do
  Log.debug [exon|Running flake: #{Color.shellCommand (unwords args)}|]
  readProcess conf >>= \case
    (ExitSuccess, stdout, _) ->
      eitherFatal (first decodeError (processOutput (toStrict stdout)))
    (ExitFailure {}, _, stderr) ->
      eitherFatal (first failureError (processError (toStrict stderr)))
  where
    conf =
      confProc $
      setWorkingDir (toFilePath cwd) $
      proc "nix" (toString <$> args)

    decodeError msg = [exon|#{desc} produced invalid output: #{msg}|]
    failureError msg = [exon|#{desc} terminated with error: #{msg}|]

flakeFailure :: ByteString -> Either Text a
flakeFailure stderr = Left [exon|stderr: #{decodeUtf8 stderr}|]

runFlake ::
  ∀ a stdin stdout stderr .
  FromJSON a =>
  Text ->
  Path Abs Dir ->
  [Text] ->
  (ProcessConfig () () () -> ProcessConfig stdin stdout stderr) ->
  M a
runFlake =
  runFlakeFor success flakeFailure
  where
    success stdout = first toText (Aeson.eitherDecodeStrict' stdout)

runFlakeForSingleLine ::
  ∀ stdin stdout stderr .
  Text ->
  Path Abs Dir ->
  [Text] ->
  (ProcessConfig () () () -> ProcessConfig stdin stdout stderr) ->
  M ByteString
runFlakeForSingleLine =
  runFlakeFor success flakeFailure
  where
    success stdout = case ByteString.lines stdout of
      [ln] -> Right ln
      lns -> Left [exon|Expected a single line of output, got #{show (length lns)}|]

runFlakeRaw ::
  ∀ stdin stdout stderr .
  Text ->
  Path Abs Dir ->
  [Text] ->
  (ProcessConfig () () () -> ProcessConfig stdin stdout stderr) ->
  M ByteString
runFlakeRaw =
  runFlakeFor Right flakeFailure

runFlakeSimple ::
  Text ->
  Path Abs Dir ->
  [Text] ->
  M ()
runFlakeSimple desc cwd args =
  runFlakeFor (const unit) (const unit) desc cwd (["--quiet", "--quiet", "--quiet"] ++ args) id

runFlakeLock :: Path Abs Dir -> M ()
runFlakeLock cwd =
  runFlakeSimple "create lock file" cwd ["flake", "lock"]

runFlakeGen :: Path Abs Dir -> M ()
runFlakeGen cwd =
  runFlakeSimple "generate Cabal and overrides" cwd ["run", ".#gen"]

runFlakeGenCabal :: Path Abs Dir -> M ()
runFlakeGenCabal cwd =
  runFlakeSimple "generate Cabal" cwd ["run", ".#gen-cabal-quiet"]