{-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} module Nix ( nixEvalE , compareVersions , lookupAttrPath , getDerivationFile , getMaintainers , getOldHash , getSrcUrl , getSrcUrls , getIsBroken , build , getDescription , cachix , numberOfFetchers , oldVersionOn ) where import Control.Category ((>>>)) import Control.Error import Control.Monad (void) import Data.Bifunctor (second) import Data.Function ((&)) import Data.Semigroup ((<>)) import Data.Text (Text) import qualified Data.Text as T import Prelude hiding (FilePath) import Shelly (FilePath, Sh, cmd, fromText, run, setStdin, toTextIgnore) import Utils (UpdateEnv(..), rewriteError, shE) data Raw = Raw | NoRaw rawOpt :: Raw -> [Text] rawOpt Raw = ["--raw"] rawOpt NoRaw = [] nixEvalE :: Raw -> Text -> Sh (Either Text Text) nixEvalE raw expr = run "nix" (["eval", "-f", "."] <> rawOpt raw <> [expr]) & (fmap T.strip >>> shE >>> rewriteError ("nix eval failed for " <> expr)) -- Error if the "new version" is actually newer according to nix compareVersions :: UpdateEnv -> Sh (Either Text ()) compareVersions updateEnv = do versionComparison <- nixEvalE NoRaw ("(builtins.compareVersions \"" <> newVersion updateEnv <> "\" \"" <> oldVersion updateEnv <> "\")") return $ case versionComparison of Right "1" -> Right () Right a -> Left $ newVersion updateEnv <> " is not newer than " <> oldVersion updateEnv <> " according to Nix; versionComparison: " <> a Left a -> Left a -- This is extremely slow but gives us the best results we know of lookupAttrPath :: UpdateEnv -> Sh (Either Text Text) lookupAttrPath updateEnv = cmd "nix-env" "-qa" (packageName updateEnv <> "-" <> oldVersion updateEnv) "-f" "." "--attr-path" "--arg" "config" "{ allowBroken = true; allowUnfree = true; allowAliases = false; }" & (fmap (head . T.words . head . T.lines) >>> shE >>> rewriteError "nix-env -q failed to find package name with old version") getDerivationFile :: UpdateEnv -> Text -> Sh (Either Text FilePath) getDerivationFile updateEnv attrPath = cmd "env" "EDITOR=echo" "nix" "edit" attrPath "-f" "." & (fmap T.strip >>> fmap fromText >>> shE >>> rewriteError "Couldn't find derivation file.") getHash :: Text -> Sh (Either Text Text) getHash attrPath = nixEvalE Raw ("pkgs." <> attrPath <> ".src.drvAttrs.outputHash") getOldHash :: Text -> Sh (Either Text Text) getOldHash attrPath = getHash attrPath & rewriteError ("Could not find old output hash at " <> attrPath <> ".src.drvAttrs.outputHash.") getSrcUrl :: Text -> Sh (Either Text Text) getSrcUrl attrPath = nixEvalE Raw ("(let pkgs = import ./. {}; in builtins.elemAt pkgs." <> attrPath <> ".src.drvAttrs.urls 0)") getMaintainers :: Text -> Sh (Either Text Text) getMaintainers attrPath = nixEvalE Raw ("(let pkgs = import ./. {}; gh = m : m.github or \"\"; nonempty = s: s != \"\"; addAt = s: \"@\"+s; in builtins.concatStringsSep \" \" (map addAt (builtins.filter nonempty (map gh pkgs." <> attrPath <> ".meta.maintainers or []))))") & rewriteError ("Could not fetch maintainers for" <> attrPath) readNixBool :: Either Text Text -> Either Text Bool readNixBool (Right "true") = Right True readNixBool (Right "false") = Right False readNixBool (Right a) = Left ("Failed to convert expected nix boolean " <> a) readNixBool (Left e) = Left e getIsBroken :: Text -> Sh (Either Text Bool) getIsBroken attrPath = nixEvalE NoRaw ("(let pkgs = import ./. {}; in pkgs." <> attrPath <> ".meta.broken or false)") & fmap readNixBool & rewriteError ("Could not get meta.broken for attrpath " <> attrPath) getDescription :: Text -> Sh (Either Text Text) getDescription attrPath = nixEvalE NoRaw ("(let pkgs = import ./. {}; in pkgs." <> attrPath <> ".meta.description or \"\")") & rewriteError ("Could not get meta.description for attrpath " <> attrPath) getSrcAttr :: Text -> Text -> Sh (Either Text Text) getSrcAttr attr attrPath = nixEvalE NoRaw ("pkgs." <> attrPath <> ".src." <> attr) getSrcUrls :: Text -> Sh (Either Text Text) getSrcUrls = getSrcAttr "urls" build :: Text -> Sh (Either Text ()) build attrPath = do buildE <- shE $ cmd "nix-build" "--option" "sandbox" "true" "--option" "restrict-eval" "true" "-A" attrPath case buildE of Right _ -> return $ Right () Left _ -> do buildLogE <- cmd "nix" "log" "-f" "." attrPath & (shE >>> (fmap . fmap) (T.lines >>> reverse >>> take 30 >>> reverse >>> T.unlines)) return $ case buildLogE of Left t -> Left "nix log failed trying to get build logs" Right buildLog -> Left ("nix build failed.\n" <> buildLog) cachix :: FilePath -> Sh () cachix resultPath = do setStdin (toTextIgnore resultPath) void $ shE $ cmd "cachix" "push" "r-ryantm" numberOfFetchers :: Text -> Int numberOfFetchers derivationContents = count "fetchurl {" + count "fetchgit {" + count "fetchFromGitHub {" where count x = T.count x derivationContents oldVersionOn :: UpdateEnv -> Text -> Text -> Sh (Either Text ()) oldVersionOn updateEnv branchName contents = pure (assertErr ("Old version not present in " <> branchName <> " derivation file.") (oldVersion updateEnv `T.isInfixOf` contents))