{-# LANGUAGE LambdaCase #-}
{-| Calling LiquidHaskell via the source plugin.
This executable is a simple wrapper around 'ghc', which gets passed an '-fplugin' option.
-}
import Control.Monad
import System.Environment (lookupEnv, getArgs)
import System.Process
import System.Exit
import Data.Maybe
import Data.Either (partitionEithers)
import Data.Bifunctor
import Data.Functor ((<&>))
import qualified System.Console.CmdArgs.Explicit as CmdArgs
import Data.List (partition, isPrefixOf, (\\))
import Language.Haskell.Liquid.UX.CmdLine (config, printLiquidHaskellBanner, getOpts)
type GhcArg = String
type LiquidArg = String
partitionArgs :: [String] -> ([GhcArg], [LiquidArg])
partitionArgs args = partitionEithers (map parseArg args)
where
parseArg :: String -> Either GhcArg LiquidArg
parseArg a
| forwardToGhc a = Left a
| otherwise = bimap (const a) (const a) (CmdArgs.process config [a])
-- Unfortunate consequence of the facts things like '-i' needs to be forwarded to GHC
-- and not the LH executable.
forwardToGhc :: String -> Bool
forwardToGhc = isPrefixOf "-i"
helpNeeded :: [String] -> Bool
helpNeeded = elem "--help"
main :: IO a
main = do
-- If no args are passed, display the help instead of ghc's "no input files." To do so,
-- due to the fact GHC needs to always have an input file to actually run a source plugin, we
-- run this with '--interactive'.
args <- getArgs <&> \case [] -> ["--interactive", "--help"]
xs -> "--make" : xs
ghcPath <- fromMaybe "ghc" <$> lookupEnv "LIQUID_GHC_PATH"
-- Strip targets out of the arguments, so that we can forward them to GHC before they
-- get intercepted by the LH parser.
let (cliArgs, targets) = partition (isPrefixOf "-") args
let (ghcArgs, liquidArgs) = partitionArgs cliArgs
-- NOTE: Typically for the executable we want to recompile everything-everytime so that
-- we could always get an "answer" out of LH. However, using `-fforce-recomp` as the default
-- is dangerous, because the executable is used also during tests, so runtime is going to be
-- badly affected. If users wants to enable recompilation, they would simply pass
-- '-fforce-recomp' as a CLI argument.
let p = proc ghcPath $ [ "-O0"
, "-no-link"
, "-fplugin=LiquidHaskell"
, "-plugin-package", "liquidhaskell"
, "-package", "liquid-ghc-prim"
, "-package", "liquid-base"
, "-package", "liquid-containers"
, "-package", "liquid-prelude"
, "-package", "liquid-vector"
, "-package", "liquid-bytestring"
, "-hide-package", "ghc-prim"
, "-hide-package", "base"
, "-hide-package", "containers"
, "-hide-package", "vector"
, "-hide-package", "bytestring"
, "-fplugin-opt=LiquidHaskell:--normal" -- normal logging.
]
<> map (mappend "-fplugin-opt=LiquidHaskell:") liquidArgs
<> ghcArgs
<> targets
-- Call into 'getOpts' so that things like the json reporter will correctly set the verbosity of the
-- logging and avoid printing the banner.
_ <- getOpts (args \\ ghcArgs)
unless (helpNeeded args) printLiquidHaskellBanner
withCreateProcess p $ \_mbStdIn _mbStdOut _mbStdErr pHandle -> waitForProcess pHandle >>= exitWith