packages feed

hie-bios-0.20.0: exe/Main.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Main where

import Control.Monad ( forM )
import qualified Colog.Core as L
import Data.Version (showVersion)
import Prettyprinter
import Options.Applicative
import System.Directory (getCurrentDirectory, makeAbsolute)
import System.IO (stdout, hSetEncoding, utf8)
import System.FilePath( (</>) )

import HIE.Bios
import HIE.Bios.Ghc.Check
import HIE.Bios.Ghc.Gap as Gap
import HIE.Bios.Internal.Debug
import HIE.Bios.Types (LoadMode(..), TargetWithContext (..), singleTarget)
import Paths_hie_bios
import Data.Void (Void)
import Data.Function

----------------------------------------------------------------

progVersion :: String
progVersion = "hie-bios version " ++ showVersion version ++ " compiled by GHC " ++ Gap.ghcVersion ++ "\n"

data UseLoadMode
  = UseSingleFile
  | UseMultiFile
  | UseUnitsInferred
  | UseUnitsFromCradle

data Cli = Cli
  { logLevel :: Maybe L.Severity
  , biosCommand :: Command
  }

data Command
  = Check { checkTargetFiles :: [FilePath] }
  | Flags { flagTargetFiles :: [FilePath] }
  | Debug { debugUseMultiLoadMode :: UseLoadMode, debugComponents :: [FilePath] }
  | ConfigInfo { configFiles :: [FilePath] }
  | CradleInfo { cradleFiles :: [FilePath] }
  | Root
  | Version


filepathParser :: Parser FilePath
filepathParser = argument str ( metavar "TARGET_FILES...")

progInfo :: ParserInfo Cli
progInfo = info (cliParser <**> helper)
  ( fullDesc
  <> progDesc "hie-bios is the way to specify how haskell-language-server and ghcide set up a GHC API session.\
              \Delivers the full set of flags to pass to GHC in order to build the project."
  <> header progVersion
  <> footer "You can report issues/contribute at https://github.com/mpickering/hie-bios")

cliParser :: Parser Cli
cliParser = Cli
  <$> optional sevParser
  <*> progParser

sevParser :: Parser L.Severity
sevParser =
  flag' L.Debug (short 'v')

progParser :: Parser Command
progParser = hsubparser
    (command "check" (info (Check <$> some filepathParser) (progDesc "Try to load modules into the GHC API."))
    <> command "flags" (info (Flags <$> some filepathParser) (progDesc "Print out the options that hie-bios thinks you will need to load a file."))
    <> command "debug" (info (Debug <$> loadModeParser <*> many filepathParser) (progDesc "Print out the options that hie-bios thinks you will need to load a file."))
    <> command "config" (info (ConfigInfo <$> some filepathParser) (progDesc "Print out the cradle config location."))
    <> command "cradle" (info (CradleInfo <$> some filepathParser) (progDesc "Print out only the cradle type."))
    <> command "root" (info (pure Root) (progDesc "Display the path towards the selected hie.yaml."))
    <> command "version" (info (pure Version) (progDesc "Print version and exit."))
    )

loadModeParser :: Parser UseLoadMode
loadModeParser =
  flag UseSingleFile UseMultiFile (long "multi" <> help "Load all targets in bulk if supported")
  <|> flag UseSingleFile UseUnitsInferred (long "inferred-units" <> help "Load all units in bulk if supported")
  <|> flag UseSingleFile UseUnitsFromCradle (long "cradle-units" <> help "Load all units from cradle components in bulk if supported")

----------------------------------------------------------------

main :: IO ()
main = do
    hSetEncoding stdout utf8
    cwd <- getCurrentDirectory
    cli <- execParser progInfo
    let
      printLog (L.WithSeverity l sev) = "[" ++ show sev ++ "] " ++ show (pretty l)
      logger :: forall a . Pretty a => L.LogAction IO (L.WithSeverity a)
      logger = L.logStringStderr
        & L.cmap printLog
        & L.cfilter (\msg -> case logLevel cli of
            Nothing -> False
            Just lvl -> L.getSeverity msg >= lvl )

    cradle <-
        -- find cradle does a takeDirectory on the argument, so make it into a file
        findCradle (cwd </> "File.hs") >>= \case
          Just yaml -> loadCradle logger yaml
          Nothing -> loadImplicitCradle logger (cwd </> "File.hs")

    res <- case biosCommand cli of
      Check targetFiles -> checkSyntax logger cradle targetFiles
      Debug useMultiStyle files -> do
        absFiles <- traverse makeAbsolute files
        debugFiles absFiles useMultiStyle cradle
      Flags files -> case files of
        -- TODO force optparse to acquire one
        [] -> error "too few arguments"
        _ -> do
          res <- forM files $ \fp -> do
                  res <- getCompilerOptions (TargetWithContext fp []) LoadFile cradle
                  case res of
                      CradleFail (CradleError _deps _ex err _fps) ->
                        return $ "Failed to show flags for \""
                                                  ++ fp
                                                  ++ "\": " ++ show err
                      CradleSuccess opts ->
                        return $ unlines ["Options: " ++ show (componentOptions opts)
                                        ,"ComponentDir: " ++ componentRoot opts
                                        ,"Dependencies: " ++ show (componentDependencies opts) ]
                      CradleNone -> return $ "No flags/None Cradle: component " ++ fp ++ " should not be loaded"
          return (unlines res)
      ConfigInfo files -> configInfo files
      CradleInfo files -> cradleInfo logger files
      Root    -> rootInfo cradle
      Version -> return progVersion
    putStr res

debugFiles :: [FilePath] -> UseLoadMode -> Cradle Void -> IO String
debugFiles fps useLoadMode cradle = case useLoadMode of
  UseSingleFile -> debugSingle
  UseMultiFile -> debugBulk LoadFileWithContext
  UseUnitsInferred -> debugBulk LoadUnitsInferred
  UseUnitsFromCradle -> debugBulk LoadUnitsFromCradle
  where
    debugSingle = case fps of
      [] -> debugInfo (singleTarget $ cradleRootDir cradle) LoadFile cradle
      _ -> concat <$> traverse (\fp -> debugInfo (singleTarget fp) LoadFile cradle) fps

    debugBulk mode = case fps of
      [] -> debugInfo (TargetWithContext (cradleRootDir cradle) []) mode cradle
      fp:otherFps -> debugInfo (TargetWithContext fp otherFps) mode cradle