ghcup-hs/lib/GHCup.hs

2358 lines
81 KiB
Haskell
Raw Normal View History

2020-04-09 17:53:22 +00:00
{-# LANGUAGE CPP #-}
2021-07-15 11:32:48 +00:00
{-# LANGUAGE BangPatterns #-}
2020-01-11 20:15:05 +00:00
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
2020-03-21 21:19:37 +00:00
{-# LANGUAGE OverloadedStrings #-}
2020-01-11 20:15:05 +00:00
{-# LANGUAGE TemplateHaskell #-}
2021-05-14 21:09:45 +00:00
{-# LANGUAGE QuasiQuotes #-}
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
{-|
Module : GHCup
Description : GHCup installation functions
Copyright : (c) Julian Ospald, 2020
2020-07-30 18:04:02 +00:00
License : LGPL-3.0
2020-07-21 23:08:58 +00:00
Maintainer : hasufell@hasufell.de
Stability : experimental
2021-05-14 21:09:45 +00:00
Portability : portable
2020-07-21 23:08:58 +00:00
This module contains the main functions that correspond
to the command line interface, like installation, listing versions
and so on.
These are the entry points.
-}
2020-01-11 20:15:05 +00:00
module GHCup where
import GHCup.Download
import GHCup.Errors
import GHCup.Platform
import GHCup.Types
import GHCup.Types.JSON ( )
import GHCup.Types.Optics
import GHCup.Utils
import GHCup.Utils.File
import GHCup.Utils.Prelude
import GHCup.Utils.String.QQ
import GHCup.Utils.Version.QQ
import GHCup.Version
#if !defined(TAR)
import Codec.Archive ( ArchiveResult )
#endif
2020-01-11 20:15:05 +00:00
import Control.Applicative
2021-07-15 11:32:48 +00:00
import Control.DeepSeq ( force )
2021-06-15 12:00:30 +00:00
import Control.Exception ( evaluate )
2020-01-11 20:15:05 +00:00
import Control.Exception.Safe
import Control.Monad
2020-04-09 17:53:22 +00:00
#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail )
#endif
2020-01-11 20:15:05 +00:00
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.Trans.Resource
hiding ( throwM )
2021-07-22 13:45:08 +00:00
#if defined(IS_WINDOWS)
import Control.Monad.IO.Unlift ( MonadUnliftIO( withRunInIO ) )
#endif
2020-01-11 20:15:05 +00:00
import Data.ByteString ( ByteString )
import Data.Either
2020-01-11 20:15:05 +00:00
import Data.List
2021-05-14 21:09:45 +00:00
import Data.List.Extra
2020-01-11 20:15:05 +00:00
import Data.Maybe
2021-04-28 16:45:48 +00:00
import Data.String ( fromString )
2020-01-11 20:15:05 +00:00
import Data.String.Interpolate
2020-04-25 10:06:41 +00:00
import Data.Text ( Text )
import Data.Time.Clock
import Data.Time.Format.ISO8601
2020-01-11 20:15:05 +00:00
import Data.Versions
import GHC.IO.Exception
import Haskus.Utils.Variant.Excepts
import Optics
import Prelude hiding ( abs
, readFile
, writeFile
)
import Safe hiding ( at )
2021-05-14 21:09:45 +00:00
import System.Directory hiding ( findFiles )
import System.Environment
import System.FilePath
2020-01-11 20:15:05 +00:00
import System.IO.Error
#if defined(IS_WINDOWS)
import System.IO.Temp
#endif
import Text.PrettyPrint.HughesPJClass ( prettyShow )
import Text.Regex.Posix
2020-01-11 20:15:05 +00:00
import qualified Crypto.Hash.SHA256 as SHA256
import qualified Data.ByteString.Base16 as B16
2020-01-11 20:15:05 +00:00
import qualified Data.ByteString as B
2020-04-25 10:06:41 +00:00
import qualified Data.ByteString.Lazy as BL
2020-01-11 20:15:05 +00:00
import qualified Data.Map.Strict as Map
2020-04-25 10:06:41 +00:00
import qualified Data.Text as T
2020-01-11 20:15:05 +00:00
import qualified Data.Text.Encoding as E
2021-06-13 08:15:34 +00:00
#if defined(IS_WINDOWS)
import qualified System.Win32.File as Win32
#endif
2021-04-28 16:45:48 +00:00
import qualified Text.Megaparsec as MP
import GHCup.Utils.MegaParsec
import Control.Concurrent (threadDelay)
2020-01-11 20:15:05 +00:00
2021-07-19 14:49:18 +00:00
---------------------
--[ Tool fetching ]--
---------------------
fetchToolBindist :: ( MonadFail m
, MonadMask m
, MonadCatch m
, MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, HasGHCupInfo env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
)
=> Version
-> Tool
-> Maybe FilePath
-> Excepts
'[ DigestError
, DownloadFailed
, NoDownload
]
m
FilePath
fetchToolBindist v t mfp = do
dlinfo <- liftE $ getDownloadInfo t v
liftE $ downloadCached' dlinfo Nothing mfp
fetchGHCSrc :: ( MonadFail m
, MonadMask m
, MonadCatch m
, MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, HasGHCupInfo env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
)
=> Version
-> Maybe FilePath
-> Excepts
'[ DigestError
, DownloadFailed
, NoDownload
]
m
FilePath
fetchGHCSrc v mfp = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
dlInfo <-
preview (ix GHC % ix v % viSourceDL % _Just) dls
?? NoDownload
liftE $ downloadCached' dlInfo Nothing mfp
2020-01-11 20:15:05 +00:00
-------------------------
--[ Tool installation ]--
-------------------------
2020-07-21 23:08:58 +00:00
-- | Like 'installGHCBin', except takes the 'DownloadInfo' as
-- argument instead of looking it up from 'GHCupDownloads'.
installGHCBindist :: ( MonadFail m
2020-07-21 23:08:58 +00:00
, MonadMask m
, MonadCatch m
, MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
2020-07-21 23:08:58 +00:00
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
2020-07-21 23:08:58 +00:00
)
=> DownloadInfo -- ^ where/how to download
-> Version -- ^ the version to install
-> Maybe FilePath -- ^ isolated filepath if user passed any
2020-07-21 23:08:58 +00:00
-> Excepts
'[ AlreadyInstalled
, BuildFailed
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
2021-08-11 10:24:51 +00:00
, DirNotEmpty
#if !defined(TAR)
2020-07-21 23:08:58 +00:00
, ArchiveResult
#endif
2020-07-21 23:08:58 +00:00
]
m
()
installGHCBindist dlinfo ver isoFilepath = do
2021-03-11 16:03:51 +00:00
let tver = mkTVer ver
2020-01-11 20:15:05 +00:00
lift $ $(logDebug) [i|Requested to install GHC with #{ver}|]
case isoFilepath of
-- we only care for already installed errors in regular (non-isolated) installs
Nothing -> whenM (lift $ ghcInstalled tver) (throwE $ AlreadyInstalled GHC ver)
_ -> pure ()
2020-01-11 20:15:05 +00:00
-- download (or use cached version)
2021-07-18 21:29:09 +00:00
dl <- liftE $ downloadCached dlinfo Nothing
2020-01-11 20:15:05 +00:00
-- prepare paths
ghcdir <- lift $ ghcupGHCDir tver
2020-01-11 20:15:05 +00:00
2020-10-30 20:07:49 +00:00
toolchainSanityChecks
case isoFilepath of
Just isoDir -> do -- isolated install
lift $ $(logInfo) [i|isolated installing GHC to #{isoDir}|]
liftE $ installPackedGHC dl (view dlSubdir dlinfo) isoDir ver
Nothing -> do -- regular install
liftE $ installPackedGHC dl (view dlSubdir dlinfo) ghcdir ver
2020-01-11 20:15:05 +00:00
-- make symlinks & stuff when regular install,
liftE $ postGHCInstall tver
2020-01-11 20:15:05 +00:00
2020-10-30 20:07:49 +00:00
where
toolchainSanityChecks = do
2021-05-14 21:09:45 +00:00
r <- forM ["CC", "LD"] (liftIO . lookupEnv)
2020-10-30 20:07:49 +00:00
case catMaybes r of
[] -> pure ()
_ -> do
lift $ $(logWarn) "CC/LD environment variable is set. This will change the compiler/linker"
lift $ $(logWarn) "GHC uses internally and can cause defunct GHC in some cases (e.g. in Anaconda"
lift $ $(logWarn) "environments). If you encounter problems, unset CC and LD and reinstall."
-- | Install a packed GHC distribution. This only deals with unpacking and the GHC
-- build system and nothing else.
installPackedGHC :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasDirs env
, HasPlatformReq env
, HasSettings env
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadUnliftIO m
)
2021-05-14 21:09:45 +00:00
=> FilePath -- ^ Path to the packed GHC bindist
-> Maybe TarDir -- ^ Subdir of the archive
2021-05-14 21:09:45 +00:00
-> FilePath -- ^ Path to install to
-> Version -- ^ The GHC version
-> Excepts
'[ BuildFailed
, UnknownArchive
, TarDirDoesNotExist
2021-08-11 10:24:51 +00:00
, DirNotEmpty
#if !defined(TAR)
, ArchiveResult
#endif
] m ()
2021-05-14 21:09:45 +00:00
installPackedGHC dl msubdir inst ver = do
PlatformRequest {..} <- lift getPlatformReq
2021-05-14 21:09:45 +00:00
2021-08-11 10:24:51 +00:00
liftE $ installDestSanityCheck inst
-- unpack
tmpUnpack <- lift mkGhcupTmpDir
liftE $ unpackToDir tmpUnpack dl
2021-05-14 21:09:45 +00:00
void $ lift $ darwinNotarization _rPlatform tmpUnpack
2020-01-11 20:15:05 +00:00
-- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack)
(liftE . intoSubdir tmpUnpack)
2021-03-11 16:03:51 +00:00
msubdir
liftE $ runBuildAction tmpUnpack
(Just inst)
2021-05-14 21:09:45 +00:00
(installUnpackedGHC workdir inst ver)
2021-08-11 10:24:51 +00:00
where
-- | Does basic checks for isolated installs
-- Isolated Directory:
-- 1. if it doesn't exist -> proceed
-- 2. if it exists and is empty -> proceed
-- 3. if it exists and is non-empty -> panic and leave the house
installDestSanityCheck :: ( MonadIO m
, MonadCatch m
) =>
FilePath ->
Excepts '[DirNotEmpty] m ()
installDestSanityCheck isoDir = do
hideErrorDef [doesNotExistErrorType] () $ do
contents <- liftIO $ getDirectoryContentsRecursive isoDir
unless (null contents) (throwE $ DirNotEmpty isoDir)
-- | Install an unpacked GHC distribution. This only deals with the GHC
-- build system and nothing else.
installUnpackedGHC :: ( MonadReader env m
, HasPlatformReq env
, HasDirs env
, HasSettings env
, MonadThrow m
, MonadLogger m
, MonadIO m
2021-07-22 13:45:08 +00:00
, MonadUnliftIO m
, MonadMask m
)
2021-05-14 21:09:45 +00:00
=> FilePath -- ^ Path to the unpacked GHC bindist (where the configure script resides)
-> FilePath -- ^ Path to install to
-> Version -- ^ The GHC version
-> Excepts '[ProcessError] m ()
2021-05-14 21:09:45 +00:00
installUnpackedGHC path inst ver = do
2021-07-22 13:45:08 +00:00
#if defined(IS_WINDOWS)
lift $ $(logInfo) "Installing GHC (this may take a while)"
-- Windows bindists are relocatable and don't need
-- to run configure.
-- We also must make sure to preserve mtime to not confuse ghc-pkg.
lift $ withRunInIO $ \run -> flip onException (run $ recyclePathForcibly inst) $ copyDirectoryRecursive path inst $ \source dest -> do
mtime <- getModificationTime source
Win32.moveFile source dest
setModificationTime dest mtime
#else
PlatformRequest {..} <- lift getPlatformReq
2021-05-14 21:09:45 +00:00
let alpineArgs
| ver >= [vver|8.2.2|], Linux Alpine <- _rPlatform
= ["--disable-ld-override"]
| otherwise
= []
lift $ $(logInfo) "Installing GHC (this may take a while)"
lEM $ execLogged "sh"
("./configure" : ("--prefix=" <> inst)
: alpineArgs
)
(Just path)
2021-05-14 21:09:45 +00:00
"ghc-configure"
Nothing
lEM $ make ["install"] (Just path)
pure ()
2021-07-22 13:45:08 +00:00
#endif
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
-- | Installs GHC into @~\/.ghcup\/ghc/\<ver\>@ and places the
-- following symlinks in @~\/.ghcup\/bin@:
--
-- * @ghc-x.y.z -> ..\/ghc\/x.y.z\/bin/ghc@
-- * @ghc-x.y -> ..\/ghc\/x.y.z\/bin/ghc@ (if x.y.z is the latest x.y version)
installGHCBin :: ( MonadFail m
, MonadMask m
, MonadCatch m
, MonadReader env m
, HasPlatformReq env
, HasGHCupInfo env
, HasDirs env
, HasSettings env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
)
2021-05-14 21:09:45 +00:00
=> Version -- ^ the version to install
-> Maybe FilePath -- ^ isolated install filepath, if user passed any
-> Excepts
'[ AlreadyInstalled
, BuildFailed
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
2021-08-11 10:24:51 +00:00
, DirNotEmpty
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installGHCBin ver isoFilepath = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo GHC ver
installGHCBindist dlinfo ver isoFilepath
2020-07-21 23:08:58 +00:00
-- | Like 'installCabalBin', except takes the 'DownloadInfo' as
-- argument instead of looking it up from 'GHCupDownloads'.
installCabalBindist :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasPlatformReq env
, HasDirs env
, HasSettings env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
, MonadFail m
)
=> DownloadInfo
-> Version
-> Maybe FilePath -- ^ isolated install filepath, if user provides any.
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
, FileAlreadyExistsError
]
m
()
installCabalBindist dlinfo ver isoFilepath = do
2020-01-11 20:15:05 +00:00
lift $ $(logDebug) [i|Requested to install cabal version #{ver}|]
PlatformRequest {..} <- lift getPlatformReq
2021-07-18 21:29:09 +00:00
Dirs {..} <- lift getDirs
case isoFilepath of
Nothing -> -- for regular install check if any previous versions installed
whenM
(lift (cabalInstalled ver) >>= \a -> liftIO $
handleIO (\_ -> pure False)
$ fmap (\x -> a && x)
-- ignore when the installation is a legacy cabal (binary, not symlink)
$ pathIsLink (binDir </> "cabal" <> exeExt)
)
(throwE $ AlreadyInstalled Cabal ver)
_ -> pure () -- check isn't required in isolated installs
2020-01-11 20:15:05 +00:00
-- download (or use cached version)
2021-07-18 21:29:09 +00:00
dl <- liftE $ downloadCached dlinfo Nothing
2020-01-11 20:15:05 +00:00
-- unpack
2021-07-18 21:29:09 +00:00
tmpUnpack <- lift withGHCupTmpDir
2020-01-11 20:15:05 +00:00
liftE $ unpackToDir tmpUnpack dl
2021-05-14 21:09:45 +00:00
void $ lift $ darwinNotarization _rPlatform tmpUnpack
2020-01-11 20:15:05 +00:00
-- the subdir of the archive where we do the work
2020-08-06 11:28:20 +00:00
workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo)
2020-01-11 20:15:05 +00:00
case isoFilepath of
Just isoDir -> do -- isolated install
lift $ $(logInfo) [i|isolated installing Cabal to #{isoDir}|]
2021-08-11 10:24:51 +00:00
liftE $ installCabalUnpacked workdir isoDir Nothing
Nothing -> do -- regular install
2021-08-11 10:24:51 +00:00
liftE $ installCabalUnpacked workdir binDir (Just ver)
-- create symlink if this is the latest version for regular installs
cVers <- lift $ fmap rights getInstalledCabals
let lInstCabal = headMay . reverse . sort $ cVers
when (maybe True (ver >=) lInstCabal) $ liftE $ setCabal ver
-- | Install an unpacked cabal distribution.
installCabalUnpacked :: (MonadLogger m, MonadCatch m, MonadIO m)
=> FilePath -- ^ Path to the unpacked cabal bindist (where the executable resides)
-> FilePath -- ^ Path to install to
2021-08-11 10:24:51 +00:00
-> Maybe Version -- ^ Nothing for isolated install
-> Excepts '[CopyError, FileAlreadyExistsError] m ()
2021-08-11 10:24:51 +00:00
installCabalUnpacked path inst mver' = do
lift $ $(logInfo) "Installing cabal"
let cabalFile = "cabal"
liftIO $ createDirRecursive' inst
2021-08-11 10:24:51 +00:00
let destFileName = cabalFile
<> maybe "" (("-" <>) . T.unpack . prettyVer) mver'
<> exeExt
let destPath = inst </> destFileName
handleIO (throwE . CopyError . show) $ liftIO $ copyFile
(path </> cabalFile <> exeExt)
destPath
lift $ chmod_755 destPath
2020-01-11 20:15:05 +00:00
2020-07-22 00:34:17 +00:00
-- | Installs cabal into @~\/.ghcup\/bin/cabal-\<ver\>@ and
2020-07-21 23:08:58 +00:00
-- creates a default @cabal -> cabal-x.y.z.q@ symlink for
-- the latest installed version.
installCabalBin :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasPlatformReq env
, HasGHCupInfo env
, HasDirs env
, HasSettings env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
, MonadFail m
)
2021-05-14 21:09:45 +00:00
=> Version
-> Maybe FilePath -- isolated install Path, if user provided any
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
, FileAlreadyExistsError
]
m
()
installCabalBin ver isoFilepath = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo Cabal ver
installCabalBindist dlinfo ver isoFilepath
-- | Like 'installHLSBin, except takes the 'DownloadInfo' as
-- argument instead of looking it up from 'GHCupDownloads'.
installHLSBindist :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasPlatformReq env
, HasDirs env
, HasSettings env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
, MonadFail m
)
=> DownloadInfo
-> Version
-> Maybe FilePath -- ^ isolated install path, if user passed any
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installHLSBindist dlinfo ver isoFilepath = do
lift $ $(logDebug) [i|Requested to install hls version #{ver}|]
PlatformRequest {..} <- lift getPlatformReq
2021-07-18 21:29:09 +00:00
Dirs {..} <- lift getDirs
case isoFilepath of
Nothing ->
-- we only check for already installed in regular (non-isolated) installs
whenM (lift (hlsInstalled ver))
(throwE $ AlreadyInstalled HLS ver)
_ -> pure ()
-- download (or use cached version)
2021-07-18 21:29:09 +00:00
dl <- liftE $ downloadCached dlinfo Nothing
-- unpack
2021-07-18 21:29:09 +00:00
tmpUnpack <- lift withGHCupTmpDir
liftE $ unpackToDir tmpUnpack dl
2021-05-14 21:09:45 +00:00
void $ lift $ darwinNotarization _rPlatform tmpUnpack
-- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo)
case isoFilepath of
Just isoDir -> do
lift $ $(logInfo) [i|isolated installing HLS to #{isoDir}|]
2021-08-11 10:24:51 +00:00
liftE $ installHLSUnpacked workdir isoDir Nothing
Nothing -> do
2021-08-11 10:24:51 +00:00
liftE $ installHLSUnpacked workdir binDir (Just ver)
-- create symlink if this is the latest version in a regular install
hlsVers <- lift $ fmap rights getInstalledHLSs
let lInstHLS = headMay . reverse . sort $ hlsVers
when (maybe True (ver >=) lInstHLS) $ liftE $ setHLS ver
2021-07-23 10:53:03 +00:00
-- | Install an unpacked hls distribution.
installHLSUnpacked :: (MonadFail m, MonadLogger m, MonadCatch m, MonadIO m)
2021-07-23 10:53:03 +00:00
=> FilePath -- ^ Path to the unpacked hls bindist (where the executable resides)
-> FilePath -- ^ Path to install to
2021-08-11 10:24:51 +00:00
-> Maybe Version -- ^ Nothing for isolated install
2021-07-23 10:53:03 +00:00
-> Excepts '[CopyError] m ()
2021-08-11 10:24:51 +00:00
installHLSUnpacked path inst mver' = do
2021-07-23 10:53:03 +00:00
lift $ $(logInfo) "Installing HLS"
liftIO $ createDirRecursive' inst
-- install haskell-language-server-<ghcver>
bins@(_:_) <- liftIO $ findFiles
path
(makeRegexOpts compExtended
execBlank
([s|^haskell-language-server-[0-9].*$|] :: ByteString)
)
forM_ bins $ \f -> do
let toF = dropSuffix exeExt f
2021-08-11 10:24:51 +00:00
<> maybe "" (("~" <>) . T.unpack . prettyVer) mver'
<> exeExt
handleIO (throwE . CopyError . show) $ liftIO $ copyFile
2021-07-23 10:53:03 +00:00
(path </> f)
(inst </> toF)
2020-12-19 17:27:27 +00:00
lift $ chmod_755 (inst </> toF)
2021-07-23 10:53:03 +00:00
-- install haskell-language-server-wrapper
let wrapper = "haskell-language-server-wrapper"
2021-08-11 10:24:51 +00:00
toF = wrapper
<> maybe "" (("-" <>) . T.unpack . prettyVer) mver'
<> exeExt
2021-07-23 10:53:03 +00:00
handleIO (throwE . CopyError . show) $ liftIO $ copyFile
(path </> wrapper <> exeExt)
(inst </> toF)
lift $ chmod_755 (inst </> toF)
-- | Installs hls binaries @haskell-language-server-\<ghcver\>@
-- into @~\/.ghcup\/bin/@, as well as @haskell-languager-server-wrapper@.
installHLSBin :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasPlatformReq env
, HasGHCupInfo env
, HasDirs env
, HasSettings env
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
, MonadFail m
)
2021-05-14 21:09:45 +00:00
=> Version
-> Maybe FilePath
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installHLSBin ver isoFilepath = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo HLS ver
installHLSBindist dlinfo ver isoFilepath
2021-05-14 22:31:36 +00:00
-- | Installs stack into @~\/.ghcup\/bin/stack-\<ver\>@ and
-- creates a default @stack -> stack-x.y.z.q@ symlink for
-- the latest installed version.
installStackBin :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, HasGHCupInfo env
2021-05-14 22:31:36 +00:00
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
, MonadFail m
)
2021-05-14 21:09:45 +00:00
=> Version
-> Maybe FilePath
2021-05-14 22:31:36 +00:00
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installStackBin ver isoFilepath = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo Stack ver
installStackBindist dlinfo ver isoFilepath
2021-05-14 22:31:36 +00:00
-- | Like 'installStackBin', except takes the 'DownloadInfo' as
-- argument instead of looking it up from 'GHCupDownloads'.
installStackBindist :: ( MonadMask m
, MonadCatch m
, MonadReader env m
, HasPlatformReq env
, HasDirs env
, HasSettings env
2021-05-14 22:31:36 +00:00
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
, MonadFail m
)
=> DownloadInfo
-> Version
-> Maybe FilePath
2021-05-14 22:31:36 +00:00
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installStackBindist dlinfo ver isoFilepath = do
2021-05-14 22:31:36 +00:00
lift $ $(logDebug) [i|Requested to install stack version #{ver}|]
PlatformRequest {..} <- lift getPlatformReq
2021-07-18 21:29:09 +00:00
Dirs {..} <- lift getDirs
2021-05-14 22:31:36 +00:00
case isoFilepath of
Nothing -> -- check previous versions in case of regular installs
whenM (lift (stackInstalled ver))
(throwE $ AlreadyInstalled Stack ver)
_ -> pure () -- don't do shit for isolates
2021-05-14 22:31:36 +00:00
-- download (or use cached version)
2021-07-18 21:29:09 +00:00
dl <- liftE $ downloadCached dlinfo Nothing
2021-05-14 22:31:36 +00:00
-- unpack
2021-07-18 21:29:09 +00:00
tmpUnpack <- lift withGHCupTmpDir
2021-05-14 22:31:36 +00:00
liftE $ unpackToDir tmpUnpack dl
2021-05-14 21:09:45 +00:00
void $ lift $ darwinNotarization _rPlatform tmpUnpack
2021-05-14 22:31:36 +00:00
-- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo)
case isoFilepath of
Just isoDir -> do -- isolated install
lift $ $(logInfo) [i|isolated installing Stack to #{isoDir}|]
2021-08-11 10:24:51 +00:00
liftE $ installStackUnpacked workdir isoDir Nothing
Nothing -> do -- regular install
2021-08-11 10:24:51 +00:00
liftE $ installStackUnpacked workdir binDir (Just ver)
-- create symlink if this is the latest version and a regular install
sVers <- lift $ fmap rights getInstalledStacks
let lInstStack = headMay . reverse . sort $ sVers
when (maybe True (ver >=) lInstStack) $ liftE $ setStack ver
2021-05-14 22:31:36 +00:00
2021-07-23 11:13:43 +00:00
-- | Install an unpacked stack distribution.
installStackUnpacked :: (MonadLogger m, MonadCatch m, MonadIO m)
2021-07-23 11:13:43 +00:00
=> FilePath -- ^ Path to the unpacked stack bindist (where the executable resides)
-> FilePath -- ^ Path to install to
2021-08-11 10:24:51 +00:00
-> Maybe Version -- ^ Nothing for isolated installs
2021-07-23 11:13:43 +00:00
-> Excepts '[CopyError] m ()
2021-08-11 10:24:51 +00:00
installStackUnpacked path inst mver' = do
2021-07-23 11:13:43 +00:00
lift $ $(logInfo) "Installing stack"
let stackFile = "stack"
liftIO $ createDirRecursive' inst
2021-08-11 10:24:51 +00:00
let destFileName = stackFile
<> maybe "" (("-" <>) . T.unpack . prettyVer) mver'
<> exeExt
2021-07-23 11:13:43 +00:00
let destPath = inst </> destFileName
handleIO (throwE . CopyError . show) $ liftIO $ copyFile
(path </> stackFile <> exeExt)
destPath
lift $ chmod_755 destPath
2020-01-11 20:15:05 +00:00
---------------------
--[ Set GHC/cabal ]--
---------------------
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
-- | Set GHC symlinks in @~\/.ghcup\/bin@ for the requested GHC version. The behavior depends
2020-01-11 20:15:05 +00:00
-- on `SetGHC`:
--
2020-07-21 23:08:58 +00:00
-- * SetGHCOnly: @~\/.ghcup\/bin\/ghc -> ~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
-- * SetGHC_XY: @~\/.ghcup\/bin\/ghc-X.Y -> ~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
-- * SetGHC_XYZ: @~\/.ghcup\/bin\/ghc-\<ver\> -> ~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
2020-01-11 20:15:05 +00:00
--
2020-07-21 23:08:58 +00:00
-- Additionally creates a @~\/.ghcup\/share -> ~\/.ghcup\/ghc\/\<ver\>\/share symlink@
-- for 'SetGHCOnly' constructor.
setGHC :: ( MonadReader env m
, HasDirs env
2020-08-06 11:28:20 +00:00
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadCatch m
2021-05-14 21:09:45 +00:00
, MonadMask m
, MonadUnliftIO m
2020-08-06 11:28:20 +00:00
)
2020-04-25 10:06:41 +00:00
=> GHCTargetVersion
2020-01-11 20:15:05 +00:00
-> SetGHC
2020-04-25 10:06:41 +00:00
-> Excepts '[NotInstalled] m GHCTargetVersion
2020-01-11 20:15:05 +00:00
setGHC ver sghc = do
2021-05-14 21:09:45 +00:00
let verS = T.unpack $ prettyVer (_tvVersion ver)
2020-08-06 11:28:20 +00:00
ghcdir <- lift $ ghcupGHCDir ver
2020-01-11 20:15:05 +00:00
2021-03-11 16:03:51 +00:00
whenM (lift $ not <$> ghcInstalled ver) (throwE (NotInstalled GHC ver))
2020-01-11 20:15:05 +00:00
-- symlink destination
Dirs {..} <- lift getDirs
2020-01-11 20:15:05 +00:00
-- first delete the old symlinks (this fixes compatibility issues
-- with old ghcup)
case sghc of
2020-04-25 10:06:41 +00:00
SetGHCOnly -> liftE $ rmPlain (_tvTarget ver)
SetGHC_XY -> liftE $ rmMajorSymlinks ver
SetGHC_XYZ -> liftE $ rmMinorSymlinks ver
2020-01-11 20:15:05 +00:00
-- for ghc tools (ghc, ghci, haddock, ...)
verfiles <- ghcToolFiles ver
forM_ verfiles $ \file -> do
2020-08-06 11:28:20 +00:00
mTargetFile <- case sghc of
SetGHCOnly -> pure $ Just file
2020-01-11 20:15:05 +00:00
SetGHC_XY -> do
2021-05-14 21:09:45 +00:00
handle
2020-08-06 11:28:20 +00:00
(\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing)
2021-05-14 21:09:45 +00:00
$ do
(mj, mi) <- getMajorMinorV (_tvVersion ver)
let major' = intToText mj <> "." <> intToText mi
pure $ Just (file <> "-" <> T.unpack major')
2020-08-06 11:28:20 +00:00
SetGHC_XYZ ->
2021-05-14 21:09:45 +00:00
pure $ Just (file <> "-" <> verS)
2020-01-11 20:15:05 +00:00
-- create symlink
2020-08-06 11:28:20 +00:00
forM mTargetFile $ \targetFile -> do
2021-05-14 21:09:45 +00:00
let fullF = binDir </> targetFile <> exeExt
fileWithExt = file <> exeExt
destL <- lift $ ghcLinkDestination fileWithExt ver
lift $ createLink destL fullF
2020-01-11 20:15:05 +00:00
-- create symlink for share dir
2021-05-14 21:09:45 +00:00
when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verS
2020-01-11 20:15:05 +00:00
pure ver
2020-01-11 20:15:05 +00:00
where
symlinkShareDir :: ( MonadReader env m
, HasDirs env
, MonadIO m
, MonadLogger m
, MonadCatch m
, MonadMask m
)
2021-05-14 21:09:45 +00:00
=> FilePath
-> String
2020-01-11 20:15:05 +00:00
-> m ()
2021-05-14 21:09:45 +00:00
symlinkShareDir ghcdir ver' = do
Dirs {..} <- getDirs
let destdir = baseDir
2020-01-11 20:15:05 +00:00
case sghc of
SetGHCOnly -> do
2021-05-14 21:09:45 +00:00
let sharedir = "share"
2020-01-11 20:15:05 +00:00
let fullsharedir = ghcdir </> sharedir
whenM (liftIO $ doesDirectoryExist fullsharedir) $ do
let fullF = destdir </> sharedir
2021-05-14 21:09:45 +00:00
let targetF = "." </> "ghc" </> ver' </> sharedir
2020-01-11 20:15:05 +00:00
$(logDebug) [i|rm -f #{fullF}|]
hideError doesNotExistErrorType $ rmDirectoryLink fullF
2020-01-11 20:15:05 +00:00
$(logDebug) [i|ln -s #{targetF} #{fullF}|]
2021-05-14 21:09:45 +00:00
liftIO
#if defined(IS_WINDOWS)
-- On windows we need to be more permissive
-- in case symlinks can't be created, be just
-- give up here. This symlink isn't strictly necessary.
$ hideError permissionErrorType
$ hideError illegalOperationErrorType
#endif
$ createDirectoryLink targetF fullF
2020-01-11 20:15:05 +00:00
_ -> pure ()
2020-07-21 23:08:58 +00:00
-- | Set the @~\/.ghcup\/bin\/cabal@ symlink.
2021-05-14 21:09:45 +00:00
setCabal :: ( MonadMask m
, MonadReader env m
, HasDirs env
2021-05-14 21:09:45 +00:00
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadUnliftIO m)
=> Version
-> Excepts '[NotInstalled] m ()
setCabal ver = do
2021-05-14 21:09:45 +00:00
let targetFile = "cabal-" <> T.unpack (prettyVer ver) <> exeExt
-- symlink destination
Dirs {..} <- lift getDirs
2021-03-11 16:03:51 +00:00
whenM (liftIO $ not <$> doesFileExist (binDir </> targetFile))
$ throwE
$ NotInstalled Cabal (GHCTargetVersion Nothing ver)
2021-05-14 21:09:45 +00:00
let cabalbin = binDir </> "cabal" <> exeExt
2021-05-14 21:09:45 +00:00
-- create link
let destL = targetFile
lift $ createLink destL cabalbin
pure ()
-- | Set the haskell-language-server symlinks.
setHLS :: ( MonadCatch m
, MonadReader env m
, HasDirs env
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
2021-05-14 21:09:45 +00:00
, MonadMask m
, MonadUnliftIO m
)
=> Version
-> Excepts '[NotInstalled] m ()
setHLS ver = do
Dirs {..} <- lift getDirs
-- Delete old symlinks, since these might have different ghc versions than the
-- selected version, so we could end up with stray or incorrect symlinks.
oldSyms <- lift hlsSymlinks
forM_ oldSyms $ \f -> do
2021-05-14 21:09:45 +00:00
lift $ $(logDebug) [i|rm #{binDir </> f}|]
lift $ rmLink (binDir </> f)
-- set haskell-language-server-<ghcver> symlinks
bins <- lift $ hlsServerBinaries ver
2021-03-11 16:03:51 +00:00
when (null bins) $ throwE $ NotInstalled HLS (GHCTargetVersion Nothing ver)
forM_ bins $ \f -> do
2021-05-14 21:09:45 +00:00
let destL = f
let target = (<> exeExt) . head . splitOn "~" $ f
lift $ createLink destL (binDir </> target)
-- set haskell-language-server-wrapper symlink
2021-05-14 21:09:45 +00:00
let destL = "haskell-language-server-wrapper-" <> T.unpack (prettyVer ver) <> exeExt
let wrapper = binDir </> "haskell-language-server-wrapper" <> exeExt
2021-05-14 21:09:45 +00:00
lift $ createLink destL wrapper
pure ()
2021-05-14 22:31:36 +00:00
-- | Set the @~\/.ghcup\/bin\/stack@ symlink.
2021-05-14 21:09:45 +00:00
setStack :: ( MonadMask m
, MonadReader env m
, HasDirs env
2021-05-14 21:09:45 +00:00
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadUnliftIO m
)
2021-05-14 22:31:36 +00:00
=> Version
-> Excepts '[NotInstalled] m ()
setStack ver = do
2021-05-14 21:09:45 +00:00
let targetFile = "stack-" <> T.unpack (prettyVer ver) <> exeExt
2021-05-14 22:31:36 +00:00
-- symlink destination
Dirs {..} <- lift getDirs
2021-05-14 22:31:36 +00:00
whenM (liftIO $ not <$> doesFileExist (binDir </> targetFile))
$ throwE
$ NotInstalled Stack (GHCTargetVersion Nothing ver)
2021-05-14 21:09:45 +00:00
let stackbin = binDir </> "stack" <> exeExt
2021-05-14 22:31:36 +00:00
2021-05-14 21:09:45 +00:00
lift $ createLink targetFile stackbin
2021-05-14 22:31:36 +00:00
pure ()
2020-01-11 20:15:05 +00:00
------------------
--[ List tools ]--
------------------
2020-07-21 23:08:58 +00:00
-- | Filter data type for 'listVersions'.
2020-01-11 20:15:05 +00:00
data ListCriteria = ListInstalled
| ListSet
deriving Show
2020-07-21 23:08:58 +00:00
-- | A list result describes a single tool version
-- and various of its properties.
2020-01-11 20:15:05 +00:00
data ListResult = ListResult
{ lTool :: Tool
, lVer :: Version
2020-04-25 10:06:41 +00:00
, lCross :: Maybe Text -- ^ currently only for GHC
2020-01-11 20:15:05 +00:00
, lTag :: [Tag]
, lInstalled :: Bool
2020-04-21 21:37:48 +00:00
, lSet :: Bool -- ^ currently active version
, fromSrc :: Bool -- ^ compiled from source
, lStray :: Bool -- ^ not in download info
, lNoBindist :: Bool -- ^ whether the version is available for this platform/arch
, hlsPowered :: Bool
2020-01-11 20:15:05 +00:00
}
2020-04-21 21:37:48 +00:00
deriving (Eq, Ord, Show)
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
-- | Extract all available tool versions and their tags.
availableToolVersions :: GHCupDownloads -> Tool -> Map.Map Version VersionInfo
2020-04-21 21:37:48 +00:00
availableToolVersions av tool = view
(at tool % non Map.empty)
2020-01-11 20:15:05 +00:00
av
2020-04-21 21:37:48 +00:00
-- | List all versions from the download info, as well as stray
-- versions.
listVersions :: ( MonadCatch m
, MonadLogger m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadReader env m
, HasDirs env
, HasPlatformReq env
, HasGHCupInfo env
)
2021-05-14 21:09:45 +00:00
=> Maybe Tool
2020-01-11 20:15:05 +00:00
-> Maybe ListCriteria
2020-07-13 16:27:21 +00:00
-> m [ListResult]
2021-05-14 21:09:45 +00:00
listVersions lt' criteria = do
-- some annoying work to avoid too much repeated IO
cSet <- cabalSet
2021-06-12 20:26:50 +00:00
cabals <- getInstalledCabals
hlsSet' <- hlsSet
hlses <- getInstalledHLSs
2021-05-14 22:31:36 +00:00
sSet <- stackSet
stacks <- getInstalledStacks
2021-05-14 22:31:36 +00:00
go lt' cSet cabals hlsSet' hlses sSet stacks
2020-01-11 20:15:05 +00:00
where
2021-05-14 22:31:36 +00:00
go lt cSet cabals hlsSet' hlses sSet stacks = do
case lt of
Just t -> do
GHCupInfo { _ghcupDownloads = dls } <- getGHCupInfo
-- get versions from GHCupDownloads
2021-05-14 21:09:45 +00:00
let avTools = availableToolVersions dls t
2021-05-14 22:31:36 +00:00
lr <- filter' <$> forM (Map.toList avTools) (toListResult t cSet cabals hlsSet' hlses sSet stacks)
case t of
GHC -> do
slr <- strayGHCs avTools
pure (sort (slr ++ lr))
Cabal -> do
slr <- strayCabals avTools cSet cabals
pure (sort (slr ++ lr))
HLS -> do
2021-07-20 09:54:14 +00:00
slr <- strayHLS avTools hlsSet' hlses
pure (sort (slr ++ lr))
2021-05-14 22:31:36 +00:00
Stack -> do
2021-07-20 09:54:14 +00:00
slr <- strayStacks avTools sSet stacks
2021-05-14 22:31:36 +00:00
pure (sort (slr ++ lr))
GHCup -> do
2021-07-28 20:36:59 +00:00
let cg = maybeToList $ currentGHCup avTools
pure (sort (cg ++ lr))
Nothing -> do
2021-05-14 22:31:36 +00:00
ghcvers <- go (Just GHC) cSet cabals hlsSet' hlses sSet stacks
cabalvers <- go (Just Cabal) cSet cabals hlsSet' hlses sSet stacks
hlsvers <- go (Just HLS) cSet cabals hlsSet' hlses sSet stacks
ghcupvers <- go (Just GHCup) cSet cabals hlsSet' hlses sSet stacks
stackvers <- go (Just Stack) cSet cabals hlsSet' hlses sSet stacks
pure (ghcvers <> cabalvers <> hlsvers <> stackvers <> ghcupvers)
strayGHCs :: ( MonadCatch m
, MonadReader env m
, HasDirs env
, MonadThrow m
, MonadLogger m
, MonadIO m
)
=> Map.Map Version VersionInfo
2020-04-21 21:37:48 +00:00
-> m [ListResult]
strayGHCs avTools = do
2020-04-25 10:06:41 +00:00
ghcs <- getInstalledGHCs
fmap catMaybes $ forM ghcs $ \case
Right tver@GHCTargetVersion{ _tvTarget = Nothing, .. } -> do
case Map.lookup _tvVersion avTools of
Just _ -> pure Nothing
Nothing -> do
lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet Nothing
fromSrc <- ghcSrcInstalled tver
2021-03-11 16:03:51 +00:00
hlsPowered <- fmap (elem _tvVersion) hlsGHCVersions
2020-04-25 10:06:41 +00:00
pure $ Just $ ListResult
{ lTool = GHC
, lVer = _tvVersion
, lCross = Nothing
, lTag = []
, lInstalled = True
2021-03-11 16:03:51 +00:00
, lStray = isNothing (Map.lookup _tvVersion avTools)
, lNoBindist = False
2020-04-25 10:06:41 +00:00
, ..
}
Right tver@GHCTargetVersion{ .. } -> do
lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet _tvTarget
fromSrc <- ghcSrcInstalled tver
2021-03-11 16:03:51 +00:00
hlsPowered <- fmap (elem _tvVersion) hlsGHCVersions
2020-04-25 10:06:41 +00:00
pure $ Just $ ListResult
{ lTool = GHC
, lVer = _tvVersion
, lCross = _tvTarget
, lTag = []
, lInstalled = True
, lStray = True -- NOTE: cross currently cannot be installed via bindist
, lNoBindist = False
2020-04-25 10:06:41 +00:00
, ..
}
Left e -> do
$(logWarn)
2021-05-14 21:09:45 +00:00
[i|Could not parse version of stray directory #{e}|]
2020-04-25 10:06:41 +00:00
pure Nothing
strayCabals :: ( MonadReader env m
, HasDirs env
, MonadCatch m
, MonadThrow m
, MonadLogger m
, MonadIO m
)
=> Map.Map Version VersionInfo
-> Maybe Version
2021-05-14 21:09:45 +00:00
-> [Either FilePath Version]
2020-08-14 14:53:32 +00:00
-> m [ListResult]
strayCabals avTools cSet cabals = do
2020-08-14 14:53:32 +00:00
fmap catMaybes $ forM cabals $ \case
Right ver ->
case Map.lookup ver avTools of
Just _ -> pure Nothing
Nothing -> do
let lSet = cSet == Just ver
2020-08-14 14:53:32 +00:00
pure $ Just $ ListResult
{ lTool = Cabal
, lVer = ver
, lCross = Nothing
, lTag = []
, lInstalled = True
2021-03-11 16:03:51 +00:00
, lStray = isNothing (Map.lookup ver avTools)
2020-08-14 14:53:32 +00:00
, lNoBindist = False
, fromSrc = False -- actually, we don't know :>
, hlsPowered = False
, ..
}
Left e -> do
$(logWarn)
2021-05-14 21:09:45 +00:00
[i|Could not parse version of stray directory #{e}|]
pure Nothing
strayHLS :: ( MonadReader env m
, HasDirs env
, MonadCatch m
, MonadThrow m
, MonadLogger m
, MonadIO m)
=> Map.Map Version VersionInfo
2021-07-20 09:54:14 +00:00
-> Maybe Version
-> [Either FilePath Version]
-> m [ListResult]
2021-07-20 09:54:14 +00:00
strayHLS avTools hlsSet' hlss = do
fmap catMaybes $ forM hlss $ \case
Right ver ->
case Map.lookup ver avTools of
Just _ -> pure Nothing
Nothing -> do
2021-07-20 09:54:14 +00:00
let lSet = hlsSet' == Just ver
pure $ Just $ ListResult
{ lTool = HLS
, lVer = ver
, lCross = Nothing
, lTag = []
, lInstalled = True
2021-03-11 16:03:51 +00:00
, lStray = isNothing (Map.lookup ver avTools)
, lNoBindist = False
, fromSrc = False -- actually, we don't know :>
, hlsPowered = False
2020-08-14 14:53:32 +00:00
, ..
}
Left e -> do
$(logWarn)
2021-05-14 21:09:45 +00:00
[i|Could not parse version of stray directory #{e}|]
2020-08-14 14:53:32 +00:00
pure Nothing
strayStacks :: ( MonadReader env m
, HasDirs env
, MonadCatch m
, MonadThrow m
, MonadLogger m
, MonadIO m
)
=> Map.Map Version VersionInfo
2021-07-20 09:54:14 +00:00
-> Maybe Version
-> [Either FilePath Version]
2021-05-14 22:31:36 +00:00
-> m [ListResult]
2021-07-20 09:54:14 +00:00
strayStacks avTools stackSet' stacks = do
2021-05-14 22:31:36 +00:00
fmap catMaybes $ forM stacks $ \case
Right ver ->
case Map.lookup ver avTools of
Just _ -> pure Nothing
Nothing -> do
2021-07-20 09:54:14 +00:00
let lSet = stackSet' == Just ver
2021-05-14 22:31:36 +00:00
pure $ Just $ ListResult
{ lTool = Stack
, lVer = ver
, lCross = Nothing
, lTag = []
, lInstalled = True
, lStray = isNothing (Map.lookup ver avTools)
, lNoBindist = False
, fromSrc = False -- actually, we don't know :>
, hlsPowered = False
, ..
}
Left e -> do
$(logWarn)
2021-05-14 21:09:45 +00:00
[i|Could not parse version of stray directory #{e}|]
2021-05-14 22:31:36 +00:00
pure Nothing
2021-07-28 20:36:59 +00:00
currentGHCup :: Map.Map Version VersionInfo -> Maybe ListResult
currentGHCup av =
let currentVer = pvpToVersion ghcUpVer
listVer = Map.lookup currentVer av
latestVer = fst <$> headOf (getTagged Latest) av
recommendedVer = fst <$> headOf (getTagged Latest) av
isOld = maybe True (> currentVer) latestVer && maybe True (> currentVer) recommendedVer
2021-07-28 20:48:28 +00:00
in if | Map.member currentVer av -> Nothing
| otherwise -> Just $ ListResult { lVer = currentVer
, lTag = maybe (if isOld then [Old] else []) _viTags listVer
, lCross = Nothing
, lTool = GHCup
, fromSrc = False
, lStray = isNothing listVer
, lSet = True
, lInstalled = True
, lNoBindist = False
, hlsPowered = False
}
2020-04-25 10:06:41 +00:00
-- NOTE: this are not cross ones, because no bindists
toListResult :: ( MonadLogger m
, MonadReader env m
, HasDirs env
, HasGHCupInfo env
, HasPlatformReq env
, MonadIO m
, MonadCatch m
)
=> Tool
-> Maybe Version
2021-05-14 21:09:45 +00:00
-> [Either FilePath Version]
-> Maybe Version
2021-05-14 21:09:45 +00:00
-> [Either FilePath Version]
2021-05-14 22:31:36 +00:00
-> Maybe Version
2021-05-14 21:09:45 +00:00
-> [Either FilePath Version]
-> (Version, VersionInfo)
-> m ListResult
toListResult t cSet cabals hlsSet' hlses stackSet' stacks (v, _viTags -> tags) = do
2021-05-14 21:09:45 +00:00
case t of
GHC -> do
2021-07-19 14:49:18 +00:00
lNoBindist <- fmap (isLeft . veitherToEither) $ runE @'[NoDownload] $ getDownloadInfo GHC v
2021-05-14 21:09:45 +00:00
let tver = mkTVer v
lSet <- fmap (maybe False (\(GHCTargetVersion _ v') -> v' == v)) $ ghcSet Nothing
lInstalled <- ghcInstalled tver
fromSrc <- ghcSrcInstalled tver
hlsPowered <- fmap (elem v) hlsGHCVersions
pure ListResult { lVer = v, lCross = Nothing , lTag = tags, lTool = t, lStray = False, .. }
Cabal -> do
2021-07-19 14:49:18 +00:00
lNoBindist <- fmap (isLeft . veitherToEither) $ runE @'[NoDownload] $ getDownloadInfo Cabal v
2021-05-14 21:09:45 +00:00
let lSet = cSet == Just v
let lInstalled = elem v $ rights cabals
pure ListResult { lVer = v
, lCross = Nothing
, lTag = tags
, lTool = t
, fromSrc = False
, lStray = False
, hlsPowered = False
, ..
}
GHCup -> do
let lSet = prettyPVP ghcUpVer == prettyVer v
let lInstalled = lSet
pure ListResult { lVer = v
, lTag = tags
, lCross = Nothing
, lTool = t
, fromSrc = False
, lStray = False
, lNoBindist = False
, hlsPowered = False
, ..
}
HLS -> do
2021-07-19 14:49:18 +00:00
lNoBindist <- fmap (isLeft . veitherToEither) $ runE @'[NoDownload] $ getDownloadInfo HLS v
2021-05-14 21:09:45 +00:00
let lSet = hlsSet' == Just v
let lInstalled = elem v $ rights hlses
pure ListResult { lVer = v
, lCross = Nothing
, lTag = tags
, lTool = t
, fromSrc = False
, lStray = False
, hlsPowered = False
, ..
}
Stack -> do
2021-07-19 14:49:18 +00:00
lNoBindist <- fmap (isLeft . veitherToEither) $ runE @'[NoDownload] $ getDownloadInfo Stack v
2021-05-14 21:09:45 +00:00
let lSet = stackSet' == Just v
let lInstalled = elem v $ rights stacks
pure ListResult { lVer = v
, lCross = Nothing
, lTag = tags
, lTool = t
, fromSrc = False
, lStray = False
, hlsPowered = False
, ..
}
2020-01-11 20:15:05 +00:00
filter' :: [ListResult] -> [ListResult]
filter' lr = case criteria of
Nothing -> lr
Just ListInstalled -> filter (\ListResult {..} -> lInstalled) lr
Just ListSet -> filter (\ListResult {..} -> lSet) lr
--------------------
--[ GHC/cabal rm ]--
--------------------
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
-- | Delete a ghc version and all its symlinks.
--
-- This may leave GHCup without a "set" version.
-- Will try to fix the ghc-x.y symlink after removal (e.g. to an
-- older version).
rmGHCVer :: ( MonadReader env m
, HasDirs env
2020-08-06 11:28:20 +00:00
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
2021-05-14 21:09:45 +00:00
, MonadMask m
, MonadUnliftIO m
2020-08-06 11:28:20 +00:00
)
2020-04-25 10:06:41 +00:00
=> GHCTargetVersion
2020-01-11 20:15:05 +00:00
-> Excepts '[NotInstalled] m ()
rmGHCVer ver = do
2021-03-11 16:03:51 +00:00
isSetGHC <- lift $ fmap (== Just ver) $ ghcSet (_tvTarget ver)
whenM (lift $ fmap not $ ghcInstalled ver) (throwE (NotInstalled GHC ver))
dir <- lift $ ghcupGHCDir ver
-- this isn't atomic, order matters
when isSetGHC $ do
lift $ $(logInfo) [i|Removing ghc symlinks|]
liftE $ rmPlain (_tvTarget ver)
lift $ $(logInfo) [i|Removing ghc-x.y.z symlinks|]
liftE $ rmMinorSymlinks ver
lift $ $(logInfo) [i|Removing/rewiring ghc-x.y symlinks|]
-- first remove
handle (\(_ :: ParseError) -> pure ()) $ liftE $ rmMajorSymlinks ver
-- then fix them (e.g. with an earlier version)
2021-05-14 21:09:45 +00:00
lift $ $(logInfo) [i|Removing directory recursively: #{dir}|]
2021-07-22 13:45:08 +00:00
lift $ recyclePathForcibly dir
v' <-
handle
(\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing)
$ fmap Just
$ getMajorMinorV (_tvVersion ver)
forM_ v' $ \(mj, mi) -> lift (getGHCForMajor mj mi (_tvTarget ver))
>>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY)
Dirs {..} <- lift getDirs
2021-07-22 13:45:08 +00:00
lift $ hideError doesNotExistErrorType $ rmDirectoryLink (baseDir </> "share")
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
-- | Delete a cabal version. Will try to fix the @cabal@ symlink
-- after removal (e.g. setting it to an older version).
2021-05-14 21:09:45 +00:00
rmCabalVer :: ( MonadMask m
, MonadReader env m
, HasDirs env
2021-05-14 21:09:45 +00:00
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
, MonadUnliftIO m
)
=> Version
-> Excepts '[NotInstalled] m ()
rmCabalVer ver = do
whenM (lift $ fmap not $ cabalInstalled ver) $ throwE (NotInstalled Cabal (GHCTargetVersion Nothing ver))
2021-03-11 16:03:51 +00:00
cSet <- lift cabalSet
Dirs {..} <- lift getDirs
2021-05-14 21:09:45 +00:00
let cabalFile = "cabal-" <> T.unpack (prettyVer ver) <> exeExt
2021-07-22 13:45:08 +00:00
lift $ hideError doesNotExistErrorType $ recycleFile (binDir </> cabalFile)
2021-03-11 16:03:51 +00:00
when (Just ver == cSet) $ do
cVers <- lift $ fmap rights getInstalledCabals
case headMay . reverse . sort $ cVers of
Just latestver -> setCabal latestver
Nothing -> lift $ rmLink (binDir </> "cabal" <> exeExt)
2020-01-11 20:15:05 +00:00
-- | Delete a hls version. Will try to fix the hls symlinks
-- after removal (e.g. setting it to an older version).
2021-05-14 21:09:45 +00:00
rmHLSVer :: ( MonadMask m
, MonadReader env m
, HasDirs env
2021-05-14 21:09:45 +00:00
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
, MonadUnliftIO m
)
=> Version
-> Excepts '[NotInstalled] m ()
rmHLSVer ver = do
whenM (lift $ fmap not $ hlsInstalled ver) $ throwE (NotInstalled HLS (GHCTargetVersion Nothing ver))
2021-03-11 16:03:51 +00:00
isHlsSet <- lift hlsSet
Dirs {..} <- lift getDirs
bins <- lift $ hlsAllBinaries ver
2021-07-22 13:45:08 +00:00
forM_ bins $ \f -> lift $ recycleFile (binDir </> f)
2021-03-11 16:03:51 +00:00
when (Just ver == isHlsSet) $ do
-- delete all set symlinks
oldSyms <- lift hlsSymlinks
forM_ oldSyms $ \f -> do
2021-05-14 21:09:45 +00:00
let fullF = binDir </> f
lift $ $(logDebug) [i|rm #{fullF}|]
lift $ rmLink fullF
-- set latest hls
2021-03-11 16:03:51 +00:00
hlsVers <- lift $ fmap rights getInstalledHLSs
case headMay . reverse . sort $ hlsVers of
Just latestver -> setHLS latestver
Nothing -> pure ()
2021-05-14 22:31:36 +00:00
-- | Delete a stack version. Will try to fix the @stack@ symlink
-- after removal (e.g. setting it to an older version).
2021-05-14 21:09:45 +00:00
rmStackVer :: ( MonadMask m
, MonadReader env m
, HasDirs env
2021-05-14 21:09:45 +00:00
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
, MonadUnliftIO m
)
2021-05-14 22:31:36 +00:00
=> Version
-> Excepts '[NotInstalled] m ()
rmStackVer ver = do
whenM (lift $ fmap not $ stackInstalled ver) $ throwE (NotInstalled Stack (GHCTargetVersion Nothing ver))
sSet <- lift stackSet
Dirs {..} <- lift getDirs
2021-05-14 22:31:36 +00:00
2021-05-14 21:09:45 +00:00
let stackFile = "stack-" <> T.unpack (prettyVer ver) <> exeExt
2021-07-22 13:45:08 +00:00
lift $ hideError doesNotExistErrorType $ recycleFile (binDir </> stackFile)
2021-05-14 22:31:36 +00:00
when (Just ver == sSet) $ do
sVers <- lift $ fmap rights getInstalledStacks
case headMay . reverse . sort $ sVers of
Just latestver -> setStack latestver
Nothing -> lift $ rmLink (binDir </> "stack" <> exeExt)
2021-05-14 22:31:36 +00:00
-- assuming the current scheme of having just 1 ghcup bin, no version info is required.
rmGhcup :: ( MonadReader env m
, HasDirs env
, MonadIO m
, MonadCatch m
, MonadLogger m
, MonadMask m
2021-07-22 13:45:08 +00:00
, MonadUnliftIO m
)
=> m ()
rmGhcup = do
Dirs { .. } <- getDirs
let ghcupFilename = "ghcup" <> exeExt
let ghcupFilepath = binDir </> ghcupFilename
2021-06-15 12:00:30 +00:00
currentRunningExecPath <- liftIO getExecutablePath
-- if paths do no exist, warn user, and continue to compare them, as is,
-- which should eventually fail and result in a non-standard install warning
p1 <- handleIO' doesNotExistErrorType
(handlePathNotPresent currentRunningExecPath)
(liftIO $ canonicalizePath currentRunningExecPath)
p2 <- handleIO' doesNotExistErrorType
2021-07-02 21:26:07 +00:00
(handlePathNotPresent ghcupFilepath)
(liftIO $ canonicalizePath ghcupFilepath)
let areEqualPaths = equalFilePath p1 p2
2021-06-15 12:00:30 +00:00
unless areEqualPaths $ $logWarn $ nonStandardInstallLocationMsg currentRunningExecPath
2021-07-02 21:26:07 +00:00
#if defined(IS_WINDOWS)
2021-07-22 13:45:08 +00:00
-- since it doesn't seem possible to delete a running exe on windows
2021-07-02 21:26:07 +00:00
-- we move it to temp dir, to be deleted at next reboot
2021-07-22 13:45:08 +00:00
tempFilepath <- mkGhcupTmpDir
2021-07-02 21:26:07 +00:00
hideError UnsupportedOperation $
liftIO $ hideError NoSuchThing $
2021-07-22 13:45:08 +00:00
Win32.moveFileEx ghcupFilepath (Just (tempFilepath </> "ghcup")) 0
#else
2021-07-02 21:26:07 +00:00
-- delete it.
hideError doesNotExistErrorType $ rmFile ghcupFilepath
#endif
where
handlePathNotPresent fp _err = do
$logDebug $ "Error: The path does not exist, " <> T.pack fp
pure fp
nonStandardInstallLocationMsg path = T.pack $
"current ghcup is invoked from a non-standard location: \n"
<> path <>
"\n you may have to uninstall it manually."
rmTool :: ( MonadReader env m
, HasDirs env
, MonadLogger m
, MonadFail m
, MonadMask m
, MonadUnliftIO m)
=> ListResult
-> Excepts '[NotInstalled ] m ()
rmTool ListResult {lVer, lTool, lCross} = do
case lTool of
2021-07-02 21:26:07 +00:00
GHC ->
let ghcTargetVersion = GHCTargetVersion lCross lVer
2021-07-02 21:26:07 +00:00
in rmGHCVer ghcTargetVersion
HLS -> rmHLSVer lVer
Cabal -> rmCabalVer lVer
Stack -> rmStackVer lVer
GHCup -> lift rmGhcup
rmGhcupDirs :: ( MonadReader env m
, HasDirs env
, MonadIO m
, MonadLogger m
, MonadCatch m
, MonadMask m )
2021-07-02 21:26:07 +00:00
=> m [FilePath]
rmGhcupDirs = do
2021-07-02 21:26:07 +00:00
Dirs
{ baseDir
, binDir
, logsDir
, cacheDir
2021-07-22 13:45:08 +00:00
, recycleDir
} <- getDirs
let envFilePath = baseDir </> "env"
confFilePath <- getConfigFilePath
2021-07-22 13:45:08 +00:00
handleRm $ rmEnvFile envFilePath
handleRm $ rmConfFile confFilePath
2021-07-29 09:51:47 +00:00
-- for xdg dirs, the order matters here
2021-07-22 13:45:08 +00:00
handleRm $ rmDir logsDir
2021-07-29 09:51:47 +00:00
handleRm $ rmDir cacheDir
2021-07-22 13:45:08 +00:00
handleRm $ rmBinDir binDir
handleRm $ rmDir recycleDir
2021-07-02 21:26:07 +00:00
#if defined(IS_WINDOWS)
2021-07-22 13:45:08 +00:00
$logInfo [i|removing #{(baseDir </> "msys64")}|]
handleRm $ rmPathForcibly (baseDir </> "msys64")
2021-07-02 21:26:07 +00:00
#endif
2021-07-22 13:45:08 +00:00
handleRm $ removeEmptyDirsRecursive baseDir
2021-07-02 21:26:07 +00:00
-- report files in baseDir that are left-over after
-- the standard location deletions above
hideErrorDef [doesNotExistErrorType] [] $ reportRemainingFiles baseDir
where
2021-07-22 13:45:08 +00:00
handleRm :: (MonadCatch m, MonadLogger m) => m () -> m ()
2021-07-26 16:13:57 +00:00
handleRm = handleIO (\e -> $logDebug [i|Part of the cleanup action failed with error: #{displayException e}
2021-07-22 13:45:08 +00:00
continuing regardless...|])
rmEnvFile :: (MonadLogger m, MonadReader env m, HasDirs env, MonadMask m, MonadIO m, MonadCatch m) => FilePath -> m ()
rmEnvFile enFilePath = do
$logInfo "Removing Ghcup Environment File"
2021-07-26 15:44:37 +00:00
hideErrorDef [permissionErrorType] () $ deleteFile enFilePath
rmConfFile :: (MonadLogger m, MonadReader env m, HasDirs env, MonadMask m, MonadIO m, MonadCatch m) => FilePath -> m ()
rmConfFile confFilePath = do
$logInfo "removing Ghcup Config File"
2021-07-26 15:44:37 +00:00
hideErrorDef [permissionErrorType] () $ deleteFile confFilePath
rmDir :: (MonadLogger m, MonadReader env m, HasDirs env, MonadMask m, MonadIO m, MonadCatch m) => FilePath -> m ()
2021-07-15 11:32:48 +00:00
rmDir dir =
-- 'getDirectoryContentsRecursive' is lazy IO. In case
-- an error leaks through, we catch it here as well,
-- althought 'deleteFile' should already handle it.
hideErrorDef [doesNotExistErrorType] () $ do
$logInfo [i|removing #{dir}|]
contents <- liftIO $ getDirectoryContentsRecursive dir
forM_ contents (deleteFile . (dir </>))
rmBinDir :: (MonadReader env m, HasDirs env, MonadMask m, MonadIO m, MonadCatch m) => FilePath -> m ()
rmBinDir binDir = do
#if !defined(IS_WINDOWS)
2021-06-15 12:00:30 +00:00
isXDGStyle <- liftIO useXDG
if not isXDGStyle
then removeDirIfEmptyOrIsSymlink binDir
else pure ()
#else
removeDirIfEmptyOrIsSymlink binDir
#endif
2021-07-02 21:26:07 +00:00
reportRemainingFiles :: MonadIO m => FilePath -> m [FilePath]
reportRemainingFiles dir = do
2021-07-15 11:32:48 +00:00
-- force the files so the errors don't leak
(force -> !remainingFiles) <- liftIO
(getDirectoryContentsRecursive dir >>= evaluate)
let normalizedFilePaths = fmap normalise remainingFiles
2021-06-15 12:00:30 +00:00
let sortedByDepthRemainingFiles = sortBy (flip compareFn) normalizedFilePaths
2021-07-02 21:26:07 +00:00
let remainingFilesAbsolute = fmap (dir </>) sortedByDepthRemainingFiles
pure remainingFilesAbsolute
where
calcDepth :: FilePath -> Int
calcDepth = length . filter isPathSeparator
compareFn :: FilePath -> FilePath -> Ordering
compareFn fp1 fp2 = compare (calcDepth fp1) (calcDepth fp2)
removeEmptyDirsRecursive :: (MonadReader env m, HasDirs env, MonadMask m, MonadIO m, MonadCatch m) => FilePath -> m ()
2021-07-02 21:26:07 +00:00
removeEmptyDirsRecursive fp = do
cs <- liftIO $ listDirectory fp >>= filterM doesDirectoryExist . fmap (fp </>)
2021-07-02 21:26:07 +00:00
forM_ cs removeEmptyDirsRecursive
hideError InappropriateType $ removeDirIfEmptyOrIsSymlink fp
-- we expect only files inside cache/log dir
-- we report remaining files/dirs later,
-- hence the force/quiet mode in these delete functions below.
deleteFile :: (MonadReader env m, HasDirs env, MonadMask m, MonadIO m) => FilePath -> m ()
deleteFile filepath = do
2021-07-15 11:32:48 +00:00
hideError doesNotExistErrorType
$ hideError InappropriateType $ rmFile filepath
removeDirIfEmptyOrIsSymlink :: (MonadReader env m, HasDirs env, MonadMask m, MonadIO m, MonadCatch m) => FilePath -> m ()
removeDirIfEmptyOrIsSymlink filepath =
hideError UnsatisfiedConstraints $
handleIO' InappropriateType
(handleIfSym filepath)
2021-07-22 13:45:08 +00:00
(liftIO $ rmDirectory filepath)
where
handleIfSym fp e = do
isSym <- liftIO $ pathIsSymbolicLink fp
if isSym
then deleteFile fp
else liftIO $ ioError e
2020-01-11 20:15:05 +00:00
2021-07-02 21:26:07 +00:00
2020-01-11 20:15:05 +00:00
------------------
--[ Debug info ]--
------------------
getDebugInfo :: ( Alternative m
, MonadFail m
, MonadReader env m
, HasDirs env
, MonadLogger m
, MonadCatch m
, MonadIO m
)
2020-01-11 20:15:05 +00:00
=> Excepts
'[NoCompatiblePlatform , NoCompatibleArch , DistroNotFound]
m
DebugInfo
getDebugInfo = do
Dirs {..} <- lift getDirs
let diBaseDir = baseDir
let diBinDir = binDir
diGHCDir <- lift ghcupGHCBaseDir
let diCacheDir = cacheDir
diArch <- lE getArchitecture
2021-03-11 16:03:51 +00:00
diPlatform <- liftE getPlatform
2020-01-11 20:15:05 +00:00
pure $ DebugInfo { .. }
---------------
--[ Compile ]--
---------------
2020-07-22 00:34:17 +00:00
-- | Compile a GHC from source. This behaves wrt symlinks and installation
2020-07-21 23:08:58 +00:00
-- the same as 'installGHCBin'.
2020-01-11 20:15:05 +00:00
compileGHC :: ( MonadMask m
, MonadReader env m
, HasDirs env
, HasPlatformReq env
, HasGHCupInfo env
, HasSettings env
2020-01-11 20:15:05 +00:00
, MonadThrow m
, MonadResource m
, MonadLogger m
, MonadIO m
, MonadUnliftIO m
2020-01-11 20:15:05 +00:00
, MonadFail m
)
2021-05-14 21:09:45 +00:00
=> Either GHCTargetVersion GitBranch -- ^ version to install
2021-06-05 20:26:35 +00:00
-> Maybe Version -- ^ overwrite version
2021-05-14 21:09:45 +00:00
-> Either Version FilePath -- ^ version to bootstrap with
-> Maybe Int -- ^ jobs
2021-05-14 21:09:45 +00:00
-> Maybe FilePath -- ^ build config
-> Maybe FilePath -- ^ patch directory
-> [Text] -- ^ additional args to ./configure
-> Maybe String -- ^ build flavour
-> Bool
-> Maybe FilePath -- ^ isolate dir
2020-01-11 20:15:05 +00:00
-> Excepts
'[ AlreadyInstalled
, BuildFailed
, DigestError
, DownloadFailed
, GHCupSetError
, NoDownload
2020-04-10 20:44:43 +00:00
, NotFoundInPATH
, PatchFailed
2020-01-11 20:15:05 +00:00
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
, NotInstalled
2021-08-11 10:24:51 +00:00
, DirNotEmpty
#if !defined(TAR)
, ArchiveResult
#endif
2020-01-11 20:15:05 +00:00
]
m
2021-04-28 16:45:48 +00:00
GHCTargetVersion
compileGHC targetGhc ov bstrap jobs mbuildConfig patchdir aargs buildFlavour hadrian isolateDir
= do
PlatformRequest { .. } <- lift getPlatformReq
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
2021-04-28 16:45:48 +00:00
(workdir, tmpUnpack, tver) <- case targetGhc of
-- unpack from version tarball
Left tver -> do
lift $ $(logDebug) [i|Requested to compile: #{tver} with #{bstrap}|]
-- download source tarball
dlInfo <-
preview (ix GHC % ix (tver ^. tvVersion) % viSourceDL % _Just) dls
?? NoDownload
2021-07-18 21:29:09 +00:00
dl <- liftE $ downloadCached dlInfo Nothing
2021-04-28 16:45:48 +00:00
-- unpack
tmpUnpack <- lift mkGhcupTmpDir
liftE $ unpackToDir tmpUnpack dl
2021-05-14 21:09:45 +00:00
void $ lift $ darwinNotarization _rPlatform tmpUnpack
2021-04-28 16:45:48 +00:00
workdir <- maybe (pure tmpUnpack)
(liftE . intoSubdir tmpUnpack)
(view dlSubdir dlInfo)
pure (workdir, tmpUnpack, tver)
-- clone from git
Right GitBranch{..} -> do
tmpUnpack <- lift mkGhcupTmpDir
2021-05-14 21:09:45 +00:00
let git args = execLogged "git" ("--no-pager":args) (Just tmpUnpack) "git" Nothing
2021-04-28 16:45:48 +00:00
tver <- reThrowAll @_ @'[ProcessError] DownloadFailed $ do
let rep = fromMaybe "https://gitlab.haskell.org/ghc/ghc.git" repo
lift $ $(logInfo) [i|Fetching git repo #{rep} at ref #{ref} (this may take a while)|]
lEM $ git [ "init" ]
lEM $ git [ "remote"
, "add"
, "origin"
, fromString rep ]
let fetch_args =
[ "fetch"
, "--depth"
, "1"
, "--quiet"
, "origin"
, fromString ref ]
lEM $ git fetch_args
lEM $ git [ "checkout", "FETCH_HEAD" ]
lEM $ git [ "submodule", "update", "--init", "--depth", "1" ]
2021-06-12 20:27:56 +00:00
lEM $ execLogged "python3" ["./boot"] (Just tmpUnpack) "ghc-bootstrap" Nothing
2021-05-14 21:09:45 +00:00
lEM $ execLogged "sh" ["./configure"] (Just tmpUnpack) "ghc-bootstrap" Nothing
CapturedProcess {..} <- lift $ makeOut
2021-04-28 16:45:48 +00:00
["show!", "--quiet", "VALUE=ProjectVersion" ] (Just tmpUnpack)
case _exitCode of
2021-05-14 21:09:45 +00:00
ExitSuccess -> throwEither . MP.parse ghcProjectVersion "" . decUTF8Safe' $ _stdOut
ExitFailure c -> fail ("Could not figure out GHC project version. Exit code was: " <> show c <> ". Error was: " <> T.unpack (decUTF8Safe' _stdErr))
2021-04-28 16:45:48 +00:00
2021-05-14 21:09:45 +00:00
void $ lift $ darwinNotarization _rPlatform tmpUnpack
lift $ $(logInfo) [i|Git version #{ref} corresponds to GHC version #{prettyVer tver}|]
2021-04-28 16:45:48 +00:00
pure (tmpUnpack, tmpUnpack, GHCTargetVersion Nothing tver)
2021-06-05 20:26:35 +00:00
-- the version that's installed may differ from the
-- compiled version, so the user can overwrite it
let installVer = maybe tver (\ov' -> tver { _tvVersion = ov' }) ov
2021-06-05 20:26:35 +00:00
alreadyInstalled <- lift $ ghcInstalled installVer
2021-03-11 16:03:51 +00:00
alreadySet <- fmap (== Just tver) $ lift $ ghcSet (_tvTarget tver)
when alreadyInstalled $ do
case isolateDir of
Just isoDir ->
lift $ $(logWarn) [i|GHC #{prettyShow tver} already installed. Isolate installing to #{isoDir} |]
Nothing ->
lift $ $(logWarn) [i|GHC #{prettyShow tver} already installed. Will overwrite existing version.|]
lift $ $(logWarn)
"...waiting for 10 seconds before continuing, you can still abort..."
liftIO $ threadDelay 10000000 -- give the user a sec to intervene
ghcdir <- case isolateDir of
Just isoDir -> pure isoDir
Nothing -> lift $ ghcupGHCDir installVer
bghc <- case bstrap of
Right g -> pure $ Right g
2021-05-14 21:09:45 +00:00
Left bver -> pure $ Left ("ghc-" <> (T.unpack . prettyVer $ bver) <> exeExt)
(mBindist, bmk) <- liftE $ runBuildAction
tmpUnpack
2020-09-17 19:20:38 +00:00
Nothing
(do
b <- if hadrian
then compileHadrianBindist bghc tver workdir ghcdir
else compileMakeBindist bghc tver workdir ghcdir
bmk <- liftIO $ handleIO (\_ -> pure "") $ B.readFile (build_mk workdir)
pure (b, bmk)
)
2020-01-11 20:15:05 +00:00
case isolateDir of
Nothing ->
-- only remove old ghc in regular installs
when alreadyInstalled $ do
lift $ $(logInfo) [i|Deleting existing installation|]
liftE $ rmGHCVer tver
_ -> pure ()
forM_ mBindist $ \bindist -> do
liftE $ installPackedGHC bindist
(Just $ RegexDir "ghc-.*")
ghcdir
(tver ^. tvVersion)
2020-01-11 20:15:05 +00:00
2021-05-14 21:09:45 +00:00
liftIO $ B.writeFile (ghcdir </> ghcUpSrcBuiltFile) bmk
case isolateDir of
-- set and make symlinks for regular (non-isolated) installs
Nothing -> do
reThrowAll GHCupSetError $ postGHCInstall tver
-- restore
when alreadySet $ liftE $ void $ setGHC tver SetGHCOnly
_ -> pure ()
2020-01-11 20:15:05 +00:00
2021-04-28 16:45:48 +00:00
pure tver
2020-01-11 20:15:05 +00:00
where
2021-04-28 16:45:48 +00:00
defaultConf = case targetGhc of
Left (GHCTargetVersion (Just _) _) -> [s|
2020-04-25 10:06:41 +00:00
V=0
BUILD_MAN = NO
BUILD_SPHINX_HTML = NO
BUILD_SPHINX_PDF = NO
2021-04-28 16:45:48 +00:00
HADDOCK_DOCS = NO
ifneq "$(BuildFlavour)" ""
include mk/flavours/$(BuildFlavour).mk
endif
2021-04-28 16:45:48 +00:00
Stage1Only = YES|]
_ -> [s|
2020-01-11 20:15:05 +00:00
V=0
BUILD_MAN = NO
BUILD_SPHINX_HTML = NO
BUILD_SPHINX_PDF = NO
HADDOCK_DOCS = YES
ifneq "$(BuildFlavour)" ""
include mk/flavours/$(BuildFlavour).mk
endif|]
2020-01-11 20:15:05 +00:00
compileHadrianBindist :: ( MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, MonadThrow m
, MonadCatch m
, MonadLogger m
, MonadIO m
, MonadFail m
)
=> Either FilePath FilePath
-> GHCTargetVersion
-> FilePath
-> FilePath
-> Excepts
'[ FileDoesNotExistError
, HadrianNotFound
, InvalidBuildConfig
, PatchFailed
, ProcessError
, NotFoundInPATH
, CopyError]
m
(Maybe FilePath) -- ^ output path of bindist, None for cross
compileHadrianBindist bghc tver workdir ghcdir = do
lEM $ execLogged "python3" ["./boot"] (Just workdir) "ghc-bootstrap" Nothing
liftE $ configureBindist bghc tver workdir ghcdir
lift $ $(logInfo) [i|Building (this may take a while)...|]
hadrian_build <- liftE $ findHadrianFile workdir
lEM $ execLogged hadrian_build
( maybe [] (\j -> [[i|-j#{j}|]] ) jobs
++ maybe [] (\bf -> [[i|--flavour=#{bf}|]]) buildFlavour
++ ["binary-dist"]
)
(Just workdir) "ghc-make" Nothing
[tar] <- liftIO $ findFiles
(workdir </> "_build" </> "bindist")
(makeRegexOpts compExtended
execBlank
([s|^ghc-.*\.tar\..*$|] :: ByteString)
)
liftE $ fmap Just $ copyBindist tver tar (workdir </> "_build" </> "bindist")
2020-03-18 16:31:17 +00:00
findHadrianFile :: (MonadIO m)
=> FilePath
-> Excepts
'[HadrianNotFound]
m
FilePath
findHadrianFile workdir = do
2021-05-14 21:09:45 +00:00
#if defined(IS_WINDOWS)
let possible_files = ((workdir </> "hadrian") </>) <$> ["build.bat"]
#else
let possible_files = ((workdir </> "hadrian") </>) <$> ["build", "build.sh"]
2021-05-14 21:09:45 +00:00
#endif
exsists <- forM possible_files (\f -> liftIO (doesFileExist f) <&> (,f))
case filter fst exsists of
[] -> throwE HadrianNotFound
((_, x):_) -> pure x
compileMakeBindist :: ( MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, MonadThrow m
, MonadCatch m
, MonadLogger m
, MonadIO m
, MonadFail m
)
=> Either FilePath FilePath
-> GHCTargetVersion
-> FilePath
-> FilePath
-> Excepts
'[ FileDoesNotExistError
, HadrianNotFound
, InvalidBuildConfig
, PatchFailed
, ProcessError
, NotFoundInPATH
, CopyError]
m
(Maybe FilePath) -- ^ output path of bindist, None for cross
compileMakeBindist bghc tver workdir ghcdir = do
liftE $ configureBindist bghc tver workdir ghcdir
2020-01-11 20:15:05 +00:00
case mbuildConfig of
Just bc -> liftIOException
doesNotExistErrorType
2021-05-14 21:09:45 +00:00
(FileDoesNotExistError bc)
(liftIO $ copyFile bc (build_mk workdir))
2020-01-11 20:15:05 +00:00
Nothing ->
liftIO $ B.writeFile (build_mk workdir) (addBuildFlavourToConf defaultConf)
liftE $ checkBuildConfig (build_mk workdir)
2020-01-11 20:15:05 +00:00
lift $ $(logInfo) [i|Building (this may take a while)...|]
lEM $ make (maybe [] (\j -> ["-j" <> fS (show j)]) jobs) (Just workdir)
if | isCross tver -> do
lift $ $(logInfo) [i|Installing cross toolchain...|]
lEM $ make ["install"] (Just workdir)
pure Nothing
| otherwise -> do
lift $ $(logInfo) [i|Creating bindist...|]
lEM $ make ["binary-dist"] (Just workdir)
[tar] <- liftIO $ findFiles
workdir
(makeRegexOpts compExtended
execBlank
([s|^ghc-.*\.tar\..*$|] :: ByteString)
)
liftE $ fmap Just $ copyBindist tver tar workdir
2020-01-11 20:15:05 +00:00
2021-05-14 21:09:45 +00:00
build_mk workdir = workdir </> "mk" </> "build.mk"
2020-01-11 20:15:05 +00:00
copyBindist :: ( MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, MonadIO m
, MonadThrow m
, MonadCatch m
, MonadLogger m
)
=> GHCTargetVersion
-> FilePath -- ^ tar file
-> FilePath -- ^ workdir
-> Excepts
'[CopyError]
m
FilePath
copyBindist tver tar workdir = do
Dirs {..} <- lift getDirs
pfreq <- lift getPlatformReq
c <- liftIO $ BL.readFile (workdir </> tar)
cDigest <-
fmap (T.take 8)
. lift
. throwEither
. E.decodeUtf8'
. B16.encode
. SHA256.hashlazy
$ c
cTime <- liftIO getCurrentTime
let tarName = makeValid [i|ghc-#{tVerToText tver}-#{pfReqToString pfreq}-#{iso8601Show cTime}-#{cDigest}.tar#{takeExtension tar}|]
let tarPath = cacheDir </> tarName
handleIO (throwE . CopyError . show) $ liftIO $ copyFile (workdir </> tar)
tarPath
lift $ $(logInfo) [i|Copied bindist to #{tarPath}|]
pure tarPath
checkBuildConfig :: (MonadCatch m, MonadIO m, MonadLogger m)
=> FilePath
-> Excepts
'[FileDoesNotExistError, InvalidBuildConfig]
2020-04-25 10:06:41 +00:00
m
()
checkBuildConfig bc = do
c <- liftIOException
doesNotExistErrorType
(FileDoesNotExistError bc)
(liftIO $ B.readFile bc)
2020-04-25 10:06:41 +00:00
let lines' = fmap T.strip . T.lines $ decUTF8Safe c
-- for cross, we need Stage1Only
2021-04-28 16:45:48 +00:00
case targetGhc of
Left (GHCTargetVersion (Just _) _) -> when ("Stage1Only = YES" `notElem` lines') $ throwE
2020-04-25 10:06:41 +00:00
(InvalidBuildConfig
[s|Cross compiling needs to be a Stage1 build, add "Stage1Only = YES" to your config!|]
)
2021-04-28 16:45:48 +00:00
_ -> pure ()
2020-04-25 10:06:41 +00:00
forM_ buildFlavour $ \bf ->
when ([i|BuildFlavour = #{bf}|] `notElem` lines') $ do
lift $ $(logWarn) [i|Customly specified build config overwrites --flavour=#{bf} switch! Waiting 5 seconds...|]
liftIO $ threadDelay 5000000
addBuildFlavourToConf bc = case buildFlavour of
2021-08-11 14:19:52 +00:00
Just bf -> [i|BuildFlavour = #{bf}
|] <> [i|#{bc}|]
Nothing -> bc
isCross :: GHCTargetVersion -> Bool
isCross = isJust . _tvTarget
2020-04-25 10:06:41 +00:00
configureBindist :: ( MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, MonadThrow m
, MonadCatch m
, MonadLogger m
, MonadIO m
, MonadFail m
)
=> Either FilePath FilePath
-> GHCTargetVersion
-> FilePath
-> FilePath
-> Excepts
'[ FileDoesNotExistError
, InvalidBuildConfig
, PatchFailed
, ProcessError
, NotFoundInPATH
, CopyError
]
m
()
configureBindist bghc tver workdir ghcdir = do
lift $ $(logInfo) [s|configuring build|]
forM_ patchdir (\dir -> liftE $ applyPatches dir workdir)
cEnv <- liftIO getEnvironment
if | _tvVersion tver >= [vver|8.8.0|] -> do
bghcPath <- case bghc of
Right ghc' -> pure ghc'
Left bver -> do
spaths <- liftIO getSearchPath
liftIO (searchPath spaths bver) !? NotFoundInPATH bver
lEM $ execLogged
"sh"
("./configure" : maybe mempty
(\x -> ["--target=" <> T.unpack x])
(_tvTarget tver)
++ ["--prefix=" <> ghcdir]
#if defined(IS_WINDOWS)
++ ["--enable-tarballs-autodownload"]
#endif
++ fmap T.unpack aargs
)
(Just workdir)
"ghc-conf"
(Just (("GHC", bghcPath) : cEnv))
| otherwise -> do
lEM $ execLogged
"sh"
( [ "./configure", "--with-ghc=" <> either id id bghc
]
++ maybe mempty
(\x -> ["--target=" <> T.unpack x])
(_tvTarget tver)
++ ["--prefix=" <> ghcdir]
#if defined(IS_WINDOWS)
++ ["--enable-tarballs-autodownload"]
#endif
++ fmap T.unpack aargs
)
(Just workdir)
"ghc-conf"
(Just cEnv)
pure ()
2020-04-25 10:06:41 +00:00
2020-01-11 20:15:05 +00:00
---------------------
--[ Upgrade GHCup ]--
---------------------
2020-07-21 23:08:58 +00:00
-- | Upgrade ghcup and place it in @~\/.ghcup\/bin\/ghcup@,
-- if no path is provided.
2020-01-11 20:15:05 +00:00
upgradeGHCup :: ( MonadMask m
, MonadReader env m
, HasDirs env
, HasPlatformReq env
, HasGHCupInfo env
, HasSettings env
2020-01-11 20:15:05 +00:00
, MonadCatch m
, MonadLogger m
, MonadThrow m
, MonadResource m
, MonadIO m
, MonadUnliftIO m
2020-01-11 20:15:05 +00:00
)
2021-05-14 21:09:45 +00:00
=> Maybe FilePath -- ^ full file destination to write ghcup into
-> Bool -- ^ whether to force update regardless
-- of currently installed version
2020-01-11 20:15:05 +00:00
-> Excepts
'[ CopyError
, DigestError
, DownloadFailed
, NoDownload
, NoUpdate
2020-01-11 20:15:05 +00:00
]
m
Version
2021-06-15 12:00:30 +00:00
upgradeGHCup mtarget force' = do
Dirs {..} <- lift getDirs
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
2020-01-11 20:15:05 +00:00
lift $ $(logInfo) [i|Upgrading GHCup...|]
let latestVer = fromJust $ fst <$> getLatest dls GHCup
2021-06-15 12:00:30 +00:00
when (not force' && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate
2021-07-19 14:49:18 +00:00
dli <- liftE $ getDownloadInfo GHCup latestVer
tmp <- lift withGHCupTmpDir
2021-05-14 21:09:45 +00:00
let fn = "ghcup" <> exeExt
2021-07-24 14:36:31 +00:00
p <- liftE $ download (_dlUri dli) (Just (_dlHash dli)) tmp (Just fn) False
2021-05-14 21:09:45 +00:00
let destDir = takeDirectory destFile
2021-07-22 13:45:08 +00:00
destFile = fromMaybe (binDir </> fn) mtarget
2021-05-14 21:09:45 +00:00
lift $ $(logDebug) [i|mkdir -p #{destDir}|]
liftIO $ createDirRecursive' destDir
2021-05-14 21:09:45 +00:00
lift $ $(logDebug) [i|rm -f #{destFile}|]
2021-07-22 13:45:08 +00:00
lift $ hideError NoSuchThing $ recycleFile destFile
2021-05-14 21:09:45 +00:00
lift $ $(logDebug) [i|cp #{p} #{destFile}|]
2020-07-07 17:39:58 +00:00
handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
destFile
lift $ chmod_755 destFile
2021-03-11 16:03:51 +00:00
liftIO (isInPath destFile) >>= \b -> unless b $
2021-05-14 21:09:45 +00:00
lift $ $(logWarn) [i|"#{takeFileName destFile}" is not in PATH! You have to add it in order to use ghcup.|]
liftIO (isShadowed destFile) >>= \case
Nothing -> pure ()
2021-05-14 21:09:45 +00:00
Just pa -> lift $ $(logWarn) [i|ghcup is shadowed by "#{pa}". The upgrade will not be in effect, unless you remove "#{pa}" or make sure "#{destDir}" comes before "#{takeFileName pa}" in PATH.|]
2020-01-11 20:15:05 +00:00
pure latestVer
-------------
--[ Other ]--
-------------
2020-01-11 20:15:05 +00:00
2020-07-21 23:08:58 +00:00
-- | Creates @ghc-x.y.z@ and @ghc-x.y@ symlinks. This is used for
2020-01-11 20:15:05 +00:00
-- both installing from source and bindist.
postGHCInstall :: ( MonadReader env m
, HasDirs env
2020-08-06 11:28:20 +00:00
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadCatch m
2021-05-14 21:09:45 +00:00
, MonadMask m
, MonadUnliftIO m
2020-08-06 11:28:20 +00:00
)
2020-04-25 10:06:41 +00:00
=> GHCTargetVersion
2020-01-11 20:15:05 +00:00
-> Excepts '[NotInstalled] m ()
2020-08-06 11:28:20 +00:00
postGHCInstall ver@GHCTargetVersion {..} = do
void $ liftE $ setGHC ver SetGHC_XYZ
2020-01-11 20:15:05 +00:00
-- Create ghc-x.y symlinks. This may not be the current
-- version, create it regardless.
2020-08-06 11:28:20 +00:00
v' <-
handle (\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing)
$ fmap Just
$ getMajorMinorV _tvVersion
forM_ v' $ \(mj, mi) -> lift (getGHCForMajor mj mi _tvTarget)
>>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY)
-- | Reports the binary location of a given tool:
--
-- * for GHC, this reports: @~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
-- * for cabal, this reports @~\/.ghcup\/bin\/cabal-\<ver\>@
-- * for hls, this reports @~\/.ghcup\/bin\/haskell-language-server-wrapper-\<ver\>@
-- * for stack, this reports @~\/.ghcup\/bin\/stack-\<ver\>@
-- * for ghcup, this reports the location of the currently running executable
whereIsTool :: ( MonadReader env m
, HasDirs env
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadCatch m
, MonadMask m
, MonadUnliftIO m
)
=> Tool
-> GHCTargetVersion
-> Excepts '[NotInstalled] m FilePath
whereIsTool tool ver@GHCTargetVersion {..} = do
dirs <- lift getDirs
case tool of
GHC -> do
whenM (lift $ fmap not $ ghcInstalled ver)
$ throwE (NotInstalled GHC ver)
bdir <- lift $ ghcupGHCDir ver
2021-07-15 20:38:42 +00:00
pure (bdir </> "bin" </> ghcBinaryName ver)
Cabal -> do
whenM (lift $ fmap not $ cabalInstalled _tvVersion)
$ throwE (NotInstalled Cabal (GHCTargetVersion Nothing _tvVersion))
pure (binDir dirs </> "cabal-" <> T.unpack (prettyVer _tvVersion) <> exeExt)
HLS -> do
whenM (lift $ fmap not $ hlsInstalled _tvVersion)
$ throwE (NotInstalled HLS (GHCTargetVersion Nothing _tvVersion))
pure (binDir dirs </> "haskell-language-server-wrapper-" <> T.unpack (prettyVer _tvVersion) <> exeExt)
Stack -> do
whenM (lift $ fmap not $ stackInstalled _tvVersion)
$ throwE (NotInstalled Stack (GHCTargetVersion Nothing _tvVersion))
pure (binDir dirs </> "stack-" <> T.unpack (prettyVer _tvVersion) <> exeExt)
GHCup -> do
currentRunningExecPath <- liftIO getExecutablePath
liftIO $ canonicalizePath currentRunningExecPath