module Stack2nix.Util ( assertMinVer , extractVersion , mapPool , logDebug , ensureExecutableExists , ensureExecutable ) where import Control.Concurrent.Async import Control.Concurrent.MSem import Control.Exception (onException) import Control.Monad (unless) import Data.Maybe (listToMaybe) import qualified Data.Traversable as T import Data.Version (Version (..), parseVersion, showVersion) import Data.Text (pack, strip, unpack) import GHC.Exts (sortWith) import Stack2nix.External.Util (runCmd) import Stack2nix.Types (Args, argVerbose) import System.Directory (findExecutable) import System.Environment (getEnv, setEnv) import System.Exit (ExitCode (..)) import System.IO (hPutStrLn, stderr) import System.Process (readProcessWithExitCode) import Text.ParserCombinators.ReadP (readP_to_S) import Text.Regex.PCRE (AllTextMatches (..), getAllTextMatches, (=~)) -- Credit: https://stackoverflow.com/a/18898822/204305 mapPool :: T.Traversable t => Int -> (a -> IO b) -> t a -> IO (t b) mapPool max' f xs = do sem <- new max' mapConcurrently (with sem . f) xs -- heuristic for parsing version from stdout extractVersion :: String -> Maybe Version extractVersion str = ver where firstLine = head . lines $ str candidateVers = getAllTextMatches (firstLine =~ "[\\d\\.]+" :: AllTextMatches [] String) bestMatch = head . reverse . sortWith length $ candidateVers ver = fmap fst . listToMaybe . reverse . readP_to_S parseVersion $ bestMatch assertMinVer :: String -> String -> IO () assertMinVer prog minVer = do hPutStrLn stderr $ unwords ["Ensuring", prog, "version is >=", minVer, "..."] result <- runCmd prog ["--version"] `onException` error ("Failed to run " ++ prog ++ ". Not found in PATH.") case result of (ExitSuccess, out, _) -> let ver = extractVersion out in unless (ver >= extractVersion minVer) $ error $ unwords ["ERROR:", prog, "version must be", minVer, "or higher. Current version:", maybe "[parse failure]" showVersion ver] (ExitFailure _, _, err) -> error err logDebug :: Args -> String -> IO () logDebug args msg | argVerbose args = hPutStrLn stderr msg | otherwise = return () -- check if executable is present, if not provision it with nix ensureExecutableExists :: String -> String -> IO () ensureExecutableExists executable nixAttr = do exec <- findExecutable executable case exec of Just _ -> return () Nothing -> ensureExecutable nixAttr -- given nixAttr, build it and add $out/bin to $PATH ensureExecutable :: String -> IO () ensureExecutable nixAttr = do (exitcode2, stdout, err2) <- readProcessWithExitCode "nix-build" ["-A", nixAttr, "", "--no-build-output", "--no-out-link"] mempty case exitcode2 of ExitSuccess -> do hPutStrLn stderr $ err2 path <- getEnv "PATH" setEnv "PATH" (unpack (strip (pack stdout)) ++ "/bin" ++ ":" ++ path) ExitFailure _ -> error $ nixAttr ++ " failed to build via nix"