2021-10-15 20:24:23 +00:00
{- # LANGUAGE CPP # -}
{- # LANGUAGE DataKinds # -}
{- # LANGUAGE TypeApplications # -}
{- # LANGUAGE FlexibleContexts # -}
{- # LANGUAGE OverloadedStrings # -}
{- # LANGUAGE TemplateHaskell # -}
{- # LANGUAGE QuasiQuotes # -}
{- # LANGUAGE DuplicateRecordFields # -}
{- # LANGUAGE RankNTypes # -}
module GHCup.OptParse.Compile where
import GHCup
import GHCup.Errors
import GHCup.Utils.File
import GHCup.Types
import GHCup.Types.Optics
import GHCup.Utils
import GHCup.Utils.Logger
import GHCup.OptParse.Common
import GHCup.Utils.String.QQ
# if ! MIN_VERSION_base ( 4 , 13 , 0 )
import Control.Monad.Fail ( MonadFail )
# endif
import Codec.Archive ( ArchiveResult )
import Control.Concurrent ( threadDelay )
import Control.Monad.Reader
import Control.Monad.Trans.Resource
import Data.Bifunctor
import Data.Functor
import Data.Maybe
import Data.Versions ( Version , prettyVer , version )
import Data.Text ( Text )
import Haskus.Utils.Variant.Excepts
import Options.Applicative hiding ( style )
import Options.Applicative.Help.Pretty ( text )
import Prelude hiding ( appendFile )
import System.Exit
import Text.PrettyPrint.HughesPJClass ( prettyShow )
2021-11-12 18:05:13 +00:00
import URI.ByteString hiding ( uriParser )
2021-10-15 20:24:23 +00:00
import qualified Data.Text as T
import Control.Exception.Safe ( MonadMask )
import System.FilePath ( isPathSeparator )
import Text.Read ( readEither )
----------------
--[ Commands ]--
----------------
data CompileCommand = CompileGHC GHCCompileOptions
| CompileHLS HLSCompileOptions
---------------
--[ Options ]--
---------------
data GHCCompileOptions = GHCCompileOptions
{ targetGhc :: Either Version GitBranch
, bootstrapGhc :: Either Version FilePath
, jobs :: Maybe Int
, buildConfig :: Maybe FilePath
2021-11-12 18:52:00 +00:00
, patches :: Maybe ( Either FilePath [ URI ] )
2021-10-15 20:24:23 +00:00
, crossTarget :: Maybe Text
, addConfArgs :: [ Text ]
, setCompile :: Bool
, ovewrwiteVer :: Maybe Version
, buildFlavour :: Maybe String
, hadrian :: Bool
, isolateDir :: Maybe FilePath
}
data HLSCompileOptions = HLSCompileOptions
{ targetHLS :: Either Version GitBranch
, jobs :: Maybe Int
, setCompile :: Bool
, ovewrwiteVer :: Maybe Version
, isolateDir :: Maybe FilePath
2021-11-12 18:05:13 +00:00
, cabalProject :: Maybe ( Either FilePath URI )
, cabalProjectLocal :: Maybe URI
2021-11-12 18:52:00 +00:00
, patches :: Maybe ( Either FilePath [ URI ] )
2021-10-15 20:24:23 +00:00
, targetGHCs :: [ ToolVersion ]
2021-11-12 00:13:57 +00:00
, cabalArgs :: [ Text ]
2021-10-15 20:24:23 +00:00
}
---------------
--[ Parsers ]--
---------------
2022-05-12 11:28:09 +00:00
2021-10-15 20:24:23 +00:00
compileP :: Parser CompileCommand
compileP = subparser
( command
" ghc "
( CompileGHC
<$> info
( ghcCompileOpts <**> helper )
( progDesc " Compile GHC from source "
<> footerDoc ( Just $ text compileFooter )
)
)
<> command
" hls "
( CompileHLS
<$> info
( hlsCompileOpts <**> helper )
( progDesc " Compile HLS from source "
<> footerDoc ( Just $ text compileHLSFooter )
)
)
)
where
compileFooter = [ s | Discussion :
Compiles and installs the specified GHC version into
a self - contained " ~/.ghcup/ghc/<ghcver> " directory
and symlinks the ghc binaries to " ~/.ghcup/bin/<binary>-<ghcver> " .
This also allows building a cross - compiler . Consult the documentation
first : < https :// gitlab . haskell . org / ghc / ghc /-/ wikis / building / cross - compiling # configuring - the - build >
ENV variables :
Various toolchain variables will be passed onto the ghc build system ,
such as : CC , LD , OBJDUMP , NM , AR , RANLIB .
Examples :
# compile from known version
ghcup compile ghc - j 4 - v 8.4 . 2 - b 8.2 . 2
# compile from git commit / reference
ghcup compile ghc - j 4 - g master - b 8.2 . 2
# specify path to bootstrap ghc
ghcup compile ghc - j 4 - v 8.4 . 2 - b / usr / bin / ghc - 8.2 . 2
# build cross compiler
ghcup compile ghc - j 4 - v 8.4 . 2 - b 8.2 . 2 - x armv7 - unknown - linux - gnueabihf --config $(pwd)/build.mk -- --enable-unregisterised|]
compileHLSFooter = [ s | Discussion :
Compiles and installs the specified HLS version .
The last argument is a list of GHC versions to compile for .
These need to be available in PATH prior to compilation .
Examples :
2021-11-12 00:13:57 +00:00
# compile 1.4 . 0 for ghc 8.10 . 5 and 8.10 . 7
ghcup compile hls - v 1.4 . 0 - j 12 --ghc 8.10.5 --ghc 8.10.7
# compile from master for ghc 8.10 . 7 , linking everything dynamically
ghcup compile hls - g master - j 12 --ghc 8.10.7 -- --ghc-options='-dynamic'|]
2021-10-15 20:24:23 +00:00
ghcCompileOpts :: Parser GHCCompileOptions
ghcCompileOpts =
GHCCompileOptions
<$> ( ( Left <$> option
( eitherReader
( first ( const " Not a valid version " ) . version . T . pack )
)
( short 'v' <> long " version " <> metavar " VERSION " <> help
" The tool version to compile "
2022-03-04 23:46:37 +00:00
<> ( completer $ versionCompleter Nothing GHC )
2021-10-15 20:24:23 +00:00
)
) <|>
( Right <$> ( GitBranch <$> option
str
( short 'g' <> long " git-ref " <> metavar " GIT_REFERENCE " <> help
" The git commit/branch/ref to build from "
) <*>
2022-03-07 21:23:39 +00:00
optional ( option str (
short 'r' <> long " repository " <> metavar " GIT_REPOSITORY " <> help " The git repository to build from (defaults to GHC upstream) "
<> completer ( gitFileUri [ " https://gitlab.haskell.org/ghc/ghc.git " ] )
) )
2021-10-15 20:24:23 +00:00
) ) )
<*> option
( eitherReader
( \ x ->
( bimap ( const " Not a valid version " ) Left . version . T . pack $ x ) <|> ( if isPathSeparator ( head x ) then pure $ Right x else Left " Not an absolute Path " )
)
)
( short 'b'
<> long " bootstrap-ghc "
<> metavar " BOOTSTRAP_GHC "
<> help
" The GHC version (or full path) to bootstrap with (must be installed) "
2022-03-04 23:46:37 +00:00
<> ( completer $ versionCompleter Nothing GHC )
2021-10-15 20:24:23 +00:00
)
<*> optional
( option
( eitherReader ( readEither @ Int ) )
( short 'j' <> long " jobs " <> metavar " JOBS " <> help
" How many jobs to use for make "
2022-03-04 23:46:37 +00:00
<> ( completer $ listCompleter $ fmap show ( [ 1 .. 12 ] :: [ Int ] ) )
2021-10-15 20:24:23 +00:00
)
)
<*> optional
( option
str
( short 'c' <> long " config " <> metavar " CONFIG " <> help
" Absolute path to build config file "
2022-03-04 23:46:37 +00:00
<> completer ( bashCompleter " file " )
2021-10-15 20:24:23 +00:00
)
)
2021-11-12 18:52:00 +00:00
<*> ( optional
(
( fmap Right $ many $ option
( eitherReader uriParser )
( long " patch " <> metavar " PATCH_URI " <> help
" URI to a patch (https/http/file) "
2022-03-04 23:46:37 +00:00
<> completer fileUri
2021-11-12 18:52:00 +00:00
)
)
<|>
( fmap Left $ option
str
( short 'p' <> long " patchdir " <> metavar " PATCH_DIR " <> help
2022-01-12 09:06:38 +00:00
" Absolute path to patch directory (applies all .patch and .diff files in order using -p1. This order is determined by a quilt series file if it exists, or the patches are lexicographically ordered) "
2022-03-04 23:46:37 +00:00
<> completer ( bashCompleter " directory " )
2021-11-12 18:52:00 +00:00
)
2021-10-15 20:24:23 +00:00
)
)
2021-11-12 18:52:00 +00:00
)
2021-10-15 20:24:23 +00:00
<*> optional
( option
str
( short 'x' <> long " cross-target " <> metavar " CROSS_TARGET " <> help
" Build cross-compiler for this platform "
)
)
<*> many ( argument str ( metavar " CONFIGURE_ARGS " <> help " Additional arguments to configure, prefix with '-- ' (longopts) " ) )
2022-03-13 21:48:45 +00:00
<*> fmap ( fromMaybe False ) ( invertableSwitch " set " Nothing False ( help " Set as active version after install " ) )
2021-10-15 20:24:23 +00:00
<*> optional
( option
( eitherReader
( first ( const " Not a valid version " ) . version . T . pack )
)
( short 'o' <> long " overwrite-version " <> metavar " OVERWRITE_VERSION " <> help
" Allows to overwrite the finally installed VERSION with a different one, e.g. when you build 8.10.4 with your own patches, you might want to set this to '8.10.4-p1' "
2022-03-04 23:46:37 +00:00
<> ( completer $ versionCompleter Nothing GHC )
2021-10-15 20:24:23 +00:00
)
)
<*> optional
( option
str
( short 'f' <> long " flavour " <> metavar " BUILD_FLAVOUR " <> help
" Set the compile build flavour (this value depends on the build system type: 'make' vs 'hadrian') "
)
)
<*> switch
( long " hadrian " <> help " Use the hadrian build system instead of make (only git versions seem to be properly supported atm) "
)
<*> optional
( option
( eitherReader isolateParser )
( short 'i'
<> long " isolate "
<> metavar " DIR "
<> help " install in an isolated directory instead of the default one, no symlinks to this installation will be made "
2022-03-04 23:46:37 +00:00
<> completer ( bashCompleter " directory " )
2021-10-15 20:24:23 +00:00
)
)
hlsCompileOpts :: Parser HLSCompileOptions
hlsCompileOpts =
HLSCompileOptions
<$> ( ( Left <$> option
( eitherReader
( first ( const " Not a valid version " ) . version . T . pack )
)
( short 'v' <> long " version " <> metavar " VERSION " <> help
" The tool version to compile "
2022-03-04 23:46:37 +00:00
<> ( completer $ versionCompleter Nothing HLS )
2021-10-15 20:24:23 +00:00
)
) <|>
( Right <$> ( GitBranch <$> option
str
( short 'g' <> long " git-ref " <> metavar " GIT_REFERENCE " <> help
" The git commit/branch/ref to build from "
) <*>
2022-05-01 15:12:39 +00:00
optional ( option str ( short 'r' <> long " repository " <> metavar " GIT_REPOSITORY " <> help " The git repository to build from (defaults to HLS upstream) "
2022-03-07 21:23:39 +00:00
<> completer ( gitFileUri [ " https://github.com/haskell/haskell-language-server.git " ] )
) )
2021-10-15 20:24:23 +00:00
) ) )
<*> optional
( option
( eitherReader ( readEither @ Int ) )
( short 'j' <> long " jobs " <> metavar " JOBS " <> help
" How many jobs to use for make "
2022-03-04 23:46:37 +00:00
<> ( completer $ listCompleter $ fmap show ( [ 1 .. 12 ] :: [ Int ] ) )
2021-10-15 20:24:23 +00:00
)
)
2022-03-13 21:48:45 +00:00
<*> fmap ( fromMaybe True ) ( invertableSwitch " set " Nothing True ( help " Don't set as active version after install " ) )
2021-10-15 20:24:23 +00:00
<*> optional
( option
( eitherReader
( first ( const " Not a valid version " ) . version . T . pack )
)
( short 'o' <> long " overwrite-version " <> metavar " OVERWRITE_VERSION " <> help
" Allows to overwrite the finally installed VERSION with a different one, e.g. when you build 8.10.4 with your own patches, you might want to set this to '8.10.4-p1' "
2022-03-04 23:46:37 +00:00
<> ( completer $ versionCompleter Nothing HLS )
2021-10-15 20:24:23 +00:00
)
)
<*> optional
( option
( eitherReader isolateParser )
( short 'i'
<> long " isolate "
<> metavar " DIR "
<> help " install in an isolated directory instead of the default one, no symlinks to this installation will be made "
2022-03-04 23:46:37 +00:00
<> completer ( bashCompleter " directory " )
2021-10-15 20:24:23 +00:00
)
)
<*> optional
( option
2021-11-12 18:05:13 +00:00
( ( fmap Right $ eitherReader uriParser ) <|> ( fmap Left str ) )
2021-10-15 20:24:23 +00:00
( long " cabal-project " <> metavar " CABAL_PROJECT " <> help
2021-11-12 18:05:13 +00:00
" If relative filepath, specifies the path to cabal.project inside the unpacked HLS tarball/checkout. Otherwise expects a full URI with https/http/file scheme. "
2022-03-04 23:46:37 +00:00
<> completer fileUri
2021-10-15 20:24:23 +00:00
)
)
<*> optional
( option
2021-11-12 18:05:13 +00:00
( eitherReader uriParser )
2021-10-15 20:24:23 +00:00
( long " cabal-project-local " <> metavar " CABAL_PROJECT_LOCAL " <> help
2021-11-12 18:05:13 +00:00
" URI (https/http/file) to a cabal.project.local to be used for the build. Will be copied over. "
2022-03-04 23:46:37 +00:00
<> completer fileUri
2021-10-15 20:24:23 +00:00
)
)
2021-11-12 18:52:00 +00:00
<*> ( optional
(
( fmap Right $ many $ option
( eitherReader uriParser )
( long " patch " <> metavar " PATCH_URI " <> help
" URI to a patch (https/http/file) "
2022-03-04 23:46:37 +00:00
<> completer fileUri
2021-11-12 18:52:00 +00:00
)
)
<|>
( fmap Left $ option
str
( short 'p' <> long " patchdir " <> metavar " PATCH_DIR " <> help
" Absolute path to patch directory (applies all .patch and .diff files in order using -p1) "
2022-03-04 23:46:37 +00:00
<> completer ( bashCompleter " directory " )
2021-11-12 18:52:00 +00:00
)
2021-10-15 20:24:23 +00:00
)
)
2021-11-12 18:52:00 +00:00
)
2022-01-29 19:02:33 +00:00
<*> some (
option ( eitherReader toolVersionEither )
( long " ghc " <> metavar " GHC_VERSION|TAG " <> help " For which GHC version to compile for (can be specified multiple times) "
<> completer ( tagCompleter GHC [] )
<> completer ( versionCompleter Nothing GHC ) )
)
2021-11-12 00:13:57 +00:00
<*> many ( argument str ( metavar " CABAL_ARGS " <> help " Additional arguments to cabal install, prefix with '-- ' (longopts) " ) )
2021-10-15 20:24:23 +00:00
---------------------------
--[ Effect interpreters ]--
---------------------------
type GHCEffects = ' [ A l r e a d y I n s t a l l e d
, BuildFailed
, DigestError
, GPGError
, DownloadFailed
, GHCupSetError
, NoDownload
, NotFoundInPATH
, PatchFailed
, UnknownArchive
, TarDirDoesNotExist
, NotInstalled
, DirNotEmpty
, ArchiveResult
, FileDoesNotExistError
, HadrianNotFound
, InvalidBuildConfig
, ProcessError
, CopyError
, BuildFailed
2022-05-12 15:58:40 +00:00
, UninstallFailed
2021-10-15 20:24:23 +00:00
]
type HLSEffects = ' [ A l r e a d y I n s t a l l e d
, BuildFailed
, DigestError
, GPGError
, DownloadFailed
, GHCupSetError
, NoDownload
, NotFoundInPATH
, PatchFailed
, UnknownArchive
, TarDirDoesNotExist
, TagNotFound
, NextVerNotFound
, NoToolVersionSet
, NotInstalled
, DirNotEmpty
, ArchiveResult
2022-05-12 15:58:40 +00:00
, UninstallFailed
2021-10-15 20:24:23 +00:00
]
runCompileGHC :: ( MonadUnliftIO m , MonadIO m )
=> ( ReaderT AppState m ( VEither GHCEffects a ) -> m ( VEither GHCEffects a ) )
-> Excepts GHCEffects ( ResourceT ( ReaderT AppState m ) ) a
-> m ( VEither GHCEffects a )
runCompileGHC runAppState =
runAppState
. runResourceT
. runE
@ GHCEffects
runCompileHLS :: ( MonadUnliftIO m , MonadIO m )
=> ( ReaderT AppState m ( VEither HLSEffects a ) -> m ( VEither HLSEffects a ) )
-> Excepts HLSEffects ( ResourceT ( ReaderT AppState m ) ) a
-> m ( VEither HLSEffects a )
runCompileHLS runAppState =
runAppState
. runResourceT
. runE
@ HLSEffects
------------------
--[ Entrypoint ]--
------------------
compile :: ( Monad m
, MonadMask m
, MonadUnliftIO m
, MonadFail m
)
=> CompileCommand
-> Settings
2021-11-13 21:50:15 +00:00
-> Dirs
2021-10-15 20:24:23 +00:00
-> ( forall eff a . ReaderT AppState m ( VEither eff a ) -> m ( VEither eff a ) )
-> ( ReaderT LeanAppState m () -> m () )
-> m ExitCode
2021-11-13 21:50:15 +00:00
compile compileCommand settings Dirs { .. } runAppState runLogger = do
2021-10-15 20:24:23 +00:00
case compileCommand of
( CompileHLS HLSCompileOptions { .. } ) -> do
runCompileHLS runAppState ( do
case targetHLS of
Left targetVer -> do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
let vi = getVersionInfo targetVer HLS dls
forM_ ( _viPreCompile =<< vi ) $ \ msg -> do
lift $ logInfo msg
lift $ logInfo
" ...waiting for 5 seconds, you can still abort... "
liftIO $ threadDelay 5000000 -- for compilation, give the user a sec to intervene
Right _ -> pure ()
ghcs <- liftE $ forM targetGHCs ( \ ghc -> fmap ( _tvVersion . fst ) . fromVersion ( Just ghc ) $ GHC )
targetVer <- liftE $ compileHLS
targetHLS
ghcs
jobs
ovewrwiteVer
2022-05-11 13:47:08 +00:00
( maybe GHCupInternal IsolateDir isolateDir )
2021-10-15 20:24:23 +00:00
cabalProject
cabalProjectLocal
2021-11-12 18:52:00 +00:00
patches
2021-11-12 00:13:57 +00:00
cabalArgs
2021-10-15 20:24:23 +00:00
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
let vi = getVersionInfo targetVer HLS dls
when setCompile $ void $ liftE $
2022-02-09 17:57:59 +00:00
setHLS targetVer SetHLSOnly Nothing
2021-10-15 20:24:23 +00:00
pure ( vi , targetVer )
)
>>= \ case
VRight ( vi , tv ) -> do
runLogger $ logInfo
" HLS successfully compiled and installed "
forM_ ( _viPostInstall =<< vi ) $ \ msg ->
runLogger $ logInfo msg
liftIO $ putStr ( T . unpack $ prettyVer tv )
pure ExitSuccess
VLeft err @ ( V ( BuildFailed tmpdir _ ) ) -> do
case keepDirs settings of
Never -> runLogger $ logError $ T . pack $ prettyShow err
_ -> runLogger ( logError $ T . pack ( prettyShow err ) <> " \ n " <>
" Check the logs at " <> T . pack logsDir <> " and the build directory "
<> T . pack tmpdir <> " for more clues. " <> " \ n " <>
" Make sure to clean up " <> T . pack tmpdir <> " afterwards. " )
pure $ ExitFailure 9
VLeft e -> do
runLogger $ logError $ T . pack $ prettyShow e
pure $ ExitFailure 9
( CompileGHC GHCCompileOptions { hadrian = True , crossTarget = Just _ } ) -> do
runLogger $ logError " Hadrian cross compile support is not yet implemented! "
pure $ ExitFailure 9
( CompileGHC GHCCompileOptions { .. } ) ->
runCompileGHC runAppState ( do
case targetGhc of
Left targetVer -> do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
let vi = getVersionInfo targetVer GHC dls
forM_ ( _viPreCompile =<< vi ) $ \ msg -> do
lift $ logInfo msg
lift $ logInfo
" ...waiting for 5 seconds, you can still abort... "
liftIO $ threadDelay 5000000 -- for compilation, give the user a sec to intervene
Right _ -> pure ()
targetVer <- liftE $ compileGHC
( first ( GHCTargetVersion crossTarget ) targetGhc )
ovewrwiteVer
bootstrapGhc
jobs
buildConfig
2021-11-12 18:52:00 +00:00
patches
2021-10-15 20:24:23 +00:00
addConfArgs
buildFlavour
hadrian
2022-05-11 13:47:08 +00:00
( maybe GHCupInternal IsolateDir isolateDir )
2021-10-15 20:24:23 +00:00
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
let vi = getVersionInfo ( _tvVersion targetVer ) GHC dls
when setCompile $ void $ liftE $
2022-02-09 17:57:59 +00:00
setGHC targetVer SetGHCOnly Nothing
2021-10-15 20:24:23 +00:00
pure ( vi , targetVer )
)
>>= \ case
VRight ( vi , tv ) -> do
runLogger $ logInfo
" GHC successfully compiled and installed "
forM_ ( _viPostInstall =<< vi ) $ \ msg ->
runLogger $ logInfo msg
liftIO $ putStr ( T . unpack $ tVerToText tv )
pure ExitSuccess
VLeft ( V ( AlreadyInstalled _ v ) ) -> do
runLogger $ logWarn $
2022-05-12 11:28:09 +00:00
" GHC ver " <> prettyVer v <> " already installed, remove it first to reinstall "
2021-10-15 20:24:23 +00:00
pure ExitSuccess
VLeft ( V ( DirNotEmpty fp ) ) -> do
2022-05-12 11:28:09 +00:00
runLogger $ logError $
" Install directory " <> T . pack fp <> " is not empty. "
2021-10-15 20:24:23 +00:00
pure $ ExitFailure 3
VLeft err @ ( V ( BuildFailed tmpdir _ ) ) -> do
case keepDirs settings of
Never -> runLogger $ logError $ T . pack $ prettyShow err
_ -> runLogger ( logError $ T . pack ( prettyShow err ) <> " \ n " <>
" Check the logs at " <> T . pack logsDir <> " and the build directory "
<> T . pack tmpdir <> " for more clues. " <> " \ n " <>
" Make sure to clean up " <> T . pack tmpdir <> " afterwards. " )
pure $ ExitFailure 9
VLeft e -> do
runLogger $ logError $ T . pack $ prettyShow e
pure $ ExitFailure 9