ghcup-hs/lib/GHCup.hs

2260 lines
74 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 )
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
-> Excepts
'[ AlreadyInstalled
, BuildFailed
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
#if !defined(TAR)
2020-07-21 23:08:58 +00:00
, ArchiveResult
#endif
2020-07-21 23:08:58 +00:00
]
m
()
2021-05-14 21:09:45 +00:00
installGHCBindist dlinfo ver = 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}|]
2021-03-11 16:03:51 +00:00
whenM (lift $ ghcInstalled tver) (throwE $ AlreadyInstalled GHC ver)
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
2021-05-14 21:09:45 +00:00
liftE $ installPackedGHC dl (view dlSubdir dlinfo) ghcdir ver
2020-01-11 20:15:05 +00:00
2020-04-25 10:06:41 +00:00
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."
-- | Installs GHC to a specified location, doesn't make any symlinks.
installGHCBinIsolated :: ( 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
)
=> FilePath
-> Version -- ^ the version to install
-> Excepts
'[ AlreadyInstalled
, BuildFailed
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installGHCBinIsolated isoDir ver = do
dlinfo <- liftE $ getDownloadInfo GHC ver
lift $ $(logDebug) [i|Requested to install GHC with #{ver}|]
-- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing
liftE $ installPackedGHC dl (view dlSubdir dlinfo) isoDir ver
-- | 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
#if !defined(TAR)
, ArchiveResult
#endif
] m ()
2021-05-14 21:09:45 +00:00
installPackedGHC dl msubdir inst ver = do
#if defined(IS_WINDOWS)
lift $ $(logInfo) "Installing GHC (this may take a while)"
Dirs { tmpDir } <- lift getDirs
unpackDir <- liftIO $ emptyTempFile tmpDir "ghc"
liftIO $ rmFile unpackDir
liftE $ unpackToDir unpackDir dl
d <- case msubdir of
Just td -> liftE $ intoSubdir unpackDir td
Nothing -> pure unpackDir
liftIO $ Win32.moveFileEx d (Just inst) 0
liftIO $ rmPath unpackDir
#else
PlatformRequest {..} <- lift getPlatformReq
2021-05-14 21:09:45 +00:00
-- 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)
#endif
-- | 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-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
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)
#if defined(IS_WINDOWS)
: "--enable-tarballs-autodownload"
#endif
: alpineArgs
)
(Just path)
2021-05-14 21:09:45 +00:00
"ghc-configure"
Nothing
lEM $ make ["install"] (Just path)
pure ()
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
-> Excepts
'[ AlreadyInstalled
, BuildFailed
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installGHCBin ver = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo GHC ver
2021-05-14 21:09:45 +00:00
installGHCBindist dlinfo ver
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
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installCabalBindist dlinfo ver = 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
whenM
(lift (cabalInstalled ver) >>= \a -> liftIO $
handleIO (\_ -> pure False)
2021-05-14 21:09:45 +00:00
$ fmap (\x -> a && x)
-- ignore when the installation is a legacy cabal (binary, not symlink)
2021-05-14 21:09:45 +00:00
$ pathIsLink (binDir </> "cabal" <> exeExt)
)
2021-03-11 16:03:51 +00:00
(throwE $ AlreadyInstalled Cabal ver)
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
liftE $ installCabal' workdir binDir ver
-- create symlink if this is the latest version
2021-03-11 16:03:51 +00:00
cVers <- lift $ fmap rights getInstalledCabals
let lInstCabal = headMay . reverse . sort $ cVers
when (maybe True (ver >=) lInstCabal) $ liftE $ setCabal ver
-- | Install an unpacked cabal distribution.
installCabal' :: (MonadLogger m, MonadCatch m, MonadIO m)
=> FilePath -- ^ Path to the unpacked cabal bindist (where the executable resides)
-> FilePath -- ^ Path to install to
-> Version
-> Excepts '[CopyError] m ()
installCabal' path inst ver = do
lift $ $(logInfo) "Installing cabal"
let cabalFile = "cabal"
liftIO $ createDirRecursive' inst
let destFileName = cabalFile <> "-" <> T.unpack (prettyVer ver) <> 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
-- | Installs GHC to a specified location, doesn't make any symlinks.
installCabalBinIsolated :: ( 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
)
=> FilePath
-> Version
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installCabalBinIsolated isoDir ver = do
dlinfo <- liftE $ getDownloadInfo Cabal ver
lift $ $(logDebug) [i|Requested to install cabal version #{ver}|]
-- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing
PlatformRequest {_rPlatform} <- lift getPlatformReq
-- unpack
tmpUnpack <- lift withGHCupTmpDir
liftE $ unpackToDir tmpUnpack dl
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)
lift $ $(logInfo) [i|isolated installing Cabal to #{isoDir}|]
liftE $ installCabal' workdir isoDir ver
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
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
2020-08-06 11:28:20 +00:00
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installCabalBin ver = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo Cabal ver
2021-05-14 21:09:45 +00:00
installCabalBindist dlinfo ver
-- | 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
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installHLSBindist dlinfo ver = do
lift $ $(logDebug) [i|Requested to install hls version #{ver}|]
PlatformRequest {..} <- lift getPlatformReq
2021-07-18 21:29:09 +00:00
Dirs {..} <- lift getDirs
whenM (lift (hlsInstalled ver))
2021-03-11 16:03:51 +00:00
(throwE $ AlreadyInstalled HLS ver)
-- 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)
liftE $ installHLS' workdir binDir ver
-- create symlink if this is the latest version
2021-03-11 16:03:51 +00:00
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.
installHLS' :: (MonadFail m, MonadLogger m, MonadCatch m, MonadIO m)
=> FilePath -- ^ Path to the unpacked hls bindist (where the executable resides)
-> FilePath -- ^ Path to install to
-> Version
-> Excepts '[CopyError] m ()
installHLS' path inst ver = do
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
<> "~" <> T.unpack (prettyVer ver) <> 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"
toF = wrapper <> "-" <> T.unpack (prettyVer ver) <> exeExt
handleIO (throwE . CopyError . show) $ liftIO $ copyFile
(path </> wrapper <> exeExt)
(inst </> toF)
lift $ chmod_755 (inst </> toF)
2021-07-23 10:55:01 +00:00
-- | Installs hls binaries in an isolated location provided by user,
-- doesn't make any symlinks
installHLSBinIsolated :: ( 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
)
=> FilePath
-> Version
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installHLSBinIsolated isoDir ver = do
dlinfo <- liftE $ getDownloadInfo HLS ver
lift $ $(logDebug) [i|Requested to install hls version #{ver}|]
PlatformRequest {_rPlatform} <- lift getPlatformReq
-- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing
-- unpack
tmpUnpack <- lift withGHCupTmpDir
liftE $ unpackToDir tmpUnpack dl
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)
lift $ $(logInfo) [i|isolated installing HLS to #{isoDir}|]
liftE $ installHLS' workdir isoDir ver
-- | 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
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installHLSBin ver = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo HLS ver
2021-05-14 21:09:45 +00:00
installHLSBindist dlinfo ver
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
2021-05-14 22:31:36 +00:00
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installStackBin ver = do
2021-07-19 14:49:18 +00:00
dlinfo <- liftE $ getDownloadInfo Stack ver
2021-05-14 21:09:45 +00:00
installStackBindist dlinfo ver
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
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
2021-05-14 21:09:45 +00:00
installStackBindist dlinfo ver = 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
2021-07-12 20:15:45 +00:00
whenM (lift (stackInstalled ver))
2021-05-14 22:31:36 +00:00
(throwE $ AlreadyInstalled Stack ver)
-- 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)
liftE $ installStack' workdir binDir ver
2021-05-14 22:31:36 +00:00
-- create symlink if this is the latest version
sVers <- lift $ fmap rights getInstalledStacks
let lInstStack = headMay . reverse . sort $ sVers
when (maybe True (ver >=) lInstStack) $ liftE $ setStack ver
2021-07-23 11:13:43 +00:00
-- | Install an unpacked stack distribution.
installStack' :: (MonadLogger m, MonadCatch m, MonadIO m)
=> FilePath -- ^ Path to the unpacked stack bindist (where the executable resides)
-> FilePath -- ^ Path to install to
-> Version
-> Excepts '[CopyError] m ()
installStack' path inst ver = do
lift $ $(logInfo) "Installing stack"
let stackFile = "stack"
liftIO $ createDirRecursive' inst
let destFileName = stackFile <> "-" <> T.unpack (prettyVer ver) <> exeExt
let destPath = inst </> destFileName
handleIO (throwE . CopyError . show) $ liftIO $ copyFile
(path </> stackFile <> exeExt)
destPath
lift $ chmod_755 destPath
2021-05-14 22:31:36 +00:00
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)
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}|]
2021-05-14 21:09:45 +00:00
liftIO $ hideError doesNotExistErrorType $ removeDirectoryLink 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}|]
liftIO $ 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.
2020-04-21 21:37:48 +00:00
availableToolVersions :: GHCupDownloads -> Tool -> Map.Map Version [Tag]
availableToolVersions av tool = view
2021-03-11 16:03:51 +00:00
(at tool % non Map.empty % to (fmap _viTags))
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
slr <- strayHLS avTools
pure (sort (slr ++ lr))
2021-05-14 22:31:36 +00:00
Stack -> do
slr <- strayStacks avTools
pure (sort (slr ++ lr))
GHCup -> pure 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
)
2020-04-21 21:37:48 +00:00
=> Map.Map Version [Tag]
-> 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
)
2020-08-14 14:53:32 +00:00
=> Map.Map Version [Tag]
-> 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 [Tag]
-> m [ListResult]
strayHLS avTools = do
hlss <- getInstalledHLSs
fmap catMaybes $ forM hlss $ \case
Right ver ->
case Map.lookup ver avTools of
Just _ -> pure Nothing
Nothing -> do
2021-03-11 16:03:51 +00:00
lSet <- fmap (== Just ver) hlsSet
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
)
2021-05-14 22:31:36 +00:00
=> Map.Map Version [Tag]
-> m [ListResult]
strayStacks avTools = do
stacks <- getInstalledStacks
fmap catMaybes $ forM stacks $ \case
Right ver ->
case Map.lookup ver avTools of
Just _ -> pure Nothing
Nothing -> do
lSet <- fmap (== Just ver) hlsSet
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
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, [Tag])
-> m ListResult
2021-05-14 21:09:45 +00:00
toListResult t cSet cabals hlsSet' hlses stackSet' stacks (v, tags) = do
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}|]
liftIO $ rmPath 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
liftIO
$ hideError doesNotExistErrorType
2021-05-14 21:09:45 +00:00
$ rmFile (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
liftIO $ hideError doesNotExistErrorType $ rmFile (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
2021-05-14 21:09:45 +00:00
Nothing -> liftIO $ 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-05-14 21:09:45 +00:00
forM_ bins $ \f -> liftIO $ rmFile (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}|]
liftIO $ 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
liftIO $ hideError doesNotExistErrorType $ rmFile (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
2021-05-14 21:09:45 +00:00
Nothing -> liftIO $ 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
)
=> m ()
rmGhcup = do
Dirs {binDir} <- 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-02 21:26:07 +00:00
-- since it doesn't seem possible to delete a running exec in windows
-- we move it to temp dir, to be deleted at next reboot
tempDir <- liftIO $ getTemporaryDirectory
let tempFilepath = tempDir </> ghcupFilename
hideError UnsupportedOperation $
liftIO $ hideError NoSuchThing $
Win32.moveFileEx ghcupFilepath (Just tempFilepath) Win32.mOVEFILE_REPLACE_EXISTING
#else
2021-07-02 21:26:07 +00:00
-- delete it.
hideError doesNotExistErrorType $ liftIO $ 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
, tmpDir
} <- getDirs
let envFilePath = baseDir </> "env"
confFilePath <- getConfigFilePath
2021-07-02 21:26:07 +00:00
rmEnvFile envFilePath
rmConfFile confFilePath
2021-07-03 09:25:49 +00:00
rmDir cacheDir
rmDir logsDir
2021-07-02 21:26:07 +00:00
rmBinDir binDir
rmDir tmpDir
2021-07-02 21:26:07 +00:00
#if defined(IS_WINDOWS)
2021-07-03 09:25:49 +00:00
rmDir (baseDir </> "msys64")
2021-07-02 21:26:07 +00:00
#endif
2021-07-02 21:26:07 +00:00
liftIO $ 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-02 21:26:07 +00:00
rmEnvFile :: (MonadCatch m, MonadLogger m, MonadIO m) => FilePath -> m ()
rmEnvFile enFilePath = do
$logInfo "Removing Ghcup Environment File"
2021-07-15 11:32:48 +00:00
liftIO $ deleteFile enFilePath
2021-07-02 21:26:07 +00:00
rmConfFile :: (MonadCatch m, MonadLogger m, MonadIO m) => FilePath -> m ()
rmConfFile confFilePath = do
$logInfo "removing Ghcup Config File"
2021-07-15 11:32:48 +00:00
liftIO $ deleteFile confFilePath
2021-06-15 12:00:30 +00:00
rmDir :: (MonadLogger 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 (liftIO . deleteFile . (dir </>))
2021-07-02 21:26:07 +00:00
rmBinDir :: (MonadCatch m, MonadIO 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)
2021-07-02 21:26:07 +00:00
removeEmptyDirsRecursive :: FilePath -> IO ()
removeEmptyDirsRecursive fp = do
cs <- listDirectory fp >>= filterM doesDirectoryExist . fmap (fp </>)
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.
2021-07-02 21:26:07 +00:00
deleteFile :: FilePath -> IO ()
deleteFile filepath = do
2021-07-15 11:32:48 +00:00
hideError doesNotExistErrorType
$ hideError InappropriateType $ rmFile filepath
2021-07-02 21:26:07 +00:00
removeDirIfEmptyOrIsSymlink :: (MonadCatch m, MonadIO m) => FilePath -> m ()
removeDirIfEmptyOrIsSymlink filepath =
hideError UnsatisfiedConstraints $
handleIO' InappropriateType
(handleIfSym filepath)
(liftIO $ removeDirectory filepath)
where
handleIfSym fp e = do
isSym <- liftIO $ pathIsSymbolicLink fp
if isSym
then liftIO $ 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
2020-04-25 10:06:41 +00:00
-> [Text] -- ^ additional args to ./configure
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
#if !defined(TAR)
, ArchiveResult
#endif
2020-01-11 20:15:05 +00:00
]
m
2021-04-28 16:45:48 +00:00
GHCTargetVersion
2021-06-05 20:26:35 +00:00
compileGHC targetGhc ov bstrap jobs mbuildConfig patchdir aargs
= 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
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
2021-06-05 20:26:35 +00:00
ghcdir <- 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 <- compileBindist bghc tver workdir ghcdir
2021-05-14 21:09:45 +00:00
bmk <- liftIO $ B.readFile (build_mk workdir)
pure (b, bmk)
)
2020-01-11 20:15:05 +00:00
when alreadyInstalled $ do
lift $ $(logInfo) [i|Deleting existing installation|]
liftE $ rmGHCVer tver
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
2020-01-11 20:15:05 +00:00
reThrowAll GHCupSetError $ postGHCInstall tver
-- restore
when alreadySet $ liftE $ void $ setGHC tver SetGHCOnly
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
Stage1Only = YES|]
_ -> [s|
2020-01-11 20:15:05 +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 = YES|]
2020-01-11 20:15:05 +00:00
compileBindist :: ( MonadReader env m
, HasDirs env
, HasSettings env
, HasPlatformReq env
, MonadThrow m
, MonadCatch m
, MonadLogger m
, MonadIO m
, MonadFail m
)
2021-05-14 21:09:45 +00:00
=> Either FilePath FilePath
2021-04-28 16:45:48 +00:00
-> GHCTargetVersion
2021-05-14 21:09:45 +00:00
-> FilePath
-> FilePath
-> Excepts
'[FileDoesNotExistError, InvalidBuildConfig, PatchFailed, ProcessError, NotFoundInPATH, CopyError]
m
(Maybe FilePath) -- ^ output path of bindist, None for cross
compileBindist bghc tver workdir ghcdir = do
2020-01-11 20:15:05 +00:00
lift $ $(logInfo) [i|configuring build|]
2021-03-11 16:03:51 +00:00
liftE checkBuildConfig
Dirs {..} <- lift getDirs
pfreq <- lift getPlatformReq
forM_ patchdir $ \dir -> liftE $ applyPatches dir workdir
2021-03-11 16:03:51 +00:00
cEnv <- liftIO getEnvironment
2020-03-18 16:31:17 +00:00
2021-04-28 16:45:48 +00:00
if | _tvVersion tver >= [vver|8.8.0|] -> do
bghcPath <- case bghc of
Right ghc' -> pure ghc'
Left bver -> do
2021-05-14 21:09:45 +00:00
spaths <- liftIO getSearchPath
2021-04-28 16:45:48 +00:00
liftIO (searchPath spaths bver) !? NotFoundInPATH bver
lEM $ execLogged
2021-05-14 21:09:45 +00:00
"sh"
("./configure" : maybe mempty
(\x -> ["--target=" <> T.unpack x])
2021-04-28 16:45:48 +00:00
(_tvTarget tver)
++ ["--prefix=" <> ghcdir]
2021-05-14 21:09:45 +00:00
#if defined(IS_WINDOWS)
++ ["--enable-tarballs-autodownload"]
#endif
++ fmap T.unpack aargs
2021-04-28 16:45:48 +00:00
)
(Just workdir)
2021-05-14 21:09:45 +00:00
"ghc-conf"
(Just (("GHC", bghcPath) : cEnv))
2021-04-28 16:45:48 +00:00
| otherwise -> do
2020-07-13 09:52:34 +00:00
lEM $ execLogged
2021-05-14 21:09:45 +00:00
"sh"
( [ "./configure", "--with-ghc=" <> either id id bghc
2020-04-25 10:06:41 +00:00
]
2021-03-11 16:03:51 +00:00
++ maybe mempty
2021-05-14 21:09:45 +00:00
(\x -> ["--target=" <> T.unpack x])
2021-03-11 16:03:51 +00:00
(_tvTarget tver)
++ ["--prefix=" <> ghcdir]
2021-05-14 21:09:45 +00:00
#if defined(IS_WINDOWS)
++ ["--enable-tarballs-autodownload"]
2021-05-14 21:09:45 +00:00
#endif
++ fmap T.unpack aargs
2020-04-25 10:06:41 +00:00
)
2020-01-11 20:15:05 +00:00
(Just workdir)
2021-05-14 21:09:45 +00:00
"ghc-conf"
2020-04-25 10:06:41 +00:00
(Just cEnv)
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 ->
2021-05-14 21:09:45 +00:00
liftIO $ B.writeFile (build_mk workdir) defaultConf
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)
)
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 $ Just tarPath
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
2020-04-25 10:06:41 +00:00
checkBuildConfig :: (MonadCatch m, MonadIO m)
=> Excepts
'[FileDoesNotExistError, InvalidBuildConfig]
2020-04-25 10:06:41 +00:00
m
()
checkBuildConfig = do
c <- case mbuildConfig of
Just bc -> do
2021-05-14 21:09:45 +00:00
liftIOException
doesNotExistErrorType
2021-05-14 21:09:45 +00:00
(FileDoesNotExistError bc)
(liftIO $ B.readFile bc)
2020-04-25 10:06:41 +00:00
Nothing -> pure defaultConf
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
isCross :: GHCTargetVersion -> Bool
isCross = isJust . _tvTarget
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-18 21:29:09 +00:00
p <- liftE $ download dli tmp (Just fn)
2021-05-14 21:09:45 +00:00
let destDir = takeDirectory destFile
2021-06-13 08:15:34 +00:00
destFile = fromMaybe (binDir </> fn <> exeExt) mtarget
2021-05-14 21:09:45 +00:00
lift $ $(logDebug) [i|mkdir -p #{destDir}|]
liftIO $ createDirRecursive' destDir
2021-06-13 08:15:34 +00:00
#if defined(IS_WINDOWS)
let tempGhcup = cacheDir </> "ghcup.old"
liftIO $ hideError NoSuchThing $ rmFile tempGhcup
lift $ $(logDebug) [i|mv #{destFile} #{tempGhcup}|]
-- NoSuchThing may be raised when we're updating ghcup from
-- a non-standard location
liftIO $ hideError NoSuchThing $ Win32.moveFileEx destFile (Just tempGhcup) 0
2021-06-13 08:15:34 +00:00
lift $ $(logDebug) [i|cp #{p} #{destFile}|]
handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
destFile
#else
2021-05-14 21:09:45 +00:00
lift $ $(logDebug) [i|rm -f #{destFile}|]
liftIO $ hideError NoSuchThing $ rmFile destFile
lift $ $(logDebug) [i|cp #{p} #{destFile}|]
2020-07-07 17:39:58 +00:00
handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
destFile
2021-06-13 08:15:34 +00:00
#endif
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-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