packages feed

cachix-1.7.3: src/Cachix/Client/NixVersion.hs

-- TODO: we may need to revisit this when the various flavours of Nix start to diverge
module Cachix.Client.NixVersion
  ( assertNixVersion,
    parseNixVersion,
    minimalVersion,
  )
where

import Data.Either.Extra (mapLeft)
import Data.Text as T
import Data.Versions hiding (versioning')
import Protolude
import System.Process (readProcessWithExitCode)
import Text.Megaparsec (Parsec, anySingle, choice, eof, lookAhead, manyTill, parse)
import qualified Text.Megaparsec as P
import Text.Megaparsec.Char (digitChar)

minimalVersion :: SemVer
minimalVersion =
  semver "2.0.1" & fromRight (panic "Couldn't parse minimalVersion")

assertNixVersion :: IO (Either Text ())
assertNixVersion = do
  nixVersion <- fmap (>>= parseNixVersion) getRawNixVersion
  return $ case nixVersion of
    Left err -> Left err
    Right ver
      | ver < Ideal minimalVersion -> Left "Nix 2.0.2 or lower is not supported. Please upgrade: https://nixos.org/nix/"
      | otherwise -> Right ()

getRawNixVersion :: IO (Either Text Text)
getRawNixVersion = do
  (exitcode, out, err) <- readProcessWithExitCode "nix-env" ["--version"] mempty
  unless (err == "") $ putStrLn $ "nix-env stderr: " <> err
  return $ case exitcode of
    ExitFailure i -> Left $ "'nix-env --version' exited with " <> show i
    ExitSuccess -> Right (toS out)

parseNixVersion :: Text -> Either Text Versioning
parseNixVersion input =
  mapLeft fromParsingError $ parse nixVersionParser "Nix version" input
  where
    fromParsingError pe =
      unlines
        [ "Couldn't parse 'nix-env --version' output: " <> input,
          T.pack $ errorBundlePretty pe
        ]

-- | Parses a semver string out of the output of `nix-env --version` or `nix --version`.
nixVersionParser :: Parsec Void Text Versioning
nixVersionParser = do
  _ <- manyTill anySingle (lookAhead digitChar)
  v <- versioning'
  _ <- manyTill anySingle eof
  pure v

-- | Same as `versioning'`, but without the restriction of preceding eof.
versioning' :: Parsec Void Text Versioning
versioning' =
  choice
    [ P.try (fmap Ideal semver'),
      P.try (fmap General version'),
      fmap Complex mess'
    ]