ghcup-hs/app/ghcup/Main.hs

522 lines
16 KiB
Haskell
Raw Normal View History

2020-02-28 23:33:32 +00:00
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE DuplicateRecordFields #-}
module Main where
2020-03-01 00:05:02 +00:00
import GHCup
2020-03-03 00:59:19 +00:00
import GHCup.Download
import GHCup.Errors
2020-03-01 00:05:02 +00:00
import GHCup.Types
2020-03-03 00:59:19 +00:00
import GHCup.Utils
import GHCup.Utils.File
import GHCup.Utils.Logger
import GHCup.Utils.Prelude
2020-03-01 00:05:02 +00:00
2020-02-28 23:33:32 +00:00
import Control.Monad.Logger
import Control.Monad.Reader
2020-03-01 01:21:40 +00:00
import Control.Monad.Trans.Resource
2020-02-28 23:33:32 +00:00
import Data.Bifunctor
import Data.Char
import Data.List ( intercalate )
import Data.Semigroup ( (<>) )
2020-03-01 00:05:02 +00:00
import Data.String.Interpolate
2020-02-28 23:33:32 +00:00
import Data.String.QQ
import Data.Versions
import Haskus.Utils.Variant.Excepts
2020-03-03 00:59:19 +00:00
import HPath
2020-02-28 23:33:32 +00:00
import Options.Applicative hiding ( style )
import System.Console.Pretty
import System.Exit
2020-03-01 00:05:02 +00:00
import System.IO
2020-03-03 00:59:19 +00:00
import Text.Read
2020-02-28 23:33:32 +00:00
import Text.Layout.Table
2020-03-01 00:05:02 +00:00
import URI.ByteString
import qualified Data.ByteString as B
import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.Text as T
2020-03-03 00:59:19 +00:00
import qualified Data.Text.Encoding as E
2020-02-28 23:33:32 +00:00
data Options = Options
2020-03-01 11:54:46 +00:00
{
-- global options
optVerbose :: Bool
2020-02-28 23:33:32 +00:00
, optCache :: Bool
, optUrlSource :: Maybe URI
2020-03-01 11:54:46 +00:00
-- commands
2020-02-28 23:33:32 +00:00
, optCommand :: Command
}
data Command
2020-03-01 11:54:46 +00:00
= Install InstallCommand
2020-02-28 23:33:32 +00:00
| SetGHC SetGHCOptions
| List ListOptions
| Rm RmOptions
2020-02-29 23:07:39 +00:00
| DInfo
2020-03-03 00:59:19 +00:00
| Compile CompileOptions
2020-02-29 23:07:39 +00:00
data ToolVersion = ToolVersion Version
| ToolTag Tag
2020-02-28 23:33:32 +00:00
2020-03-01 11:54:46 +00:00
data InstallCommand = InstallGHC InstallOptions
| InstallCabal InstallOptions
2020-02-28 23:33:32 +00:00
2020-03-01 11:54:46 +00:00
data InstallOptions = InstallOptions
{ instVer :: Maybe ToolVersion
2020-02-28 23:33:32 +00:00
}
data SetGHCOptions = SetGHCOptions
2020-02-29 23:07:39 +00:00
{ ghcVer :: Maybe ToolVersion
2020-02-28 23:33:32 +00:00
}
data ListOptions = ListOptions
{ lTool :: Maybe Tool
, lCriteria :: Maybe ListCriteria
}
data RmOptions = RmOptions
{ ghcVer :: Version
}
2020-03-03 00:59:19 +00:00
data CompileOptions = CompileOptions
{ ghcVer :: Version
, bootstrapVer :: Version
, jobs :: Maybe Int
, buildConfig :: Maybe (Path Abs)
}
2020-02-28 23:33:32 +00:00
opts :: Parser Options
opts =
Options
<$> switch
(short 'v' <> long "verbose" <> help
"Whether to enable verbosity (default: False)"
)
<*> switch
(short 'c' <> long "cache" <> help
"Whether to cache downloads (default: False)"
)
<*> (optional
(option
(eitherReader parseUri)
(short 's' <> long "url-source" <> metavar "URL" <> help
"Alternative ghcup download info url (default: internal)"
)
)
)
<*> com
where
2020-02-29 23:07:39 +00:00
parseUri s' =
bimap show id $ parseURI strictURIParserOptions (UTF8.fromString s')
2020-02-28 23:33:32 +00:00
com :: Parser Command
2020-03-01 11:54:46 +00:00
com =
subparser
( command
"install"
( Install
<$> (info (installP <**> helper) (progDesc "Install GHC or cabal"))
)
<> command
"list"
( List
<$> (info (listOpts <**> helper)
(progDesc "Show available GHCs and other tools")
)
)
<> commandGroup "Main commands:"
)
<|> subparser
( command
"set"
( SetGHC
<$> (info (setGHCOpts <**> helper)
(progDesc "Set the currently active GHC version")
)
)
<> command
"rm"
( Rm
<$> (info
(rmOpts <**> helper)
(progDesc "Remove a GHC version installed by ghcup")
)
)
2020-03-03 00:59:19 +00:00
<> command
"compile"
( Compile
<$> (info (compileOpts <**> helper)
(progDesc "Compile GHC from source")
)
)
2020-03-01 11:54:46 +00:00
<> commandGroup "GHC commands:"
<> hidden
)
<|> subparser
( command
"debug-info"
((\_ -> DInfo) <$> (info (helper) (progDesc "Show debug info")))
<> commandGroup "Other commands:"
<> hidden
)
installP :: Parser InstallCommand
installP = subparser
2020-02-28 23:33:32 +00:00
( command
2020-03-01 11:54:46 +00:00
"ghc"
2020-02-28 23:33:32 +00:00
( InstallGHC
2020-03-01 11:54:46 +00:00
<$> (info (installOpts <**> helper) (progDesc "Install a GHC version"))
2020-02-28 23:33:32 +00:00
)
<> command
2020-03-01 11:54:46 +00:00
"cabal"
2020-02-28 23:33:32 +00:00
( InstallCabal
2020-03-01 11:54:46 +00:00
<$> (info (installOpts <**> helper)
(progDesc "Install or update a Cabal version")
2020-02-28 23:33:32 +00:00
)
)
)
2020-03-01 11:54:46 +00:00
installOpts :: Parser InstallOptions
installOpts = InstallOptions <$> optional toolVersionParser
2020-02-28 23:33:32 +00:00
setGHCOpts :: Parser SetGHCOptions
2020-02-29 23:07:39 +00:00
setGHCOpts = SetGHCOptions <$> optional toolVersionParser
2020-02-28 23:33:32 +00:00
listOpts :: Parser ListOptions
listOpts =
ListOptions
<$> optional
(option
(eitherReader toolParser)
(short 't' <> long "tool" <> metavar "<ghc|cabal>" <> help
2020-02-29 23:07:39 +00:00
"Tool to list versions for. Default is all"
2020-02-28 23:33:32 +00:00
)
)
<*> (optional
(option
(eitherReader criteriaParser)
( short 'c'
<> long "show-criteria"
<> metavar "<installed|set>"
<> help "Show only installed or set tool versions"
)
)
)
rmOpts :: Parser RmOptions
2020-03-03 00:59:19 +00:00
rmOpts = RmOptions <$> versionParser
compileOpts :: Parser CompileOptions
compileOpts =
CompileOptions
2020-02-29 23:07:39 +00:00
<$> (option
(eitherReader
(bimap (const "Not a valid version") id . version . T.pack)
)
(short 'v' <> long "version" <> metavar "VERSION" <> help
2020-03-03 00:59:19 +00:00
"The GHC version to compile"
2020-02-29 23:07:39 +00:00
)
)
2020-03-03 00:59:19 +00:00
<*> (option
(eitherReader
(bimap (const "Not a valid version") id . version . T.pack)
)
( short 'b'
<> long "bootstrap-version"
<> metavar "BOOTSTRAP_VERSION"
<> help "The GHC version to bootstrap with (must be installed)"
)
)
<*> optional
(option
(eitherReader (readEither @Int))
(short 'j' <> long "jobs" <> metavar "JOBS" <> help
"How many jobs to use for make"
)
)
<*> optional
(option
(eitherReader
(\x ->
bimap show id . parseAbs . E.encodeUtf8 . T.pack $ x :: Either
String
(Path Abs)
)
)
(short 'c' <> long "config" <> metavar "CONFIG" <> help
"Absolute path to build config file"
)
)
2020-02-29 23:07:39 +00:00
2020-02-28 23:33:32 +00:00
2020-02-29 23:07:39 +00:00
versionParser :: Parser Version
versionParser = option
(eitherReader (bimap (const "Not a valid version") id . version . T.pack))
2020-03-01 11:54:46 +00:00
(short 'v' <> long "version" <> metavar "VERSION" <> help "The target version"
)
2020-02-29 23:07:39 +00:00
toolVersionParser :: Parser ToolVersion
toolVersionParser = verP <|> toolP
where
verP = ToolVersion <$> versionParser
toolP =
ToolTag
<$> (option
(eitherReader
(\s' -> case fmap toLower s' of
"recommended" -> Right Recommended
"latest" -> Right Latest
other -> Left ([i|Unknown tag #{other}|])
)
)
2020-03-01 11:54:46 +00:00
(short 't' <> long "tag" <> metavar "TAG" <> help "The target tag")
2020-02-29 23:07:39 +00:00
)
2020-02-28 23:33:32 +00:00
toolParser :: String -> Either String Tool
2020-02-29 23:07:39 +00:00
toolParser s' | t == T.pack "ghc" = Right GHC
| t == T.pack "cabal" = Right Cabal
| otherwise = Left ("Unknown tool: " <> s')
where t = T.toLower (T.pack s')
2020-02-28 23:33:32 +00:00
criteriaParser :: String -> Either String ListCriteria
2020-02-29 23:07:39 +00:00
criteriaParser s' | t == T.pack "installed" = Right ListInstalled
| t == T.pack "set" = Right ListSet
| otherwise = Left ("Unknown criteria: " <> s')
where t = T.toLower (T.pack s')
2020-02-28 23:33:32 +00:00
toSettings :: Options -> Settings
toSettings Options {..} =
let cache = optCache
urlSource = maybe GHCupURL OwnSource optUrlSource
in Settings { .. }
-- TODO: something better than Show instance for errors
main :: IO ()
main = do
customExecParser (prefs showHelpOnError) (info (opts <**> helper) idm)
>>= \opt@Options {..} -> do
2020-03-01 00:05:02 +00:00
let settings = toSettings opt
-- logger interpreter
let runLogger = myLoggerT (LoggerConfig optVerbose $ B.hPut stderr)
2020-02-28 23:33:32 +00:00
-- wrapper to run effects with settings
let runInstTool =
runLogger
. flip runReaderT settings
2020-03-01 01:21:40 +00:00
. runResourceT
2020-02-28 23:33:32 +00:00
. runE
2020-02-29 23:07:39 +00:00
@'[ AlreadyInstalled
2020-02-28 23:33:32 +00:00
, ArchiveError
, DistroNotFound
2020-02-29 23:07:39 +00:00
, FileDoesNotExistError
, FileError
2020-02-28 23:33:32 +00:00
, JSONError
2020-02-29 23:07:39 +00:00
, NoCompatibleArch
, NoDownload
, NotInstalled
, PlatformResultError
, ProcessError
, TagNotFound
, URLException
2020-03-03 00:59:19 +00:00
, DigestError
2020-02-28 23:33:32 +00:00
]
let runSetGHC =
runLogger
. flip runReaderT settings
2020-02-29 23:07:39 +00:00
. runE
@'[ FileDoesNotExistError
, NotInstalled
, TagNotFound
, URLException
, JSONError
, TagNotFound
]
2020-02-28 23:33:32 +00:00
let runListGHC =
runLogger
. flip runReaderT settings
2020-02-29 23:07:39 +00:00
. runE @'[FileDoesNotExistError , URLException , JSONError]
2020-02-28 23:33:32 +00:00
let runRmGHC =
2020-02-29 23:07:39 +00:00
runLogger . flip runReaderT settings . runE @'[NotInstalled]
let runDebugInfo =
2020-02-28 23:33:32 +00:00
runLogger
. flip runReaderT settings
2020-02-29 23:07:39 +00:00
. runE
@'[PlatformResultError , NoCompatibleArch , DistroNotFound]
2020-02-28 23:33:32 +00:00
2020-03-03 00:59:19 +00:00
let runCompileGHC =
runLogger
. flip runReaderT settings
. runResourceT
. runE
@'[ AlreadyInstalled
, NotInstalled
, GHCNotFound
, ArchiveError
, ProcessError
, URLException
, DigestError
, BuildConfigNotFound
, FileDoesNotExistError
, URLException
, JSONError
]
2020-02-28 23:33:32 +00:00
case optCommand of
2020-03-01 11:54:46 +00:00
Install (InstallGHC InstallOptions {..}) ->
2020-02-28 23:33:32 +00:00
void
$ (runInstTool $ do
2020-03-03 00:59:19 +00:00
dls <- _binaryDownloads <$> liftE getDownloads
v <- liftE $ fromVersion dls instVer GHC
liftE $ installTool dls (ToolRequest GHC v) Nothing
2020-02-28 23:33:32 +00:00
)
>>= \case
VRight _ -> runLogger
$ $(logInfo) ([s|GHC installation successful|])
VLeft (V (AlreadyInstalled treq)) ->
runLogger $ $(logWarn)
(T.pack (show treq) <> [s| already installed|])
2020-02-29 23:07:39 +00:00
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-03-03 00:59:19 +00:00
Install (InstallCabal InstallOptions {..}) ->
2020-02-28 23:33:32 +00:00
void
$ (runInstTool $ do
2020-03-03 00:59:19 +00:00
dls <- _binaryDownloads <$> liftE getDownloads
v <- liftE $ fromVersion dls instVer Cabal
liftE $ installTool dls (ToolRequest Cabal v) Nothing
2020-02-28 23:33:32 +00:00
)
>>= \case
VRight _ -> runLogger
$ $(logInfo) ([s|Cabal installation successful|])
VLeft (V (AlreadyInstalled treq)) ->
runLogger $ $(logWarn)
(T.pack (show treq) <> [s| already installed|])
2020-02-29 23:07:39 +00:00
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-02-28 23:33:32 +00:00
SetGHC (SetGHCOptions {..}) ->
void
$ (runSetGHC $ do
2020-03-03 00:59:19 +00:00
dls <- _binaryDownloads <$> liftE getDownloads
v <- liftE $ fromVersion dls ghcVer GHC
2020-02-28 23:33:32 +00:00
liftE $ setGHC v SetGHCOnly
)
>>= \case
VRight _ ->
runLogger $ $(logInfo) ([s|GHC successfully set|])
2020-02-29 23:07:39 +00:00
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-02-28 23:33:32 +00:00
List (ListOptions {..}) ->
void
$ (runListGHC $ do
2020-03-03 00:59:19 +00:00
dls <- _binaryDownloads <$> liftE getDownloads
liftIO $ listVersions dls lTool lCriteria
2020-02-28 23:33:32 +00:00
)
>>= \case
VRight r -> liftIO $ printListResult r
2020-02-29 23:07:39 +00:00
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-02-28 23:33:32 +00:00
Rm (RmOptions {..}) ->
void
$ (runRmGHC $ do
liftE $ rmGHCVer ghcVer
)
>>= \case
VRight _ -> pure ()
2020-02-29 23:07:39 +00:00
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-02-28 23:33:32 +00:00
2020-02-29 23:07:39 +00:00
DInfo -> do
void
$ (runDebugInfo $ do
liftE $ getDebugInfo
)
>>= \case
VRight dinfo -> putStrLn $ show dinfo
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-03-03 00:59:19 +00:00
Compile (CompileOptions {..}) ->
void
$ (runCompileGHC $ do
dls <- _sourceDownloads <$> liftE getDownloads
liftE $ compileGHC dls ghcVer bootstrapVer jobs buildConfig
)
>>= \case
VRight _ ->
runLogger $ $(logInfo)
([s|GHC successfully compiled and installed|])
VLeft (V (AlreadyInstalled treq)) ->
runLogger $ $(logWarn)
(T.pack (show treq) <> [s| already installed|])
VLeft e ->
runLogger ($(logError) [i|#{e}|]) >> exitFailure
2020-02-28 23:33:32 +00:00
pure ()
2020-02-29 23:07:39 +00:00
fromVersion :: Monad m
2020-03-03 00:59:19 +00:00
=> BinaryDownloads
2020-02-29 23:07:39 +00:00
-> Maybe ToolVersion
-> Tool
-> Excepts '[TagNotFound] m Version
fromVersion av Nothing tool =
getRecommended av tool ?? TagNotFound Recommended tool
fromVersion _ (Just (ToolVersion v)) _ = pure v
fromVersion av (Just (ToolTag Latest)) tool =
getLatest av tool ?? TagNotFound Latest tool
fromVersion av (Just (ToolTag Recommended)) tool =
getRecommended av tool ?? TagNotFound Recommended tool
2020-02-28 23:33:32 +00:00
printListResult :: [ListResult] -> IO ()
printListResult lr = do
let
formatted =
gridString
[ column expand left def def
, column expand left def def
, column expand left def def
, column expand left def def
]
$ fmap
(\ListResult {..} ->
[ if
| lSet -> (color Green "✔✔")
| lInstalled -> (color Green "")
| otherwise -> (color Red "")
, fmap toLower . show $ lTool
, T.unpack . prettyVer $ lVer
, intercalate "," $ ((fmap . fmap) toLower . fmap show $ lTag)
]
)
lr
putStrLn $ formatted