Compare commits

..

5 Commits

Author SHA1 Message Date
2f62067d96 Windows support 2021-06-05 21:01:01 +02:00
9793fc6888 Update stack things 2021-05-30 15:17:04 +02:00
043cab08ae Update www 2021-05-20 19:21:57 +02:00
b7c83780da Update remaining links 2021-05-19 19:22:51 +02:00
cff11135ff Update IRC link 2021-05-19 19:19:12 +02:00
35 changed files with 15284 additions and 16779 deletions

View File

@@ -20,6 +20,7 @@ variables:
variables: variables:
OS: "LINUX" OS: "LINUX"
ARCH: "64" ARCH: "64"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.alpine:64bit: .alpine:64bit:
image: "alpine:3.12" image: "alpine:3.12"
@@ -28,6 +29,7 @@ variables:
variables: variables:
OS: "LINUX" OS: "LINUX"
ARCH: "64" ARCH: "64"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.alpine:32bit: .alpine:32bit:
image: "i386/alpine:3.12" image: "i386/alpine:3.12"
@@ -36,6 +38,7 @@ variables:
variables: variables:
OS: "LINUX" OS: "LINUX"
ARCH: "32" ARCH: "32"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.linux:armv7: .linux:armv7:
image: "arm32v7/fedora" image: "arm32v7/fedora"
@@ -44,6 +47,7 @@ variables:
variables: variables:
OS: "LINUX" OS: "LINUX"
ARCH: "ARM" ARCH: "ARM"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.linux:aarch64: .linux:aarch64:
image: "arm64v8/fedora" image: "arm64v8/fedora"
@@ -52,6 +56,7 @@ variables:
variables: variables:
OS: "LINUX" OS: "LINUX"
ARCH: "ARM64" ARCH: "ARM64"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.darwin: .darwin:
tags: tags:
@@ -59,6 +64,7 @@ variables:
variables: variables:
OS: "DARWIN" OS: "DARWIN"
ARCH: "64" ARCH: "64"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.freebsd: .freebsd:
tags: tags:
@@ -66,22 +72,25 @@ variables:
variables: variables:
OS: "FREEBSD" OS: "FREEBSD"
ARCH: "64" ARCH: "64"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.windows:
tags:
- new-x86_64-windows
variables:
OS: "WINDOWS"
ARCH: "64"
CABAL_DIR: "$CI_PROJECT_DIR/cabal"
.root_cleanup: .root_cleanup:
after_script: after_script:
- BUILD_DIR=$CI_PROJECT_DIR - bash ./.gitlab/after_script.sh
- echo "Cleaning $BUILD_DIR"
- cd $HOME
- test -n "$BUILD_DIR"
- shopt -s extglob
- rm -Rf "$BUILD_DIR"/!(out)
- exit 0
.test_ghcup_version: .test_ghcup_version:
script: script:
- ./.gitlab/script/ghcup_version.sh - bash ./.gitlab/script/ghcup_version.sh
variables: variables:
JSON_VERSION: "0.0.4" JSON_VERSION: "0.0.5"
artifacts: artifacts:
expire_in: 2 week expire_in: 2 week
paths: paths:
@@ -132,9 +141,18 @@ variables:
before_script: before_script:
- ./.gitlab/before_script/freebsd/install_deps.sh - ./.gitlab/before_script/freebsd/install_deps.sh
.test_ghcup_version:windows:
extends:
- .test_ghcup_version
- .windows
- .root_cleanup
before_script:
- set CABAL_DIR="$CI_PROJECT_DIR/cabal"
- bash ./.gitlab/before_script/windows/install_deps.sh
.release_ghcup: .release_ghcup:
script: script:
- ./.gitlab/script/ghcup_release.sh - bash ./.gitlab/script/ghcup_release.sh
artifacts: artifacts:
expire_in: 2 week expire_in: 2 week
paths: paths:
@@ -142,7 +160,7 @@ variables:
only: only:
- tags - tags
variables: variables:
JSON_VERSION: "0.0.4" JSON_VERSION: "0.0.5"
######## stack test ######## ######## stack test ########
@@ -260,6 +278,15 @@ test:freebsd:latest:
when: manual when: manual
needs: [] needs: []
######## windows test ########
test:windows:recommended:
stage: test
extends: .test_ghcup_version:windows
variables:
GHC_VERSION: "8.10.4"
CABAL_VERSION: "3.4.0.0"
needs: []
######## linux release ######## ######## linux release ########
@@ -350,6 +377,21 @@ release:freebsd:
GHC_VERSION: "8.10.4" GHC_VERSION: "8.10.4"
CABAL_VERSION: "3.4.0.0" CABAL_VERSION: "3.4.0.0"
######## windows release ########
release:windows:
stage: release
needs: ["test:windows:recommended"]
extends:
- .windows
- .release_ghcup
- .root_cleanup
before_script:
- bash ./.gitlab/before_script/windows/install_deps.sh
variables:
ARTIFACT: "x86_64-mingw64-ghcup"
GHC_VERSION: "8.10.4"
CABAL_VERSION: "3.4.0.0"
######## hlint ######## ######## hlint ########

15
.gitlab/after_script.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
set -eux
BUILD_DIR=$CI_PROJECT_DIR
echo "Cleaning $BUILD_DIR"
cd $HOME
test -n "$BUILD_DIR"
shopt -s extglob
rm -Rf "$BUILD_DIR"/!(out)
if [ "${OS}" = "WINDOWS" ] ; then
rm -Rf /c/ghcup
fi
exit 0

View File

@@ -0,0 +1,22 @@
#!/bin/sh
set -eux
. "$( cd "$(dirname "$0")" ; pwd -P )/../../ghcup_env"
mkdir -p "${TMPDIR}" "${CABAL_DIR}"
rm -rf /c/ghcup
mkdir -p /c/ghcup
CI_PROJECT_DIR=$(pwd)
curl -o ghcup.exe https://downloads.haskell.org/~ghcup/tmp/x86_64-mingw64-ghcup-5.exe
chmod +x ghcup.exe
./ghcup.exe install ${GHC_VERSION}
./ghcup.exe set ${GHC_VERSION}
./ghcup.exe install-cabal ${CABAL_VERSION}
rm ./ghcup.exe
exit 0

View File

@@ -1,3 +1,9 @@
export GHCUP_INSTALL_BASE_PREFIX="$CI_PROJECT_DIR" if [ "${OS}" = "WINDOWS" ] ; then
export PATH="$CI_PROJECT_DIR/.ghcup/bin:$CI_PROJECT_DIR/.local/bin:$PATH" export GHCUP_INSTALL_BASE_PREFIX="$CI_PROJECT_DIR"
export TMPDIR="$CI_PROJECT_DIR/tmp" export PATH="/c/ghcup/bin:$CI_PROJECT_DIR/.local/bin:$PATH"
export TMPDIR="$CI_PROJECT_DIR/tmp"
else
export GHCUP_INSTALL_BASE_PREFIX="$CI_PROJECT_DIR"
export PATH="$CI_PROJECT_DIR/.ghcup/bin:$CI_PROJECT_DIR/.local/bin:$PATH"
export TMPDIR="$CI_PROJECT_DIR/tmp"
fi

View File

@@ -7,7 +7,7 @@ set -eux
mkdir -p "$CI_PROJECT_DIR"/.local/bin mkdir -p "$CI_PROJECT_DIR"/.local/bin
ecabal() { ecabal() {
cabal --store-dir="$(pwd)"/.store "$@" cabal "$@"
} }
eghcup() { eghcup() {

View File

@@ -7,7 +7,7 @@ set -eux
mkdir -p "$CI_PROJECT_DIR"/.local/bin mkdir -p "$CI_PROJECT_DIR"/.local/bin
ecabal() { ecabal() {
cabal --store-dir="$(pwd)"/.store "$@" cabal "$@"
} }
git describe git describe
@@ -30,6 +30,8 @@ if [ "${OS}" = "LINUX" ] ; then
fi fi
elif [ "${OS}" = "FREEBSD" ] ; then elif [ "${OS}" = "FREEBSD" ] ; then
ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections' --constraint="zlib +bundled-c-zlib" -ftui ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections' --constraint="zlib +bundled-c-zlib" -ftui
elif [ "${OS}" = "WINDOWS" ] ; then
ecabal build -w ghc-${GHC_VERSION} --constraint="zlib +bundled-c-zlib" --constraint="lzma +static"
else else
ecabal build -w ghc-${GHC_VERSION} --constraint="zlib +bundled-c-zlib" --constraint="lzma +static" -ftui ecabal build -w ghc-${GHC_VERSION} --constraint="zlib +bundled-c-zlib" --constraint="lzma +static" -ftui
fi fi

View File

@@ -6,12 +6,18 @@ set -eux
mkdir -p "$CI_PROJECT_DIR"/.local/bin mkdir -p "$CI_PROJECT_DIR"/.local/bin
CI_PROJECT_DIR=$(pwd)
ecabal() { ecabal() {
cabal --store-dir="$CI_PROJECT_DIR"/.store "$@" cabal "$@"
} }
eghcup() { eghcup() {
ghcup -v -c -s file://$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml "$@" if [ "${OS}" = "WINDOWS" ] ; then
ghcup -v -c -s file:/$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml "$@"
else
ghcup -v -c -s file://$CI_PROJECT_DIR/ghcup-${JSON_VERSION}.yaml "$@"
fi
} }
git describe --always git describe --always
@@ -36,6 +42,9 @@ elif [ "${OS}" = "LINUX" ] ; then
ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui
ecabal test -w ghc-${GHC_VERSION} -finternal-downloader -ftui ghcup-test ecabal test -w ghc-${GHC_VERSION} -finternal-downloader -ftui ghcup-test
fi fi
elif [ "${OS}" = "WINDOWS" ] ; then
ecabal build -w ghc-${GHC_VERSION}
ecabal test -w ghc-${GHC_VERSION} ghcup-test
else else
ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui
ecabal test -w ghc-${GHC_VERSION} -finternal-downloader -ftui ghcup-test ecabal test -w ghc-${GHC_VERSION} -finternal-downloader -ftui ghcup-test

View File

@@ -2,6 +2,7 @@
## 0.1.15 -- ????-??-?? ## 0.1.15 -- ????-??-??
* Add windows support wrt [#130](https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/130)
* Add date to GHC bindist names created by ghcup * Add date to GHC bindist names created by ghcup
* Warn when /tmp doesn't have 5GB or more of disk space * Warn when /tmp doesn't have 5GB or more of disk space
* Allow to compile GHC from git repo wrt [#126](https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/126) * Allow to compile GHC from git repo wrt [#126](https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/126)

View File

@@ -123,8 +123,8 @@ main = do
where where
valAndExit f contents = do valAndExit f contents = do
(GHCupInfo _ av) <- case Y.decodeEither' contents of (GHCupInfo _ av gt) <- case Y.decodeEither' contents of
Right r -> pure r Right r -> pure r
Left e -> die (color Red $ show e) Left e -> die (color Red $ show e)
myLoggerT (LoggerConfig True (B.hPut stdout) (\_ -> pure ())) (f av) myLoggerT (LoggerConfig True (B.hPut stdout) (\_ -> pure ())) (f av gt)
>>= exitWith >>= exitWith

View File

@@ -11,6 +11,7 @@ module Validate where
import GHCup import GHCup
import GHCup.Download import GHCup.Download
import GHCup.Errors import GHCup.Errors
import GHCup.Platform
import GHCup.Types import GHCup.Types
import GHCup.Types.Optics import GHCup.Types.Optics
import GHCup.Utils import GHCup.Utils
@@ -22,6 +23,7 @@ import qualified Codec.Archive.Tar as Tar
#else #else
import Codec.Archive import Codec.Archive
#endif #endif
import Control.Applicative
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
@@ -66,8 +68,9 @@ addError = do
validate :: (Monad m, MonadLogger m, MonadThrow m, MonadIO m, MonadUnliftIO m) validate :: (Monad m, MonadLogger m, MonadThrow m, MonadIO m, MonadUnliftIO m)
=> GHCupDownloads => GHCupDownloads
-> M.Map GlobalTool DownloadInfo
-> m ExitCode -> m ExitCode
validate dls = do validate dls _ = do
ref <- liftIO $ newIORef 0 ref <- liftIO $ newIORef 0
-- verify binary downloads -- -- verify binary downloads --
@@ -191,22 +194,24 @@ validateTarballs :: ( Monad m
, MonadIO m , MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
, MonadMask m , MonadMask m
, Alternative m
, MonadFail m
) )
=> TarballFilter => TarballFilter
-> GHCupDownloads -> GHCupDownloads
-> M.Map GlobalTool DownloadInfo
-> m ExitCode -> m ExitCode
validateTarballs (TarballFilter tool versionRegex) dls = do validateTarballs (TarballFilter tool versionRegex) dls gt = do
ref <- liftIO $ newIORef 0 ref <- liftIO $ newIORef 0
flip runReaderT ref $ do flip runReaderT ref $ do
-- download/verify all tarballs -- download/verify all tarballs
let dlis = nubOrd $ dls ^.. each let dlis = nubOrd $ dls ^.. each %& indices (maybe (const True) (==) tool) %> each %& indices (matchTest versionRegex . T.unpack . prettyVer) % (viSourceDL % _Just `summing` viArch % each % each % each)
%& indices (maybe (const True) (==) tool) %> each
%& indices (matchTest versionRegex . T.unpack . prettyVer)
% (viSourceDL % _Just `summing` viArch % each % each % each)
when (null dlis) $ $(logError) [i|no tarballs selected by filter|] *> addError when (null dlis) $ $(logError) [i|no tarballs selected by filter|] *> addError
forM_ dlis downloadAll let gdlis = nubOrd $ gt ^.. each
forM_ (dlis ++ gdlis) downloadAll
-- exit -- exit
e <- liftIO $ readIORef ref e <- liftIO $ readIORef ref
@@ -223,11 +228,21 @@ validateTarballs (TarballFilter tool versionRegex) dls = do
} }
downloadAll dli = do downloadAll dli = do
dirs <- liftIO getDirs dirs <- liftIO getDirs
let settings = AppState (Settings True False Never Curl False GHCupURL) dirs defaultKeyBindings
pfreq <- (
runLogger . runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] . liftE $ platformRequest
) >>= \case
VRight r -> pure r
VLeft e -> do
lift $ runLogger
($(logError) $ T.pack $ prettyShow e)
liftIO $ exitWith (ExitFailure 2)
let appstate = AppState (Settings True False Never Curl False GHCupURL) dirs defaultKeyBindings (GHCupInfo mempty mempty mempty) pfreq
r <- r <-
runLogger runLogger
. flip runReaderT settings . flip runReaderT appstate
. runResourceT . runResourceT
. runE @'[DigestError . runE @'[DigestError
, DownloadFailed , DownloadFailed
@@ -242,12 +257,11 @@ validateTarballs (TarballFilter tool versionRegex) dls = do
case tool of case tool of
Just GHCup -> do Just GHCup -> do
let fn = "ghcup" let fn = "ghcup"
dir <- liftIO ghcupCacheDir p <- liftE $ download (settings appstate) dli (cacheDir dirs) (Just fn)
p <- liftE $ download dli dir (Just fn) liftE $ checkDigest (settings appstate) dli p
liftE $ checkDigest dli p
pure Nothing pure Nothing
_ -> do _ -> do
p <- liftE $ downloadCached dli Nothing p <- liftE $ downloadCached (settings appstate) dirs dli Nothing
fmap (Just . head . splitDirectories . head) fmap (Just . head . splitDirectories . head)
. liftE . liftE
. getArchiveFiles . getArchiveFiles

View File

@@ -6,6 +6,7 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE RankNTypes #-}
module BrickMain where module BrickMain where
@@ -32,6 +33,7 @@ import Codec.Archive
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad.Logger import Control.Monad.Logger
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.Trans.Except
import Control.Monad.Trans.Resource import Control.Monad.Trans.Resource
import Data.Bool import Data.Bool
import Data.Functor import Data.Functor
@@ -58,11 +60,12 @@ import qualified Graphics.Vty as Vty
import qualified Data.Vector as V import qualified Data.Vector as V
hiddenTools :: [Tool]
hiddenTools = [Stack]
data BrickData = BrickData data BrickData = BrickData
{ lr :: [ListResult] { lr :: [ListResult]
, dls :: GHCupDownloads
, pfreq :: PlatformRequest
} }
deriving Show deriving Show
@@ -97,7 +100,7 @@ keyHandlers KeyBindings {..} =
[ (bQuit, const "Quit" , halt) [ (bQuit, const "Quit" , halt)
, (bInstall, const "Install" , withIOAction install') , (bInstall, const "Install" , withIOAction install')
, (bUninstall, const "Uninstall", withIOAction del') , (bUninstall, const "Uninstall", withIOAction del')
, (bSet, const "Set" , withIOAction set') , (bSet, const "Set" , withIOAction ((liftIO .) . set'))
, (bChangelog, const "ChangeLog", withIOAction changelog') , (bChangelog, const "ChangeLog", withIOAction changelog')
, ( bShowAllVersions , ( bShowAllVersions
, \BrickSettings {..} -> , \BrickSettings {..} ->
@@ -149,12 +152,7 @@ ui dimAttrs BrickState{ appSettings = as@BrickSettings{}, ..}
<+> minHSize 15 (str "Version") <+> minHSize 15 (str "Version")
<+> padLeft (Pad 1) (minHSize 25 $ str "Tags") <+> padLeft (Pad 1) (minHSize 25 $ str "Tags")
<+> padLeft (Pad 5) (str "Notes") <+> padLeft (Pad 5) (str "Notes")
renderList' = withDefAttr listAttr . drawListElements renderItem True . filterStack renderList' = withDefAttr listAttr . drawListElements renderItem True
filterStack appState'
| showAllTools as = appState'
| let v = clr appState'
nv = V.filter (\ListResult{..} -> lTool /= Stack) v
, otherwise = BrickInternalState { clr = nv, ix = ix appState' }
renderItem _ b listResult@ListResult{..} = renderItem _ b listResult@ListResult{..} =
let marks = if let marks = if
| lSet -> (withAttr "set" $ str "✔✔") | lSet -> (withAttr "set" $ str "✔✔")
@@ -329,21 +327,25 @@ moveCursor steps ais@BrickInternalState{..} direction =
-- | Suspend the current UI and run an IO action in terminal. If the -- | Suspend the current UI and run an IO action in terminal. If the
-- IO action returns a Left value, then it's thrown as userError. -- IO action returns a Left value, then it's thrown as userError.
withIOAction :: (BrickState -> (Int, ListResult) -> IO (Either String a)) withIOAction :: (BrickState
-> (Int, ListResult)
-> ReaderT AppState IO (Either String a))
-> BrickState -> BrickState
-> EventM n (Next BrickState) -> EventM n (Next BrickState)
withIOAction action as = case listSelectedElement' (appState as) of withIOAction action as = case listSelectedElement' (appState as) of
Nothing -> continue as Nothing -> continue as
Just (ix, e) -> suspendAndResume $ do Just (ix, e) -> do
action as (ix, e) >>= \case suspendAndResume $ do
Left err -> putStrLn ("Error: " <> err) settings <- readIORef settings'
Right _ -> putStrLn "Success" flip runReaderT settings $ action as (ix, e) >>= \case
getAppData Nothing (pfreq . appData $ as) >>= \case Left err -> liftIO $ putStrLn ("Error: " <> err)
Right data' -> do Right _ -> liftIO $ putStrLn "Success"
putStrLn "Press enter to continue" getAppData Nothing >>= \case
_ <- getLine Right data' -> do
pure (updateList data' as) putStrLn "Press enter to continue"
Left err -> throwIO $ userError err _ <- getLine
pure (updateList data' as)
Left err -> throwIO $ userError err
-- | Update app data and list internal state based on new evidence. -- | Update app data and list internal state based on new evidence.
@@ -364,7 +366,9 @@ constructList :: BrickData
-> Maybe BrickInternalState -> Maybe BrickInternalState
-> BrickInternalState -> BrickInternalState
constructList appD appSettings = constructList appD appSettings =
replaceLR (filterVisible (showAllVersions appSettings)) (lr appD) replaceLR (filterVisible (showAllVersions appSettings)
(showAllTools appSettings))
(lr appD)
listSelectedElement' :: BrickInternalState -> Maybe (Int, ListResult) listSelectedElement' :: BrickInternalState -> Maybe (Int, ListResult)
listSelectedElement' BrickInternalState{..} = fmap (ix, ) $ clr !? ix listSelectedElement' BrickInternalState{..} = fmap (ix, ) $ clr !? ix
@@ -397,21 +401,32 @@ replaceLR filterF lr s =
lTool e1 == lTool e2 && lVer e1 == lVer e2 && lCross e1 == lCross e2 lTool e1 == lTool e2 && lVer e1 == lVer e2 && lCross e1 == lCross e2
filterVisible :: Bool -> ListResult -> Bool filterVisible :: Bool -> Bool -> ListResult -> Bool
filterVisible showAllVersions e | lInstalled e = True filterVisible v t e | lInstalled e = True
| showAllVersions = True | v
| otherwise = not (elem Old (lTag e)) , not t
, not (elem (lTool e) hiddenTools) = True
| not v
, t
, not (elem Old (lTag e)) = True
| v
, t = True
| otherwise = not (elem Old (lTag e)) &&
not (elem (lTool e) hiddenTools)
install' :: BrickState -> (Int, ListResult) -> IO (Either String ()) install' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m)
install' BrickState { appData = BrickData {..} } (_, ListResult {..}) = do => BrickState
settings <- readIORef settings' -> (Int, ListResult)
l <- readIORef logger' -> m (Either String ())
install' _ (_, ListResult {..}) = do
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
l <- liftIO $ readIORef logger'
let runLogger = myLoggerT l let runLogger = myLoggerT l
let run = let run =
runLogger runLogger
. flip runReaderT settings
. runResourceT . runResourceT
. runE . runE
@'[ AlreadyInstalled @'[ AlreadyInstalled
@@ -435,24 +450,24 @@ install' BrickState { appData = BrickData {..} } (_, ListResult {..}) = do
case lTool of case lTool of
GHC -> do GHC -> do
let vi = getVersionInfo lVer GHC dls let vi = getVersionInfo lVer GHC dls
liftE $ installGHCBin dls lVer pfreq $> vi liftE $ installGHCBin lVer $> vi
Cabal -> do Cabal -> do
let vi = getVersionInfo lVer Cabal dls let vi = getVersionInfo lVer Cabal dls
liftE $ installCabalBin dls lVer pfreq $> vi liftE $ installCabalBin lVer $> vi
GHCup -> do GHCup -> do
let vi = snd <$> getLatest dls GHCup let vi = snd <$> getLatest dls GHCup
liftE $ upgradeGHCup dls Nothing False pfreq $> vi liftE $ upgradeGHCup Nothing False $> vi
HLS -> do HLS -> do
let vi = getVersionInfo lVer HLS dls let vi = getVersionInfo lVer HLS dls
liftE $ installHLSBin dls lVer pfreq $> vi liftE $ installHLSBin lVer $> vi
Stack -> do Stack -> do
let vi = getVersionInfo lVer Stack dls let vi = getVersionInfo lVer Stack dls
liftE $ installStackBin dls lVer pfreq $> vi liftE $ installStackBin lVer $> vi
) )
>>= \case >>= \case
VRight vi -> do VRight vi -> do
forM_ (_viPostInstall =<< vi) $ \msg -> forM_ (_viPostInstall =<< vi) $ \msg ->
runLogger $ $(logInfo) msg myLoggerT l $ $(logInfo) msg
pure $ Right () pure $ Right ()
VLeft (V (AlreadyInstalled _ _)) -> pure $ Right () VLeft (V (AlreadyInstalled _ _)) -> pure $ Right ()
VLeft (V NoUpdate) -> pure $ Right () VLeft (V NoUpdate) -> pure $ Right ()
@@ -484,13 +499,16 @@ set' _ (_, ListResult {..}) = do
VLeft e -> pure $ Left (prettyShow e) VLeft e -> pure $ Left (prettyShow e)
del' :: BrickState -> (Int, ListResult) -> IO (Either String ()) del' :: (MonadReader AppState m, MonadIO m, MonadFail m, MonadMask m, MonadUnliftIO m)
del' BrickState { appData = BrickData {..} } (_, ListResult {..}) = do => BrickState
settings <- readIORef settings' -> (Int, ListResult)
l <- readIORef logger' -> m (Either String ())
let runLogger = myLoggerT l del' _ (_, ListResult {..}) = do
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
let run = runLogger . flip runReaderT settings . runE @'[NotInstalled] l <- liftIO $ readIORef logger'
let runLogger = myLoggerT l
let run = myLoggerT l . runE @'[NotInstalled]
run (do run (do
let vi = getVersionInfo lVer lTool dls let vi = getVersionInfo lVer lTool dls
@@ -509,8 +527,12 @@ del' BrickState { appData = BrickData {..} } (_, ListResult {..}) = do
VLeft e -> pure $ Left (prettyShow e) VLeft e -> pure $ Left (prettyShow e)
changelog' :: BrickState -> (Int, ListResult) -> IO (Either String ()) changelog' :: (MonadReader AppState m, MonadIO m)
changelog' BrickState { appData = BrickData {..} } (_, ListResult {..}) = do => BrickState
-> (Int, ListResult)
-> m (Either String ())
changelog' _ (_, ListResult {..}) = do
AppState { pfreq, ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
case getChangeLog dls lTool (Left lVer) of case getChangeLog dls lTool (Left lVer) of
Nothing -> pure $ Left Nothing -> pure $ Left
[i|Could not find ChangeLog for #{lTool}, version #{prettyVer lVer}|] [i|Could not find ChangeLog for #{lTool}, version #{prettyVer lVer}|]
@@ -539,6 +561,8 @@ settings' = unsafePerformIO $ do
}) })
dirs dirs
defaultKeyBindings defaultKeyBindings
(GHCupInfo mempty mempty mempty)
(PlatformRequest A_64 Darwin Nothing)
@@ -554,10 +578,9 @@ logger' = unsafePerformIO
brickMain :: AppState brickMain :: AppState
-> LoggerConfig -> LoggerConfig
-> GHCupDownloads -> GHCupInfo
-> PlatformRequest
-> IO () -> IO ()
brickMain s l av pfreq' = do brickMain s l gi = do
writeIORef settings' s writeIORef settings' s
-- logger interpreter -- logger interpreter
writeIORef logger' l writeIORef logger' l
@@ -565,7 +588,7 @@ brickMain s l av pfreq' = do
no_color <- isJust <$> lookupEnv "NO_COLOR" no_color <- isJust <$> lookupEnv "NO_COLOR"
eAppData <- getAppData (Just av) pfreq' eAppData <- getAppData (Just gi)
case eAppData of case eAppData of
Right ad -> Right ad ->
defaultMain defaultMain
@@ -586,8 +609,8 @@ defaultAppSettings :: BrickSettings
defaultAppSettings = BrickSettings { showAllVersions = False, showAllTools = False } defaultAppSettings = BrickSettings { showAllVersions = False, showAllTools = False }
getDownloads' :: IO (Either String GHCupDownloads) getGHCupInfo :: IO (Either String GHCupInfo)
getDownloads' = do getGHCupInfo = do
settings <- readIORef settings' settings <- readIORef settings'
l <- readIORef logger' l <- readIORef logger'
let runLogger = myLoggerT l let runLogger = myLoggerT l
@@ -596,29 +619,25 @@ getDownloads' = do
runLogger runLogger
. flip runReaderT settings . flip runReaderT settings
. runE @'[JSONError , DownloadFailed , FileDoesNotExistError] . runE @'[JSONError , DownloadFailed , FileDoesNotExistError]
$ fmap _ghcupDownloads
$ liftE $ liftE
$ getDownloadsF (urlSource . GT.settings $ settings) $ getDownloadsF (GT.settings settings) (GT.dirs settings)
case r of case r of
VRight a -> pure $ Right a VRight a -> pure $ Right a
VLeft e -> pure $ Left (prettyShow e) VLeft e -> pure $ Left (prettyShow e)
getAppData :: Maybe GHCupDownloads getAppData :: Maybe GHCupInfo
-> PlatformRequest
-> IO (Either String BrickData) -> IO (Either String BrickData)
getAppData mg pfreq' = do getAppData mgi = runExceptT $ do
settings <- readIORef settings' l <- liftIO $ readIORef logger'
l <- readIORef logger'
let runLogger = myLoggerT l let runLogger = myLoggerT l
r <- maybe getDownloads' (pure . Right) mg r <- ExceptT $ maybe getGHCupInfo (pure . Right) mgi
liftIO $ modifyIORef settings' (\s -> s { ghcupInfo = r })
settings <- liftIO $ readIORef settings'
runLogger . flip runReaderT settings $ do runLogger . flip runReaderT settings $ do
case r of lV <- listVersions Nothing Nothing
Right dls -> do pure $ BrickData (reverse lV)
lV <- listVersions dls Nothing Nothing pfreq'
pure $ Right $ BrickData (reverse lV) dls pfreq'
Left e -> pure $ Left [i|#{e}|]

View File

@@ -79,8 +79,6 @@ import qualified Text.Megaparsec.Char as MPC
data Options = Options data Options = Options
{ {
-- global options -- global options
@@ -807,53 +805,47 @@ versionArgument criteria tool = argument (eitherReader tVersionEither) (metavar
tagCompleter :: Tool -> [String] -> Completer tagCompleter :: Tool -> [String] -> Completer
tagCompleter tool add = listIOCompleter $ do tagCompleter tool add = listIOCompleter $ do
dirs' <- liftIO getDirs
let loggerConfig = LoggerConfig let loggerConfig = LoggerConfig
{ lcPrintDebug = False { lcPrintDebug = False
, colorOutter = mempty , colorOutter = mempty
, rawOutter = mempty , rawOutter = mempty
} }
let runLogger = myLoggerT loggerConfig
runLogger = myLoggerT loggerConfig mGhcUpInfo <- runLogger . runE $ readFromCache dirs'
dirs <- getDirs
let simpleSettings = Settings False False Never Curl False GHCupURL
simpleAppState = AppState simpleSettings dirs defaultKeyBindings
runEnv = runLogger . flip runReaderT simpleAppState
mGhcUpInfo <- runEnv . runE $ readFromCache
case mGhcUpInfo of case mGhcUpInfo of
VRight dls -> do VRight ghcupInfo -> do
let allTags = filter (\t -> t /= Old) let allTags = filter (\t -> t /= Old)
$ join $ join
$ M.elems $ M.elems
$ availableToolVersions (_ghcupDownloads dls) tool $ availableToolVersions (_ghcupDownloads ghcupInfo) tool
pure $ nub $ (add ++) $ fmap tagToString allTags pure $ nub $ (add ++) $ fmap tagToString allTags
VLeft _ -> pure (nub $ ["recommended", "latest"] ++ add) VLeft _ -> pure (nub $ ["recommended", "latest"] ++ add)
versionCompleter :: Maybe ListCriteria -> Tool -> Completer versionCompleter :: Maybe ListCriteria -> Tool -> Completer
versionCompleter criteria tool = listIOCompleter $ do versionCompleter criteria tool = listIOCompleter $ do
dirs' <- liftIO getDirs
let loggerConfig = LoggerConfig let loggerConfig = LoggerConfig
{ lcPrintDebug = False { lcPrintDebug = False
, colorOutter = mempty , colorOutter = mempty
, rawOutter = mempty , rawOutter = mempty
} }
let runLogger = myLoggerT loggerConfig
runLogger = myLoggerT loggerConfig mGhcUpInfo <- runLogger . runE $ readFromCache dirs'
mpFreq <- runLogger . runE $ platformRequest mpFreq <- runLogger . runE $ platformRequest
forFold mpFreq $ \pfreq ->
forFold mGhcUpInfo $ \ghcupInfo -> do
let appState = AppState
(Settings True False Never Curl False GHCupURL)
dirs'
defaultKeyBindings
ghcupInfo
pfreq
forFold mpFreq $ \pfreq -> do runEnv = runLogger . flip runReaderT appState
dirs <- getDirs
let simpleSettings = Settings False False Never Curl False GHCupURL
simpleAppState = AppState simpleSettings dirs defaultKeyBindings
runEnv = runLogger . flip runReaderT simpleAppState
mGhcUpInfo <- runEnv . runE $ readFromCache installedVersions <- runEnv $ listVersions (Just tool) criteria
forFold mGhcUpInfo $ \(GHCupInfo _ dls) -> do
installedVersions <- runEnv $ listVersions dls (Just tool) criteria pfreq
return $ T.unpack . prettyVer . lVer <$> installedVersions return $ T.unpack . prettyVer . lVer <$> installedVersions
@@ -974,9 +966,8 @@ bindistParser :: String -> Either String URI
bindistParser = first show . parseURI strictURIParserOptions . UTF8.fromString bindistParser = first show . parseURI strictURIParserOptions . UTF8.fromString
toSettings :: Options -> IO AppState toSettings :: Options -> IO (Settings, KeyBindings)
toSettings options = do toSettings options = do
dirs <- getDirs
userConf <- runE @'[ JSONError ] ghcupConfigFile >>= \case userConf <- runE @'[ JSONError ] ghcupConfigFile >>= \case
VRight r -> pure r VRight r -> pure r
VLeft (V (JSONDecodeError e)) -> do VLeft (V (JSONDecodeError e)) -> do
@@ -984,10 +975,10 @@ toSettings options = do
pure defaultUserSettings pure defaultUserSettings
_ -> do _ -> do
die "Unexpected error!" die "Unexpected error!"
pure $ mergeConf options dirs userConf pure $ mergeConf options userConf
where where
mergeConf :: Options -> Dirs -> UserSettings -> AppState mergeConf :: Options -> UserSettings -> (Settings, KeyBindings)
mergeConf Options{..} dirs UserSettings{..} = mergeConf Options{..} UserSettings{..} =
let cache = fromMaybe (fromMaybe False uCache) optCache let cache = fromMaybe (fromMaybe False uCache) optCache
noVerify = fromMaybe (fromMaybe False uNoVerify) optNoVerify noVerify = fromMaybe (fromMaybe False uNoVerify) optNoVerify
verbose = fromMaybe (fromMaybe False uVerbose) optVerbose verbose = fromMaybe (fromMaybe False uVerbose) optVerbose
@@ -995,7 +986,7 @@ toSettings options = do
downloader = fromMaybe (fromMaybe defaultDownloader uDownloader) optsDownloader downloader = fromMaybe (fromMaybe defaultDownloader uDownloader) optsDownloader
keyBindings = maybe defaultKeyBindings mergeKeys uKeyBindings keyBindings = maybe defaultKeyBindings mergeKeys uKeyBindings
urlSource = maybe (fromMaybe GHCupURL uUrlSource) OwnSource optUrlSource urlSource = maybe (fromMaybe GHCupURL uUrlSource) OwnSource optUrlSource
in AppState (Settings {..}) dirs keyBindings in (Settings {..}, keyBindings)
#if defined(INTERNAL_DOWNLOADER) #if defined(INTERNAL_DOWNLOADER)
defaultDownloader = Internal defaultDownloader = Internal
#else #else
@@ -1038,7 +1029,10 @@ upgradeOptsP =
describe_result :: String describe_result :: String
describe_result = $( LitE . StringL <$> describe_result = $( LitE . StringL <$>
runIO (do runIO (do
CapturedProcess{..} <- executeOut "git" ["describe"] Nothing CapturedProcess{..} <- do
dirs <- liftIO getDirs
let settings = AppState (Settings True False Never Curl False GHCupURL) dirs defaultKeyBindings
flip runReaderT settings $ executeOut "git" ["describe"] Nothing
case _exitCode of case _exitCode of
ExitSuccess -> pure . T.unpack . decUTF8Safe' $ _stdOut ExitSuccess -> pure . T.unpack . decUTF8Safe' $ _stdOut
ExitFailure _ -> pure numericVer ExitFailure _ -> pure numericVer
@@ -1048,6 +1042,11 @@ describe_result = $( LitE . StringL <$>
main :: IO () main :: IO ()
main = do main = do
-- https://gitlab.haskell.org/ghc/ghc/issues/8118
setLocaleEncoding utf8
void enableAnsiSupport
let versionHelp = infoOption let versionHelp = infoOption
( ("The GHCup Haskell installer, version " <>) ( ("The GHCup Haskell installer, version " <>)
(head . lines $ describe_result) (head . lines $ describe_result)
@@ -1084,28 +1083,76 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
(footerDoc (Just $ text main_footer)) (footerDoc (Just $ text main_footer))
) )
>>= \opt@Options {..} -> do >>= \opt@Options {..} -> do
appstate@AppState{dirs = Dirs{..}, ..} <- toSettings opt dirs <- getDirs
(settings, keybindings) <- toSettings opt
-- create ~/.ghcup dir -- create ~/.ghcup dir
createDirRecursive' baseDir createDirRecursive' (baseDir dirs)
-- logger interpreter -- logger interpreter
logfile <- flip runReaderT appstate $ initGHCupFileLogging logfile <- initGHCupFileLogging (logsDir dirs)
let loggerConfig = LoggerConfig let loggerConfig = LoggerConfig
{ lcPrintDebug = verbose settings { lcPrintDebug = verbose settings
, colorOutter = B.hPut stderr , colorOutter = B.hPut stderr
, rawOutter = B.appendFile logfile , rawOutter = B.appendFile logfile
} }
let runLogger = myLoggerT loggerConfig let runLogger = myLoggerT loggerConfig
let siletRunLogger = myLoggerT loggerConfig { colorOutter = \_ -> pure () }
pfreq <- (
runLogger . runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] . liftE $ platformRequest
) >>= \case
VRight r -> pure r
VLeft e -> do
runLogger
($(logError) $ T.pack $ prettyShow e)
exitWith (ExitFailure 2)
----------------------------------------
-- Getting download and platform info --
----------------------------------------
ghcupInfo <-
( runLogger
. runE @'[JSONError , DownloadFailed, FileDoesNotExistError]
$ liftE
$ getDownloadsF settings dirs
)
>>= \case
VRight r -> pure r
VLeft e -> do
runLogger
($(logError) $ T.pack $ prettyShow e)
exitWith (ExitFailure 2)
let appstate@AppState{dirs = Dirs{..}
, ghcupInfo = GHCupInfo { _ghcupDownloads = dls, .. }
} = AppState settings dirs keybindings ghcupInfo pfreq
case optCommand of
Upgrade _ _ -> pure ()
_ -> runLogger $ flip runReaderT appstate $ checkForUpdates
-- ensure global tools
(siletRunLogger $ flip runReaderT appstate $ runE ensureGlobalTools) >>= \case
VRight _ -> pure ()
VLeft e -> do
runLogger
($(logError) $ T.pack $ prettyShow e)
exitWith (ExitFailure 30)
------------------------- -------------------------
-- Effect interpreters -- -- Effect interpreters --
------------------------- -------------------------
let runInstTool' appstate' = let runInstTool' appstate' mInstPlatform =
runLogger runLogger
. flip runReaderT appstate' . flip runReaderT (maybe appstate' (\x -> appstate'{ pfreq = x }) mInstPlatform)
. runResourceT . runResourceT
. runE . runE
@'[ AlreadyInstalled @'[ AlreadyInstalled
@@ -1208,57 +1255,22 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
] ]
----------------------------------------
-- Getting download and platform info --
----------------------------------------
pfreq <- (
runLogger . runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] . liftE $ platformRequest
) >>= \case
VRight r -> pure r
VLeft e -> do
runLogger
($(logError) $ T.pack $ prettyShow e)
exitWith (ExitFailure 2)
(GHCupInfo treq dls) <-
( runLogger
. flip runReaderT appstate
. runE @'[JSONError , DownloadFailed, FileDoesNotExistError]
$ liftE
$ getDownloadsF (urlSource settings)
)
>>= \case
VRight r -> pure r
VLeft e -> do
runLogger
($(logError) $ T.pack $ prettyShow e)
exitWith (ExitFailure 2)
case optCommand of
Upgrade _ _ -> pure ()
_ -> runLogger $ flip runReaderT appstate $ checkForUpdates dls pfreq
----------------------- -----------------------
-- Command functions -- -- Command functions --
----------------------- -----------------------
let installGHC InstallOptions{..} = let installGHC InstallOptions{..} =
(case instBindist of (case instBindist of
Nothing -> runInstTool $ do Nothing -> runInstTool instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer GHC (v, vi) <- liftE $ fromVersion instVer GHC
liftE $ installGHCBin dls (_tvVersion v) (fromMaybe pfreq instPlatform) liftE $ installGHCBin (_tvVersion v)
when instSet $ void $ liftE $ setGHC v SetGHCOnly when instSet $ void $ liftE $ setGHC v SetGHCOnly
pure vi pure vi
Just uri -> runInstTool' appstate{ settings = settings {noVerify = True}} $ do Just uri -> runInstTool' appstate{ settings = settings {noVerify = True}} instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer GHC (v, vi) <- liftE $ fromVersion instVer GHC
liftE $ installGHCBindist liftE $ installGHCBindist
(DownloadInfo uri (Just $ RegexDir "ghc-.*") "") (DownloadInfo uri (Just $ RegexDir "ghc-.*") "")
(_tvVersion v) (_tvVersion v)
(fromMaybe pfreq instPlatform)
when instSet $ void $ liftE $ setGHC v SetGHCOnly when instSet $ void $ liftE $ setGHC v SetGHCOnly
pure vi pure vi
) )
@@ -1274,8 +1286,8 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
pure ExitSuccess pure ExitSuccess
VLeft err@(V (BuildFailed tmpdir _)) -> do VLeft err@(V (BuildFailed tmpdir _)) -> do
case keepDirs settings of case keepDirs settings of
Never -> runLogger ($(logError) $ T.pack $ prettyShow err) Never -> myLoggerT loggerConfig $ ($(logError) $ T.pack $ prettyShow err)
_ -> runLogger ($(logError) [i|#{prettyShow err} _ -> myLoggerT loggerConfig $ ($(logError) [i|#{prettyShow err}
Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues. Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues.
Make sure to clean up #{tmpdir} afterwards.|]) Make sure to clean up #{tmpdir} afterwards.|])
pure $ ExitFailure 3 pure $ ExitFailure 3
@@ -1288,16 +1300,15 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let installCabal InstallOptions{..} = let installCabal InstallOptions{..} =
(case instBindist of (case instBindist of
Nothing -> runInstTool $ do Nothing -> runInstTool instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer Cabal (v, vi) <- liftE $ fromVersion instVer Cabal
liftE $ installCabalBin dls (_tvVersion v) (fromMaybe pfreq instPlatform) liftE $ installCabalBin (_tvVersion v)
pure vi pure vi
Just uri -> runInstTool' appstate{ settings = settings { noVerify = True}} $ do Just uri -> runInstTool' appstate{ settings = settings { noVerify = True}} instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer Cabal (v, vi) <- liftE $ fromVersion instVer Cabal
liftE $ installCabalBindist liftE $ installCabalBindist
(DownloadInfo uri Nothing "") (DownloadInfo uri Nothing "")
(_tvVersion v) (_tvVersion v)
(fromMaybe pfreq instPlatform)
pure vi pure vi
) )
>>= \case >>= \case
@@ -1318,16 +1329,15 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let installHLS InstallOptions{..} = let installHLS InstallOptions{..} =
(case instBindist of (case instBindist of
Nothing -> runInstTool $ do Nothing -> runInstTool instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer HLS (v, vi) <- liftE $ fromVersion instVer HLS
liftE $ installHLSBin dls (_tvVersion v) (fromMaybe pfreq instPlatform) liftE $ installHLSBin (_tvVersion v)
pure vi pure vi
Just uri -> runInstTool' appstate{ settings = settings { noVerify = True}} $ do Just uri -> runInstTool' appstate{ settings = settings { noVerify = True}} instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer HLS (v, vi) <- liftE $ fromVersion instVer HLS
liftE $ installHLSBindist liftE $ installHLSBindist
(DownloadInfo uri Nothing "") (DownloadInfo uri Nothing "")
(_tvVersion v) (_tvVersion v)
(fromMaybe pfreq instPlatform)
pure vi pure vi
) )
>>= \case >>= \case
@@ -1348,16 +1358,15 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let installStack InstallOptions{..} = let installStack InstallOptions{..} =
(case instBindist of (case instBindist of
Nothing -> runInstTool $ do Nothing -> runInstTool instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer Stack (v, vi) <- liftE $ fromVersion instVer Stack
liftE $ installStackBin dls (_tvVersion v) (fromMaybe pfreq instPlatform) liftE $ installStackBin (_tvVersion v)
pure vi pure vi
Just uri -> runInstTool' appstate{ settings = settings { noVerify = True}} $ do Just uri -> runInstTool' appstate{ settings = settings { noVerify = True}} instPlatform $ do
(v, vi) <- liftE $ fromVersion dls instVer Stack (v, vi) <- liftE $ fromVersion instVer Stack
liftE $ installStackBindist liftE $ installStackBindist
(DownloadInfo uri Nothing "") (DownloadInfo uri Nothing "")
(_tvVersion v) (_tvVersion v)
(fromMaybe pfreq instPlatform)
pure vi pure vi
) )
>>= \case >>= \case
@@ -1379,7 +1388,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let setGHC' SetOptions{..} = let setGHC' SetOptions{..} =
runSetGHC (do runSetGHC (do
v <- liftE $ fst <$> fromVersion' dls sToolVer GHC v <- liftE $ fst <$> fromVersion' sToolVer GHC
liftE $ setGHC v SetGHCOnly liftE $ setGHC v SetGHCOnly
) )
>>= \case >>= \case
@@ -1394,7 +1403,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let setCabal' SetOptions{..} = let setCabal' SetOptions{..} =
runSetCabal (do runSetCabal (do
v <- liftE $ fst <$> fromVersion' dls sToolVer Cabal v <- liftE $ fst <$> fromVersion' sToolVer Cabal
liftE $ setCabal (_tvVersion v) liftE $ setCabal (_tvVersion v)
pure v pure v
) )
@@ -1410,7 +1419,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let setHLS' SetOptions{..} = let setHLS' SetOptions{..} =
runSetHLS (do runSetHLS (do
v <- liftE $ fst <$> fromVersion' dls sToolVer HLS v <- liftE $ fst <$> fromVersion' sToolVer HLS
liftE $ setHLS (_tvVersion v) liftE $ setHLS (_tvVersion v)
pure v pure v
) )
@@ -1426,7 +1435,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let setStack' SetOptions{..} = let setStack' SetOptions{..} =
runSetCabal (do runSetCabal (do
v <- liftE $ fst <$> fromVersion' dls sToolVer Stack v <- liftE $ fst <$> fromVersion' sToolVer Stack
liftE $ setStack (_tvVersion v) liftE $ setStack (_tvVersion v)
pure v pure v
) )
@@ -1502,7 +1511,8 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
res <- case optCommand of res <- case optCommand of
#if defined(BRICK) #if defined(BRICK)
Interactive -> liftIO $ brickMain appstate loggerConfig dls pfreq >> pure ExitSuccess Interactive -> do
liftIO $ brickMain appstate loggerConfig ghcupInfo >> pure ExitSuccess
#endif #endif
Install (Right iopts) -> do Install (Right iopts) -> do
runLogger ($(logWarn) [i|This is an old-style command for installing GHC. Use 'ghcup install ghc' instead.|]) runLogger ($(logWarn) [i|This is an old-style command for installing GHC. Use 'ghcup install ghc' instead.|])
@@ -1525,7 +1535,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
List ListOptions {..} -> List ListOptions {..} ->
runListGHC (do runListGHC (do
l <- listVersions dls loTool lCriteria pfreq l <- listVersions loTool lCriteria
liftIO $ printListResult lRawFormat l liftIO $ printListResult lRawFormat l
pure ExitSuccess pure ExitSuccess
) )
@@ -1559,14 +1569,13 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
"...waiting for 5 seconds, you can still abort..." "...waiting for 5 seconds, you can still abort..."
liftIO $ threadDelay 5000000 -- for compilation, give the user a sec to intervene liftIO $ threadDelay 5000000 -- for compilation, give the user a sec to intervene
Right _ -> pure () Right _ -> pure ()
targetVer <- liftE $ compileGHC dls targetVer <- liftE $ compileGHC
(first (GHCTargetVersion crossTarget) targetGhc) (first (GHCTargetVersion crossTarget) targetGhc)
bootstrapGhc bootstrapGhc
jobs jobs
buildConfig buildConfig
patchDir patchDir
addConfArgs addConfArgs
pfreq
let vi = getVersionInfo (_tvVersion targetVer) GHC dls let vi = getVersionInfo (_tvVersion targetVer) GHC dls
when setCompile $ void $ liftE $ when setCompile $ void $ liftE $
setGHC targetVer SetGHCOnly setGHC targetVer SetGHCOnly
@@ -1585,8 +1594,8 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
pure ExitSuccess pure ExitSuccess
VLeft err@(V (BuildFailed tmpdir _)) -> do VLeft err@(V (BuildFailed tmpdir _)) -> do
case keepDirs settings of case keepDirs settings of
Never -> runLogger $ $(logError) $ T.pack $ prettyShow err Never -> myLoggerT loggerConfig $ $(logError) $ T.pack $ prettyShow err
_ -> runLogger ($(logError) [i|#{prettyShow err} _ -> myLoggerT loggerConfig $ ($(logError) [i|#{prettyShow err}
Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues. Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues.
Make sure to clean up #{tmpdir} afterwards.|]) Make sure to clean up #{tmpdir} afterwards.|])
pure $ ExitFailure 9 pure $ ExitFailure 9
@@ -1600,7 +1609,7 @@ Make sure to clean up #{tmpdir} afterwards.|])
(UpgradeAt p) -> pure $ Just p (UpgradeAt p) -> pure $ Just p
UpgradeGHCupDir -> pure (Just (binDir </> "ghcup")) UpgradeGHCupDir -> pure (Just (binDir </> "ghcup"))
runUpgrade (liftE $ upgradeGHCup dls target force pfreq) >>= \case runUpgrade (liftE $ upgradeGHCup target force) >>= \case
VRight v' -> do VRight v' -> do
let pretty_v = prettyVer v' let pretty_v = prettyVer v'
let vi = fromJust $ snd <$> getLatest dls GHCup let vi = fromJust $ snd <$> getLatest dls GHCup
@@ -1617,12 +1626,13 @@ Make sure to clean up #{tmpdir} afterwards.|])
pure $ ExitFailure 11 pure $ ExitFailure 11
ToolRequirements -> ToolRequirements ->
runLogger flip runReaderT appstate
$ runLogger
(runE (runE
@'[NoCompatiblePlatform , DistroNotFound , NoToolRequirements] @'[NoCompatiblePlatform , DistroNotFound , NoToolRequirements]
$ do $ do
platform <- liftE getPlatform platform <- liftE getPlatform
req <- getCommonRequirements platform treq ?? NoToolRequirements req <- getCommonRequirements platform _toolRequirements ?? NoToolRequirements
liftIO $ T.hPutStr stdout (prettyRequirements req) liftIO $ T.hPutStr stdout (prettyRequirements req)
) )
>>= \case >>= \case
@@ -1658,6 +1668,7 @@ Make sure to clean up #{tmpdir} afterwards.|])
if clOpen if clOpen
then then
flip runReaderT appstate $
exec cmd exec cmd
[T.unpack $ decUTF8Safe $ serializeURIRef' uri] [T.unpack $ decUTF8Safe $ serializeURIRef' uri]
Nothing Nothing
@@ -1674,36 +1685,40 @@ Make sure to clean up #{tmpdir} afterwards.|])
pure () pure ()
fromVersion :: (MonadLogger m, MonadFail m, MonadReader AppState m, MonadThrow m, MonadIO m, MonadCatch m) fromVersion :: (MonadLogger m, MonadFail m, MonadReader AppState m, MonadThrow m, MonadIO m, MonadCatch m)
=> GHCupDownloads => Maybe ToolVersion
-> Maybe ToolVersion
-> Tool -> Tool
-> Excepts '[TagNotFound, NextVerNotFound, NoToolVersionSet] m (GHCTargetVersion, Maybe VersionInfo) -> Excepts '[TagNotFound, NextVerNotFound, NoToolVersionSet] m (GHCTargetVersion, Maybe VersionInfo)
fromVersion av tv = fromVersion' av (toSetToolVer tv) fromVersion tv = fromVersion' (toSetToolVer tv)
fromVersion' :: (MonadLogger m, MonadFail m, MonadReader AppState m, MonadThrow m, MonadIO m, MonadCatch m) fromVersion' :: (MonadLogger m, MonadFail m, MonadReader AppState m, MonadThrow m, MonadIO m, MonadCatch m)
=> GHCupDownloads => SetToolVersion
-> SetToolVersion
-> Tool -> Tool
-> Excepts '[TagNotFound, NextVerNotFound, NoToolVersionSet] m (GHCTargetVersion, Maybe VersionInfo) -> Excepts '[TagNotFound, NextVerNotFound, NoToolVersionSet] m (GHCTargetVersion, Maybe VersionInfo)
fromVersion' av SetRecommended tool = fromVersion' SetRecommended tool = do
(\(x, y) -> (mkTVer x, Just y)) <$> getRecommended av tool AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
(\(x, y) -> (mkTVer x, Just y)) <$> getRecommended dls tool
?? TagNotFound Recommended tool ?? TagNotFound Recommended tool
fromVersion' av (SetToolVersion v) tool = do fromVersion' (SetToolVersion v) tool = do
let vi = getVersionInfo (_tvVersion v) tool av AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
let vi = getVersionInfo (_tvVersion v) tool dls
case pvp $ prettyVer (_tvVersion v) of case pvp $ prettyVer (_tvVersion v) of
Left _ -> pure (v, vi) Left _ -> pure (v, vi)
Right (PVP (major' :|[minor'])) -> Right (PVP (major' :|[minor'])) ->
case getLatestGHCFor (fromIntegral major') (fromIntegral minor') av of case getLatestGHCFor (fromIntegral major') (fromIntegral minor') dls of
Just (v', vi') -> pure (GHCTargetVersion (_tvTarget v) v', Just vi') Just (v', vi') -> pure (GHCTargetVersion (_tvTarget v) v', Just vi')
Nothing -> pure (v, vi) Nothing -> pure (v, vi)
Right _ -> pure (v, vi) Right _ -> pure (v, vi)
fromVersion' av (SetToolTag Latest) tool = fromVersion' (SetToolTag Latest) tool = do
(\(x, y) -> (mkTVer x, Just y)) <$> getLatest av tool ?? TagNotFound Latest tool AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
fromVersion' av (SetToolTag Recommended) tool = (\(x, y) -> (mkTVer x, Just y)) <$> getLatest dls tool ?? TagNotFound Latest tool
(\(x, y) -> (mkTVer x, Just y)) <$> getRecommended av tool ?? TagNotFound Recommended tool fromVersion' (SetToolTag Recommended) tool = do
fromVersion' av (SetToolTag (Base pvp'')) GHC = AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
(\(x, y) -> (mkTVer x, Just y)) <$> getLatestBaseVersion av pvp'' ?? TagNotFound (Base pvp'') GHC (\(x, y) -> (mkTVer x, Just y)) <$> getRecommended dls tool ?? TagNotFound Recommended tool
fromVersion' av SetNext tool = do fromVersion' (SetToolTag (Base pvp'')) GHC = do
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
(\(x, y) -> (mkTVer x, Just y)) <$> getLatestBaseVersion dls pvp'' ?? TagNotFound (Base pvp'') GHC
fromVersion' SetNext tool = do
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
next <- case tool of next <- case tool of
GHC -> do GHC -> do
set <- fmap _tvVersion $ ghcSet Nothing !? NoToolVersionSet tool set <- fmap _tvVersion $ ghcSet Nothing !? NoToolVersionSet tool
@@ -1746,17 +1761,14 @@ fromVersion' av SetNext tool = do
. sort . sort
$ stacks) ?? NoToolVersionSet tool $ stacks) ?? NoToolVersionSet tool
GHCup -> fail "GHCup cannot be set" GHCup -> fail "GHCup cannot be set"
let vi = getVersionInfo (_tvVersion next) tool av let vi = getVersionInfo (_tvVersion next) tool dls
pure (next, vi) pure (next, vi)
fromVersion' _ (SetToolTag t') tool = fromVersion' (SetToolTag t') tool =
throwE $ TagNotFound t' tool throwE $ TagNotFound t' tool
printListResult :: Bool -> [ListResult] -> IO () printListResult :: Bool -> [ListResult] -> IO ()
printListResult raw lr = do printListResult raw lr = do
-- https://gitlab.haskell.org/ghc/ghc/issues/8118
setLocaleEncoding utf8
no_color <- isJust <$> lookupEnv "NO_COLOR" no_color <- isJust <$> lookupEnv "NO_COLOR"
let let
@@ -1780,9 +1792,15 @@ printListResult raw lr = do
. fmap . fmap
(\ListResult {..} -> (\ListResult {..} ->
let marks = if let marks = if
#if defined(IS_WINDOWS)
| lSet -> (color Green "IS")
| lInstalled -> (color Green "I ")
| otherwise -> (color Red "X ")
#else
| lSet -> (color Green "✔✔") | lSet -> (color Green "✔✔")
| lInstalled -> (color Green "") | lInstalled -> (color Green "")
| otherwise -> (color Red "") | otherwise -> (color Red "")
#endif
in in
(if raw then [] else [marks]) (if raw then [] else [marks])
++ [ fmap toLower . show $ lTool ++ [ fmap toLower . show $ lTool
@@ -1909,11 +1927,10 @@ checkForUpdates :: ( MonadReader AppState m
, MonadFail m , MonadFail m
, MonadLogger m , MonadLogger m
) )
=> GHCupDownloads => m ()
-> PlatformRequest checkForUpdates = do
-> m () AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
checkForUpdates dls pfreq = do lInstalled <- listVersions Nothing (Just ListInstalled)
lInstalled <- listVersions dls Nothing (Just ListInstalled) pfreq
let latestInstalled tool = (fmap lVer . lastMay . filter (\lr -> lTool lr == tool)) lInstalled let latestInstalled tool = (fmap lVer . lastMay . filter (\lr -> lTool lr == tool)) lInstalled
forM_ (getLatest dls GHCup) $ \(l, _) -> do forM_ (getLatest dls GHCup) $ \(l, _) -> do

View File

@@ -14,26 +14,51 @@
# safety subshell to avoid executing anything in case this script is not downloaded properly # safety subshell to avoid executing anything in case this script is not downloaded properly
( (
: "${GHCUP_INSTALL_BASE_PREFIX:=$HOME}" plat="$(uname -s)"
arch=$(uname -m)
ghver="0.1.14.1"
base_url="https://downloads.haskell.org/~ghcup"
export GHCUP_USE_XDG_DIRS case "${plat}" in
MSYS*|MINGW*)
: "${GHCUP_INSTALL_BASE_PREFIX:=/c}"
GHCUP_DIR=${GHCUP_INSTALL_BASE_PREFIX}/ghcup
GHCUP_BIN=${GHCUP_INSTALL_BASE_PREFIX}/ghcup/bin
;;
*)
: "${GHCUP_INSTALL_BASE_PREFIX:=$HOME}"
export GHCUP_USE_XDG_DIRS
if [ -n "${GHCUP_USE_XDG_DIRS}" ] ; then if [ -n "${GHCUP_USE_XDG_DIRS}" ] ; then
GHCUP_DIR=${XDG_DATA_HOME:=$HOME/.local/share}/ghcup GHCUP_DIR=${XDG_DATA_HOME:=$HOME/.local/share}/ghcup
GHCUP_BIN=${XDG_BIN_HOME:=$HOME/.local/bin} GHCUP_BIN=${XDG_BIN_HOME:=$HOME/.local/bin}
else else
GHCUP_DIR=${GHCUP_INSTALL_BASE_PREFIX}/.ghcup GHCUP_DIR=${GHCUP_INSTALL_BASE_PREFIX}/.ghcup
GHCUP_BIN=${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin GHCUP_BIN=${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin
fi fi
;;
esac
: "${BOOTSTRAP_HASKELL_GHC_VERSION:=recommended}" : "${BOOTSTRAP_HASKELL_GHC_VERSION:=recommended}"
: "${BOOTSTRAP_HASKELL_CABAL_VERSION:=recommended}" : "${BOOTSTRAP_HASKELL_CABAL_VERSION:=recommended}"
die() { die() {
(>&2 printf "\\033[0;31m%s\\033[0m\\n" "$1") (>&2 printf "\\033[0;31m%s\\033[0m\\n" "$1")
exit 2 exit 2
} }
warn() {
case "${plat}" in
MSYS*|MINGW*)
echo -e "\\033[0;35m$1\\033[0m"
;;
*)
printf "\\033[0;35m%s\\033[0m\\n" "$1"
;;
esac
}
edo() { edo() {
"$@" || die "\"$*\" failed!" "$@" || die "\"$*\" failed!"
} }
@@ -43,92 +68,133 @@ eghcup() {
} }
_eghcup() { _eghcup() {
if [ -n "${BOOTSTRAP_HASKELL_YAML}" ] ; then
args="-s ${BOOTSTRAP_HASKELL_YAML}"
fi
if [ -z "${BOOTSTRAP_HASKELL_VERBOSE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_VERBOSE}" ] ; then
ghcup "$@" ghcup ${args} "$@"
else else
ghcup --verbose "$@" ghcup ${args} --verbose "$@"
fi fi
} }
_done() { _done() {
echo case "${plat}" in
echo "All done!" MSYS*|MINGW*)
echo echo
echo "To start a simple repl, run:" echo "All done!"
echo " ghci" echo
echo echo "In a new powershell or cmd.exe session, now you can..."
echo "To start a new haskell project in the current directory, run:" echo
echo " cabal init --interactive" echo "Start a simple repl via:"
echo echo " ghci"
echo "To install other GHC versions, run:" echo
echo " ghcup tui" echo "Start a new haskell project in the current directory via:"
echo " cabal init --interactive"
echo
echo "Install other GHC versions and tools via:"
echo " ghcup list"
echo " ghcup install <tool> <version>"
echo
echo "To install system libraries and update msys2/mingw64,"
echo "open the \"Mingw haskell shell\""
echo "and the \"Mingw package management docs\""
echo "desktop shortcuts."
;;
*)
echo
echo "All done!"
echo
echo "To start a simple repl, run:"
echo " ghci"
echo
echo "To start a new haskell project in the current directory, run:"
echo " cabal init --interactive"
echo
echo "To install other GHC versions and tools, run:"
echo " ghcup tui"
;;
esac
exit 0 exit 0
} }
download_ghcup() { download_ghcup() {
_plat="$(uname -s)"
_arch=$(uname -m)
_ghver="0.1.14.1"
_base_url="https://downloads.haskell.org/~ghcup"
case "${_plat}" in case "${plat}" in
"linux"|"Linux") "linux"|"Linux")
case "${_arch}" in case "${arch}" in
x86_64|amd64) x86_64|amd64)
# we could be in a 32bit docker container, in which # we could be in a 32bit docker container, in which
# case uname doesn't give us what we want # case uname doesn't give us what we want
if [ "$(getconf LONG_BIT)" = "32" ] ; then if [ "$(getconf LONG_BIT)" = "32" ] ; then
_url=${_base_url}/${_ghver}/i386-linux-ghcup-${_ghver} _url=${base_url}/${ghver}/i386-linux-ghcup-${ghver}
elif [ "$(getconf LONG_BIT)" = "64" ] ; then elif [ "$(getconf LONG_BIT)" = "64" ] ; then
_url=${_base_url}/${_ghver}/x86_64-linux-ghcup-${_ghver} _url=${base_url}/${ghver}/x86_64-linux-ghcup-${ghver}
else else
die "Unknown long bit size: $(getconf LONG_BIT)" die "Unknown long bit size: $(getconf LONG_BIT)"
fi fi
;; ;;
i*86) i*86)
_url=${_base_url}/${_ghver}/i386-linux-ghcup-${_ghver} _url=${base_url}/${ghver}/i386-linux-ghcup-${ghver}
;; ;;
armv7*) armv7*)
_url=${_base_url}/${_ghver}/armv7-linux-ghcup-${_ghver} _url=${base_url}/${ghver}/armv7-linux-ghcup-${ghver}
;; ;;
aarch64|arm64|armv8l) aarch64|arm64|armv8l)
_url=${_base_url}/${_ghver}/aarch64-linux-ghcup-${_ghver} _url=${base_url}/${ghver}/aarch64-linux-ghcup-${ghver}
;; ;;
*) die "Unknown architecture: ${_arch}" *) die "Unknown architecture: ${arch}"
;; ;;
esac esac
;; ;;
"FreeBSD"|"freebsd") "FreeBSD"|"freebsd")
case "${_arch}" in case "${arch}" in
x86_64|amd64) x86_64|amd64)
;; ;;
i*86) i*86)
die "i386 currently not supported!" die "i386 currently not supported!"
;; ;;
*) die "Unknown architecture: ${_arch}" *) die "Unknown architecture: ${arch}"
;; ;;
esac esac
_url=${_base_url}/${_ghver}/x86_64-portbld-freebsd-ghcup-${_ghver} _url=${base_url}/${ghver}/x86_64-portbld-freebsd-ghcup-${ghver}
;; ;;
"Darwin"|"darwin") "Darwin"|"darwin")
case "${_arch}" in case "${arch}" in
x86_64|amd64) x86_64|amd64)
;; ;;
i*86) i*86)
die "i386 currently not supported!" die "i386 currently not supported!"
;; ;;
*) die "Unknown architecture: ${_arch}" *) die "Unknown architecture: ${arch}"
;; ;;
esac esac
_url=${_base_url}/${_ghver}/x86_64-apple-darwin-ghcup-${_ghver} ;; _url=${base_url}/${ghver}/x86_64-apple-darwin-ghcup-${ghver} ;;
*) die "Unknown platform: ${_plat}" MSYS*|MINGW*)
case "${arch}" in
x86_64|amd64)
_url=https://downloads.haskell.org/~ghcup/tmp/x86_64-mingw64-ghcup-7.exe
;;
*) die "Unknown architecture: ${arch}"
;;
esac
;;
*) die "Unknown platform: ${plat}"
;; ;;
esac esac
case "${plat}" in
edo curl -Lf "${_url}" > "${GHCUP_BIN}"/ghcup MSYS*|MINGW*)
edo curl -Lf "${_url}" > "${GHCUP_BIN}"/ghcup.exe
edo chmod +x "${GHCUP_BIN}"/ghcup edo chmod +x "${GHCUP_BIN}"/ghcup.exe
;;
*)
edo curl -Lf "${_url}" > "${GHCUP_BIN}"/ghcup
edo chmod +x "${GHCUP_BIN}"/ghcup
;;
esac
edo mkdir -p "${GHCUP_DIR}" edo mkdir -p "${GHCUP_DIR}"
cat <<-EOF > "${GHCUP_DIR}"/env || die "Failed to create env file" cat <<-EOF > "${GHCUP_DIR}"/env || die "Failed to create env file"
@@ -137,8 +203,6 @@ download_ghcup() {
# shellcheck disable=SC1090 # shellcheck disable=SC1090
edo . "${GHCUP_DIR}"/env edo . "${GHCUP_DIR}"/env
eghcup upgrade eghcup upgrade
unset _plat _arch _url _ghver _base_url
} }
@@ -147,14 +211,22 @@ echo "Welcome to Haskell!"
echo echo
echo "This script will download and install the following binaries:" echo "This script will download and install the following binaries:"
echo " * ghcup - The Haskell toolchain installer" echo " * ghcup - The Haskell toolchain installer"
echo " (for managing GHC/cabal versions)"
echo " * ghc - The Glasgow Haskell Compiler" echo " * ghc - The Glasgow Haskell Compiler"
echo " * cabal - The Cabal build tool" echo " * cabal - The Cabal build tool for managing Haskell software"
echo " * stack - (optional) A cross-platform program for developing Haskell projects"
echo " * hls - (optional) A language server for developers to integrate with their editor/IDE"
echo echo
if [ -z "${GHCUP_USE_XDG_DIRS}" ] ; then if [ -z "${GHCUP_USE_XDG_DIRS}" ] ; then
echo "ghcup installs only into the following directory," echo "ghcup installs only into the following directory,"
echo "which can be removed anytime:" echo "which can be removed anytime:"
echo " $GHCUP_INSTALL_BASE_PREFIX/.ghcup" case "${plat}" in
MSYS*|MINGW*)
echo " $(cygpath -w "$GHCUP_DIR")"
;;
*)
echo " $GHCUP_DIR"
;;
esac
else else
echo "ghcup installs into XDG directories as long as" echo "ghcup installs into XDG directories as long as"
echo "'GHCUP_USE_XDG_DIRS' is set." echo "'GHCUP_USE_XDG_DIRS' is set."
@@ -162,8 +234,8 @@ fi
echo echo
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
printf "\\033[0;35m%s\\033[0m\\n" "Press ENTER to proceed or ctrl-c to abort." warn "Press ENTER to proceed or ctrl-c to abort."
printf "\\033[0;35m%s\\033[0m\\n" "Note that this script can be re-run at any given time." warn "Note that this script can be re-run at any given time."
echo echo
# Wait for user input to continue. # Wait for user input to continue.
# shellcheck disable=SC2034 # shellcheck disable=SC2034
@@ -181,12 +253,12 @@ else
fi fi
echo echo
echo "$(ghcup tool-requirements)" echo "$(if [ -n "${BOOTSTRAP_HASKELL_YAML}" ] ; then ghcup -s "${BOOTSTRAP_HASKELL_YAML}" tool-requirements ; else ghcup tool-requirements ; fi)"
echo echo
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
printf "\\033[0;35m%s\\033[0m\\n" "Press ENTER to proceed or ctrl-c to abort." warn "Press ENTER to proceed or ctrl-c to abort."
printf "\\033[0;35m%s\\033[0m\\n" "Installation may take a while." warn "Installation may take a while."
echo echo
# Wait for user input to continue. # Wait for user input to continue.
@@ -199,21 +271,54 @@ eghcup --cache install ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}"
eghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}" eghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}"
eghcup --cache install cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}" eghcup --cache install cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}"
adjust_cabal_config() {
edo cabal user-config -a "extra-prog-path: $(cygpath -w $GHCUP_BIN), $(cygpath -w "$HOME"/AppData/Roaming/cabal/bin), $(cygpath -w "$GHCUP_DIR"/msys64/usr/bin), $(cygpath -w "$GHCUP_DIR"/msys64/mingw64/bin)" -a "extra-include-dirs: $(cygpath -w "$GHCUP_DIR"/msys64/mingw64/include)" -a "extra-lib-dirs: $(cygpath -w "$GHCUP_DIR"/msys64/mingw64/lib)" -f init
}
case "${plat}" in
MSYS*|MINGW*)
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
warn "Create an initial cabal.config including relevant msys2 paths (recommended)?"
warn "[Y] Yes [N] No [?] Help (default is \"Y\")."
echo
while true; do
read -r mingw_answer </dev/tty
case $mingw_answer in
[Yy]* | "")
adjust_cabal_config
break ;;
[Nn]*)
echo "Make sure that your global cabal.config references the correct mingw64 paths (extra-prog-path, extra-include-dirs and extra-lib-dirs)."
sleep 5
break ;;
*)
echo "Possible choices are:"
echo
echo "Y - Yes, create a cabal.config with pre-set paths to msys2/mingw64 (default)"
echo "N - No, leave the current/default cabal config untouched"
echo
echo "Please make your choice and press ENTER."
;;
esac
done
else
adjust_cabal_config
fi
;;
esac
edo cabal new-update edo cabal new-update
printf "\\033[0;35m%s\\033[0m\\n" ""
printf "\\033[0;35m%s\\033[0m\\n" "Installation done!"
printf "\\033[0;35m%s\\033[0m\\n" ""
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
printf "\\033[0;35m%s\\033[0m\\n" "Do you want to install haskell-language-server (HLS) now?" warn "Do you want to install haskell-language-server (HLS) now?"
printf "\\033[0;35m%s\\033[0m\\n" "HLS is a language-server that provides IDE-like functionality" warn "HLS is a language-server that provides IDE-like functionality"
printf "\\033[0;35m%s\\033[0m\\n" "and can integrate with different editors, such as Vim, Emacs, VS Code, Atom, ..." warn "and can integrate with different editors, such as Vim, Emacs, VS Code, Atom, ..."
printf "\\033[0;35m%s\\033[0m\\n" "Also see https://github.com/haskell/haskell-language-server/blob/master/README.md" warn "Also see https://github.com/haskell/haskell-language-server/blob/master/README.md"
printf "\\033[0;35m%s\\033[0m\\n" "" warn ""
printf "\\033[0;35m%s\\033[0m\\n" "Answer with YES or NO and press ENTER." warn "[Y] Yes [N] No [?] Help (default is \"N\")."
printf "\\033[0;35m%s\\033[0m\\n" "" warn ""
while true; do while true; do
read -r hls_answer </dev/tty read -r hls_answer </dev/tty
@@ -222,10 +327,43 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
[Yy]*) [Yy]*)
eghcup --cache install hls eghcup --cache install hls
break ;; break ;;
[Nn]*) [Nn]* | "")
break ;; break ;;
*) *)
echo "Please type YES or NO and press enter.";; echo "Possible choices are:"
echo
echo "Y - Yes, install the haskell-langauge-server"
echo "N - No, don't install anything more (default)"
echo
echo "Please make your choice and press ENTER."
;;
esac
done
warn "Do you want to install stack now?"
warn "Stack is a haskell build tool similar to cabal that is used by some projects."
warn "Also see https://docs.haskellstack.org/"
warn ""
warn "[Y] Yes [N] No [?] Help (default is \"N\")."
warn ""
while true; do
read -r stack_answer </dev/tty
case $stack_answer in
[Yy]*)
eghcup --cache install stack
break ;;
[Nn]* | "")
break ;;
*)
echo "Possible choices are:"
echo
echo "Y - Yes, install stack"
echo "N - No, don't install anything more (default)"
echo
echo "Please make your choice and press ENTER."
;;
esac esac
done done
@@ -258,17 +396,20 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
esac esac
printf "\\033[0;35m%s\\033[0m\\n" "" warn ""
printf "\\033[0;35m%s\\033[0m\\n" "Detected ${MY_SHELL} shell on your system..." warn "Detected ${MY_SHELL} shell on your system..."
printf "\\033[0;35m%s\\033[0m\\n" "If you want ghcup to automatically add the required PATH variable to \"${GHCUP_PROFILE_FILE}\"" warn "If you want ghcup to automatically add the required PATH variable to \"${GHCUP_PROFILE_FILE}\""
printf "\\033[0;35m%s\\033[0m\\n" "answer with YES, otherwise with NO and press ENTER." warn ""
printf "\\033[0;35m%s\\033[0m\\n" "" warn "[Y] Yes [N] No [?] Help (default is \"Y\")."
warn ""
while true; do while true; do
read -r next_answer </dev/tty read -r next_answer </dev/tty
case $next_answer in case $next_answer in
[Yy]*) [Nn]*)
_done ;;
[Yy]* | "")
case $MY_SHELL in case $MY_SHELL in
"") break ;; "") break ;;
fish) fish)
@@ -283,7 +424,7 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then
echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}" echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}"
fi fi
case "$(uname -s)" in case "${plat}" in
"Darwin"|"darwin") "Darwin"|"darwin")
if ! grep -q "ghcup-env" "${HOME}/.bash_profile" ; then if ! grep -q "ghcup-env" "${HOME}/.bash_profile" ; then
echo "[[ -f ~/.bashrc ]] && source ~/.bashrc # ghcup-env" >> "${HOME}/.bash_profile" echo "[[ -f ~/.bashrc ]] && source ~/.bashrc # ghcup-env" >> "${HOME}/.bash_profile"
@@ -298,17 +439,24 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
fi fi
break ;; break ;;
esac esac
printf "\\033[0;35m%s\\033[0m\\n" "OK! ${GHCUP_PROFILE_FILE} has been modified. Restart your terminal for the changes to take effect," warn "OK! ${GHCUP_PROFILE_FILE} has been modified. Restart your terminal for the changes to take effect,"
printf "\\033[0;35m%s\\033[0m\\n" "or type \"source ${GHCUP_DIR}/env\" to apply them in your current terminal session." warn "or type \"source ${GHCUP_DIR}/env\" to apply them in your current terminal session."
_done _done
;; ;;
[Nn]*) *)
_done ;; echo "Possible choices are:"
*) echo
echo "Please type YES or NO and press enter.";; echo "Y - Yes, update my \"${GHCUP_PROFILE_FILE}\" (default)"
echo "N - No, don't mess with my configuration"
echo
echo "Please make your choice and press ENTER."
;;
esac esac
done done
fi fi
_done
) )
# vim: tabstop=4 shiftwidth=4 expandtab # vim: tabstop=4 shiftwidth=4 expandtab

194
bootstrap-haskell.ps1 Normal file
View File

@@ -0,0 +1,194 @@
function Print-Msg {
param ( [Parameter(Mandatory=$true, HelpMessage='String to output')][string]$msg )
Write-Host ('{0}' -f $msg) -ForegroundColor Green
}
function Create-Shortcut {
param ( [Parameter(Mandatory=$true,HelpMessage='Target path')][string]$SourceExe, [Parameter(Mandatory=$true,HelpMessage='Arguments to the path/exe')][AllowEmptyString()]$ArgumentsToSourceExe, [Parameter(Mandatory=$true,HelpMessage='The destination of the desktop link')][string]$DestinationPath )
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($DestinationPath)
$Shortcut.TargetPath = $SourceExe
if($ArgumentsToSourceExe) {
$Shortcut.Arguments = $ArgumentsToSourceExe
}
$Shortcut.Save()
}
function Add-EnvPath {
param(
[Parameter(Mandatory=$true,HelpMessage='The Pathe to add to Users environment')]
[string] $Path,
[ValidateSet('Machine', 'User', 'Session')]
[string] $Container = 'Session'
)
function Where-Something
{
param
(
[Parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage='Data to filter')]
$InputObject
)
process
{
if ($InputObject)
{
$InputObject
}
}
}
if ($Container -ne 'Session') {
$containerMapping = @{
Machine = [EnvironmentVariableTarget]::Machine
User = [EnvironmentVariableTarget]::User
}
$containerType = $containerMapping[$Container]
$persistedPaths = [Environment]::GetEnvironmentVariable('Path', $containerType) -split ';'
if ($persistedPaths -notcontains $Path) {
$persistedPaths = $persistedPaths + $Path | Where-Something
[Environment]::SetEnvironmentVariable('Path', $persistedPaths -join ';', $containerType)
}
}
$envPaths = $env:Path -split ';'
if ($envPaths -notcontains $Path) {
$envPaths = $envPaths + $Path | Where-Something
$env:Path = $envPaths -join ';'
}
}
filter Get-FileSize {
'{0:N2} {1}' -f $(
if ($_ -lt 1kb) { $_, 'Bytes' }
elseif ($_ -lt 1mb) { ($_/1kb), 'KB' }
elseif ($_ -lt 1gb) { ($_/1mb), 'MB' }
elseif ($_ -lt 1tb) { ($_/1gb), 'GB' }
elseif ($_ -lt 1pb) { ($_/1tb), 'TB' }
else { ($_/1pb), 'PB' }
)
}
function Get-FileWCSynchronous{
param(
[Parameter(Mandatory=$true)]
[string]$url,
[string]$destinationFolder="$env:USERPROFILE\Downloads",
[switch]$includeStats
)
$wc = New-Object -TypeName Net.WebClient
$wc.UseDefaultCredentials = $true
$destination = Join-Path -Path $destinationFolder -ChildPath ($url | Split-Path -Leaf)
$start = Get-Date
$wc.DownloadFile($url, $destination)
$elapsed = ((Get-Date) - $start).ToString('hh\:mm\:ss')
$totalSize = (Get-Item -Path $destination).Length | Get-FileSize
if ($includeStats.IsPresent){
[PSCustomObject]@{Name=$MyInvocation.MyCommand;TotalSize=$totalSize;Time=$elapsed}
}
Get-Item -Path $destination | Unblock-File
}
$ErrorActionPreference = 'Stop'
$GhcupDir = "$env:HOMEDRIVE\ghcup"
$MsysDir = ('{0}\msys64' -f $GhcupDir)
$Bash = ('{0}\usr\bin\bash' -f $MsysDir)
Print-Msg -msg 'Preparing for GHCup installation...'
if (Test-Path -Path ('{0}' -f $GhcupDir)) {
$decision = $Host.UI.PromptForChoice('Install', 'GHCup is already installed, what do you want to do?', @('&Reinstall'
'&Continue'
'&Abort'), 1)
if ($decision -eq 0) {
$suffix = [IO.Path]::GetRandomFileName()
Print-Msg -msg ('Backing up {0} to {0}-{1} ...' -f $GhcupDir, $suffix)
Rename-Item -Path ('{0}' -f $GhcupDir) -NewName ('{0}-{1}' -f $GhcupDir, $suffix)
} elseif ($decision -eq 1) {
Print-Msg -msg 'Continuing installation...'
} elseif ($decision -eq 2) {
Exit
}
}
$null = New-Item -Path ('{0}' -f $GhcupDir) -ItemType 'directory' -ErrorAction SilentlyContinue
$null = New-Item -Path ('{0}' -f $GhcupDir) -Name 'bin' -ItemType 'directory' -ErrorAction SilentlyContinue
Print-Msg -msg 'First checking for Msys2...'
if (!(Test-Path -Path ('{0}' -f $MsysDir))) {
Print-Msg -msg ('...Msys2 doesn''t exist, installing into {0} ...this may take a while' -f $MsysDir)
# Download the archive
Print-Msg -msg 'Downloading Msys2 archive...'
$archive = 'msys2-x86_64-latest.sfx.exe'
if (Get-Command -Name 'curl.exe' -ErrorAction SilentlyContinue) {
curl.exe -o ('{0}\{1}' -f $env:TEMP, $archive) ('https://repo.msys2.org/distrib/{0}' -f $archive)
} else {
Get-FileWCSynchronous -url ('https://repo.msys2.org/distrib/{0}' -f $archive) -destinationFolder "$env:TEMP" -includeStats
}
Print-Msg -msg 'Extracting Msys2 archive...'
$null = & "$env:TEMP\$archive" '-y' ('-o{0}' -f $GhcupDir) # Extract
Remove-Item -Path ('{0}/{1}' -f $env:TEMP, $archive)
Print-Msg -msg 'Processing MSYS2 bash for first time use...'
& "$Bash" -lc 'exit'
& "$env:windir\system32\taskkill.exe" /F /FI `"MODULES eq msys-2.0.dll`"
Print-Msg -msg 'Upgrading full system...'
& "$Bash" -lc 'pacman --noconfirm -Syuu'
Print-Msg -msg 'Upgrading full system twice...'
& "$Bash" -lc 'pacman --noconfirm -Syuu'
Print-Msg -msg 'Installing GHC Build Dependencies...'
& "$Bash" -lc 'pacman --noconfirm -S --needed git tar curl wget base-devel gettext binutils autoconf make libtool automake python p7zip patch unzip mingw-w64-x86_64-toolchain mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb mingw-w64-x86_64-python2 mingw-w64-x86_64-python3-sphinx'
Print-Msg -msg 'Updating SSL root certificate authorities...'
& "$Bash" -lc 'pacman --noconfirm -S ca-certificates'
Print-Msg -msg 'Setting default home directory...'
& "$Bash" -lc "sed -i -e 's/db_home:.*$/db_home: windows/' /etc/nsswitch.conf"
} else {
Print-Msg -msg ('...Msys2 found in {0} ...skipping Msys2 installation.' -f $MsysDir)
}
Print-Msg -msg 'Creating shortcuts...'
Create-Shortcut -SourceExe ('{0}\msys2_shell.cmd' -f $MsysDir) -ArgumentsToSourceExe '-mingw64' -DestinationPath ('{0}\Desktop\Mingw haskell shell.lnk' -f $HOME)
Create-Shortcut -SourceExe 'https://www.msys2.org/docs/package-management' -ArgumentsToSourceExe '' -DestinationPath ('{0}\Desktop\Mingw package management docs.url' -f $HOME)
Print-Msg -msg ('Adding {0}\bin to Users Path...' -f $GhcupDir)
Add-EnvPath -Path ('{0}\bin' -f $GhcupDir) -Container 'User'
Print-Msg -msg 'Starting GHCup installer...'
& "$Bash" -lc "export PATH=`"/c/ghcup/bin:`$PATH`" ; curl --proto =https --tlsv1.2 -sSf https://gitlab.haskell.org/haskell/ghcup-hs/-/raw/windows-support/bootstrap-haskell | bash"
# SIG # Begin signature block
# MIID4QYJKoZIhvcNAQcCoIID0jCCA84CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUVqKek181kF/Jx/P7z176herc
# ZyCgggH/MIIB+zCCAWSgAwIBAgIQGOezhGS1A5tHh9VubW0liDANBgkqhkiG9w0B
# AQUFADAYMRYwFAYDVQQDDA1KdWxpYW4gT3NwYWxkMB4XDTIxMDUzMDE4Mzk1OVoX
# DTI1MDUzMDAwMDAwMFowGDEWMBQGA1UEAwwNSnVsaWFuIE9zcGFsZDCBnzANBgkq
# hkiG9w0BAQEFAAOBjQAwgYkCgYEAs76XCXYPM14buR1RkVKhOB8pyM4Df6kPaz75
# nkbA0nq1VmMhBfCYFWyYHd7jniqTH0LoAKGGquN1bniREaCP9j2pFWpMIgLpQH3H
# +jpsfmxV2BTG8q+Jok88gTXS1FlAk72E85zO/Jhr6Fja1aFYAdibBRsRxcVMTVh7
# 4AGLNGUCAwEAAaNGMEQwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFC+R
# hdhPo0Ty5HnzHyo1pN35IfZQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQUF
# AAOBgQAl3IdBVIwbJJDp7BksMYPeM4ivB3UyNvlw8aVxGwAzNgdSaezYIdMFtKXV
# CSv5bd4VnFRAPDJW9dhW0h3SkeJUoklUxMjKXhR3qygQhSxPDjIatAuOCffGACba
# ZZ7Om40b+pKXc6i/HnlApk9DGbXJ59bFcLGGcZ9QjoUae6Ex1DGCAUwwggFIAgEB
# MCwwGDEWMBQGA1UEAwwNSnVsaWFuIE9zcGFsZAIQGOezhGS1A5tHh9VubW0liDAJ
# BgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0B
# CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAj
# BgkqhkiG9w0BCQQxFgQUosm9nN1JgajqSBa1cUwxxhLrAsYwDQYJKoZIhvcNAQEB
# BQAEgYCnKzfsH1aDjS6xkC/uymjaBowHSnh6nFu2AkjcKu8RgcBZzP5SLBXgU9wm
# aED5Ujwyq3Qre+TGVRUqwkEauDhQiX2A008G00fRO6+di6yJRCRn5eaRAbdU3Xww
# E5VhEwLBnwzWrvLKtdEclhgUCo5Tq87QMXVdgX4aRmunl4ZE+Q==
# SIG # End signature block

View File

@@ -2,11 +2,8 @@ packages: ./ghcup.cabal
optimization: 2 optimization: 2
package streamly
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16
package ghcup package ghcup
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16 ghc-options: -O2 -rtsopts
tests: True tests: True
flags: +tui flags: +tui
@@ -20,4 +17,4 @@ constraints: http-io-streams -brotli
package libarchive package libarchive
flags: -system-libarchive flags: -system-libarchive
allow-newer: base, ghc-prim, template-haskell allow-newer: base, ghc-prim, template-haskell, language-c

View File

@@ -127,6 +127,13 @@ toolRequirements:
- libffi - libffi
- libiconv - libiconv
notes: '' notes: ''
Windows:
unknown_versioning:
distroPKGs: []
notes: On Windows, msys2 should already have been set up during the installation,
so most users should just press ENTER.
If you are installing manually, make sure to have a working mingw64 toolchain and
shell.
ghcupDownloads: ghcupDownloads:
GHC: GHC:
7.10.3: 7.10.3:
@@ -1929,8 +1936,8 @@ ghcupDownloads:
dlHash: 89a70980d77888dae8b9fd0f05e7a7920f421bc3bb5192da8e73fd4e7b4cb86f dlHash: 89a70980d77888dae8b9fd0f05e7a7920f421bc3bb5192da8e73fd4e7b4cb86f
Windows: Windows:
unknown_versioning: unknown_versioning:
dlUri: https://TODO dlUri: https://downloads.haskell.org/ghcup/tmp/ghcup.exe
dlHash: 89a70980d77888dae8b9fd0f05e7a7920f421bc3bb5192da8e73fd4e7b4cb86f dlHash: e6dc0b337b29164d5e4a299e572955591b1b6e5d4d11e895c8cbc05666d98ad5
Linux_Alpine: Linux_Alpine:
unknown_versioning: *ghcup-64 unknown_versioning: *ghcup-64
A_32: A_32:
@@ -1977,6 +1984,7 @@ ghcupDownloads:
2.5.1: 2.5.1:
viTags: [] viTags: []
viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v251 viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v251
viPostInstall: &stack-post "Stack manages GHC versions internally by default. In order to make it use ghcup installed GHC versions have a look at the options 'system-ghc', 'compiler-check' and 'compiler': https://docs.haskellstack.org/en/stable/yaml_configuration/#system-ghc"
viArch: viArch:
A_64: A_64:
Linux_UnknownLinux: Linux_UnknownLinux:
@@ -2004,6 +2012,7 @@ ghcupDownloads:
- Recommended - Recommended
- Latest - Latest
viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v271 viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v271
viPostInstall: *stack-post
viArch: viArch:
A_64: A_64:
Linux_UnknownLinux: Linux_UnknownLinux:

2042
ghcup-0.0.5.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
cabal-version: 3.0 cabal-version: 3.0
name: ghcup name: ghcup
version: 0.1.14.2 version: 0.1.15
license: LGPL-3.0-only license: LGPL-3.0-only
license-file: LICENSE license-file: LICENSE
copyright: Julian Ospald 2020 copyright: Julian Ospald 2020
@@ -43,7 +43,7 @@ flag internal-downloader
flag tar flag tar
description: description:
Use tar-bytestring instead of libarchive. This is always enabled on windows. Use tar-bytestring instead of libarchive.
default: False default: False
manual: True manual: True
@@ -75,14 +75,20 @@ library
autogen-modules: Paths_ghcup autogen-modules: Paths_ghcup
default-language: Haskell2010 default-language: Haskell2010
default-extensions: default-extensions:
DeriveGeneric
LambdaCase LambdaCase
MultiWayIf MultiWayIf
NamedFieldPuns
PackageImports PackageImports
QuasiQuotes
RecordWildCards RecordWildCards
ScopedTypeVariables ScopedTypeVariables
Strict Strict
StrictData StrictData
TupleSections TupleSections
TypeApplications
TypeFamilies
ViewPatterns
ghc-options: ghc-options:
-Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns
@@ -118,17 +124,14 @@ library
, parsec ^>=3.1 , parsec ^>=3.1
, pretty ^>=1.1.3.1 , pretty ^>=1.1.3.1
, pretty-terminal ^>=0.1.0.0 , pretty-terminal ^>=0.1.0.0
, process ^>=1.6.9.0
, regex-posix ^>=0.96 , regex-posix ^>=0.96
, resourcet ^>=1.2.2 , resourcet ^>=1.2.2
, safe ^>=0.3.18 , safe ^>=0.3.18
, safe-exceptions ^>=0.1 , safe-exceptions ^>=0.1
, split ^>=0.2.3.4 , split ^>=0.2.3.4
, streamly ^>=0.7.3
, streamly-bytestring ^>=0.1.2
, strict-base ^>=0.4 , strict-base ^>=0.4
, string-interpolate >=0.2.0.0 && <0.4 , string-interpolate >=0.2.0.0 && <0.4
, template-haskell >=2.7 && <2.17 , template-haskell >=2.7 && <2.18
, temporary ^>=1.3 , temporary ^>=1.3
, text ^>=1.2.4.0 , text ^>=1.2.4.0
, time ^>=1.9.3 , time ^>=1.9.3
@@ -150,7 +153,7 @@ library
build-depends: build-depends:
, HsOpenSSL >=0.11.4.18 , HsOpenSSL >=0.11.4.18
, http-io-streams >=0.1.2.0 , http-io-streams >=0.1.2.0
, io-streams >=1.5 , io-streams >=1.5.2.1
, terminal-progress-bar >=0.4.1 , terminal-progress-bar >=0.4.1
if (flag(tar) || os(windows)) if (flag(tar) || os(windows))
@@ -163,14 +166,18 @@ library
if os(windows) if os(windows)
cpp-options: -DIS_WINDOWS cpp-options: -DIS_WINDOWS
other-modules: GHCup.Utils.File.Windows other-modules: GHCup.Utils.File.Windows
build-depends: bzlib build-depends:
, bzlib
, process ^>=1.6.11.0
, retry ^>=0.8.1.2
, Win32 ^>=2.10
else else
other-modules: GHCup.Utils.File.Posix other-modules: GHCup.Utils.File.Posix
build-depends: build-depends:
bz2 >=0.5.0.5 && <1.1 , bz2 >=0.5.0.5 && <1.1
, hpath-posix ^>=0.13.3 , hpath-posix ^>=0.13.3
, streamly-posix ^>=0.1.0.0 , process ^>=1.6.9
, unix ^>=2.7 , unix ^>=2.7
, unix-bytestring ^>=0.3.7.3 , unix-bytestring ^>=0.3.7.3
@@ -185,6 +192,7 @@ executable ghcup
default-extensions: default-extensions:
LambdaCase LambdaCase
MultiWayIf MultiWayIf
NamedFieldPuns
PackageImports PackageImports
RecordWildCards RecordWildCards
ScopedTypeVariables ScopedTypeVariables
@@ -197,7 +205,6 @@ executable ghcup
-fwarn-incomplete-record-updates -threaded -fwarn-incomplete-record-updates -threaded
build-depends: build-depends:
, aeson >=1.4 && <1.6
, base >=4.13 && <5 , base >=4.13 && <5
, bytestring ^>=0.10 , bytestring ^>=0.10
, containers ^>=0.6 , containers ^>=0.6
@@ -214,7 +221,7 @@ executable ghcup
, safe ^>=0.3.18 , safe ^>=0.3.18
, safe-exceptions ^>=0.1 , safe-exceptions ^>=0.1
, string-interpolate >=0.2.0.0 && <0.4 , string-interpolate >=0.2.0.0 && <0.4
, template-haskell >=2.7 && <2.17 , template-haskell >=2.7 && <2.18
, text ^>=1.2.4.0 , text ^>=1.2.4.0
, uri-bytestring ^>=0.3.2.2 , uri-bytestring ^>=0.3.2.2
, utf8-string ^>=1.0 , utf8-string ^>=1.0
@@ -227,9 +234,13 @@ executable ghcup
cpp-options: -DBRICK cpp-options: -DBRICK
other-modules: BrickMain other-modules: BrickMain
build-depends: build-depends:
, brick >=0.5 && <0.62 , brick >=0.5 && <0.62
, vector ^>=0.12 , transformers ^>=0.5
, vty >=5.28.2 && <5.34 , vector ^>=0.12
, vty >=5.28.2 && <5.34
if os(windows)
cpp-options: -DIS_WINDOWS
if (flag(tar) || os(windows)) if (flag(tar) || os(windows))
cpp-options: -DTAR cpp-options: -DTAR
@@ -243,20 +254,26 @@ executable ghcup-gen
other-modules: Validate other-modules: Validate
default-language: Haskell2010 default-language: Haskell2010
default-extensions: default-extensions:
DeriveGeneric
LambdaCase LambdaCase
MultiWayIf MultiWayIf
NamedFieldPuns
PackageImports PackageImports
QuasiQuotes
RecordWildCards RecordWildCards
ScopedTypeVariables ScopedTypeVariables
Strict
StrictData
TupleSections TupleSections
TypeApplications
TypeFamilies
ViewPatterns
ghc-options: ghc-options:
-Wall -fwarn-tabs -fwarn-incomplete-uni-patterns -Wall -fwarn-tabs -fwarn-incomplete-uni-patterns
-fwarn-incomplete-record-updates -threaded -fwarn-incomplete-record-updates -threaded
build-depends: build-depends:
, aeson >=1.4 && <1.6
, aeson-pretty ^>=0.8.8
, base >=4.13 && <5 , base >=4.13 && <5
, bytestring ^>=0.10 , bytestring ^>=0.10
, containers ^>=0.6 , containers ^>=0.6
@@ -276,7 +293,6 @@ executable ghcup-gen
, text ^>=1.2.4.0 , text ^>=1.2.4.0
, transformers ^>=0.5 , transformers ^>=0.5
, uri-bytestring ^>=0.3.2.2 , uri-bytestring ^>=0.3.2.2
, utf8-string ^>=1.0
, versions ^>=4.0.1 , versions ^>=4.0.1
, yaml ^>=0.11.4.0 , yaml ^>=0.11.4.0
@@ -316,7 +332,7 @@ test-suite ghcup-test
, generic-arbitrary ^>=0.1.0 , generic-arbitrary ^>=0.1.0
, ghcup , ghcup
, hspec ^>=2.7.10 , hspec ^>=2.7.10
, hspec-golden-aeson >=0.9 && <0.10 , hspec-golden-aeson ^>=0.9
, QuickCheck ^>=2.14.1 , QuickCheck ^>=2.14.1
, quickcheck-arbitrary-adt ^>=0.3.1.0 , quickcheck-arbitrary-adt ^>=0.3.1.0
, text ^>=1.2.4.0 , text ^>=1.2.4.0

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,11 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-| {-|
Module : GHCup Module : GHCup
@@ -112,7 +108,6 @@ installGHCBindist :: ( MonadFail m
) )
=> DownloadInfo -- ^ where/how to download => DownloadInfo -- ^ where/how to download
-> Version -- ^ the version to install -> Version -- ^ the version to install
-> PlatformRequest -- ^ the platform to install on
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, BuildFailed , BuildFailed
@@ -128,20 +123,22 @@ installGHCBindist :: ( MonadFail m
] ]
m m
() ()
installGHCBindist dlinfo ver pfreq = do installGHCBindist dlinfo ver = do
AppState { dirs , settings } <- lift ask
let tver = mkTVer ver let tver = mkTVer ver
lift $ $(logDebug) [i|Requested to install GHC with #{ver}|] lift $ $(logDebug) [i|Requested to install GHC with #{ver}|]
whenM (lift $ ghcInstalled tver) (throwE $ AlreadyInstalled GHC ver) whenM (lift $ ghcInstalled tver) (throwE $ AlreadyInstalled GHC ver)
-- download (or use cached version) -- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing dl <- liftE $ downloadCached settings dirs dlinfo Nothing
-- prepare paths -- prepare paths
ghcdir <- lift $ ghcupGHCDir tver ghcdir <- lift $ ghcupGHCDir tver
toolchainSanityChecks toolchainSanityChecks
liftE $ installPackedGHC dl (view dlSubdir dlinfo) ghcdir ver pfreq liftE $ installPackedGHC dl (view dlSubdir dlinfo) ghcdir ver
liftE $ postGHCInstall tver liftE $ postGHCInstall tver
@@ -170,7 +167,6 @@ installPackedGHC :: ( MonadMask m
-> Maybe TarDir -- ^ Subdir of the archive -> Maybe TarDir -- ^ Subdir of the archive
-> FilePath -- ^ Path to install to -> FilePath -- ^ Path to install to
-> Version -- ^ The GHC version -> Version -- ^ The GHC version
-> PlatformRequest
-> Excepts -> Excepts
'[ BuildFailed '[ BuildFailed
, UnknownArchive , UnknownArchive
@@ -179,11 +175,13 @@ installPackedGHC :: ( MonadMask m
, ArchiveResult , ArchiveResult
#endif #endif
] m () ] m ()
installPackedGHC dl msubdir inst ver pfreq@PlatformRequest{..} = do installPackedGHC dl msubdir inst ver = do
AppState { pfreq = PlatformRequest {..} } <- lift ask
-- unpack -- unpack
tmpUnpack <- lift mkGhcupTmpDir tmpUnpack <- lift mkGhcupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ lift $ darwinNotarization _rPlatform tmpUnpack
-- the subdir of the archive where we do the work -- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack) workdir <- maybe (pure tmpUnpack)
@@ -192,7 +190,7 @@ installPackedGHC dl msubdir inst ver pfreq@PlatformRequest{..} = do
liftE $ runBuildAction tmpUnpack liftE $ runBuildAction tmpUnpack
(Just inst) (Just inst)
(installUnpackedGHC workdir inst ver pfreq) (installUnpackedGHC workdir inst ver)
-- | Install an unpacked GHC distribution. This only deals with the GHC -- | Install an unpacked GHC distribution. This only deals with the GHC
@@ -205,30 +203,36 @@ installUnpackedGHC :: ( MonadReader AppState m
=> FilePath -- ^ Path to the unpacked GHC bindist (where the configure script resides) => FilePath -- ^ Path to the unpacked GHC bindist (where the configure script resides)
-> FilePath -- ^ Path to install to -> FilePath -- ^ Path to install to
-> Version -- ^ The GHC version -> Version -- ^ The GHC version
-> PlatformRequest
-> Excepts '[ProcessError] m () -> Excepts '[ProcessError] m ()
#if defined(IS_WINDOWS) #if defined(IS_WINDOWS)
installUnpackedGHC path inst _ _ = do installUnpackedGHC path inst _ = do
lift $ $(logInfo) "Installing GHC (this may take a while)" lift $ $(logInfo) "Installing GHC (this may take a while)"
-- windows bindists are relocatable and don't need -- windows bindists are relocatable and don't need
-- to run configure -- to run configure
liftIO $ copyDirectoryRecursive path inst liftIO $ copyDirectoryRecursive path inst
#else #else
installUnpackedGHC path inst ver PlatformRequest{..} = do installUnpackedGHC path inst ver = do
AppState { pfreq = PlatformRequest {..} } <- lift ask
let alpineArgs
| ver >= [vver|8.2.2|], Linux Alpine <- _rPlatform
= ["--disable-ld-override"]
| otherwise
= []
lift $ $(logInfo) "Installing GHC (this may take a while)" lift $ $(logInfo) "Installing GHC (this may take a while)"
lEM $ execLogged "sh" lEM $ execLogged "sh"
("./configure" : ("--prefix=" <> inst) : alpineArgs) ("./configure" : ("--prefix=" <> inst)
#if defined(IS_WINDOWS)
: "--enable-tarballs-autodownload"
#endif
: alpineArgs
)
(Just path) (Just path)
"ghc-configure" "ghc-configure"
Nothing Nothing
lEM $ make ["install"] (Just path) lEM $ make ["install"] (Just path)
pure () pure ()
where
alpineArgs
| ver >= [vver|8.2.2|], Linux Alpine <- _rPlatform
= ["--disable-ld-override"]
| otherwise
= []
#endif #endif
@@ -246,9 +250,7 @@ installGHCBin :: ( MonadFail m
, MonadIO m , MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
) )
=> GHCupDownloads -- ^ the download info to look up the tarball from => Version -- ^ the version to install
-> Version -- ^ the version to install
-> PlatformRequest -- ^ the platform to install on
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, BuildFailed , BuildFailed
@@ -264,9 +266,11 @@ installGHCBin :: ( MonadFail m
] ]
m m
() ()
installGHCBin bDls ver pfreq = do installGHCBin ver = do
dlinfo <- lE $ getDownloadInfo GHC ver pfreq bDls AppState { pfreq
installGHCBindist dlinfo ver pfreq , ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
dlinfo <- lE $ getDownloadInfo GHC ver pfreq dls
installGHCBindist dlinfo ver
-- | Like 'installCabalBin', except takes the 'DownloadInfo' as -- | Like 'installCabalBin', except takes the 'DownloadInfo' as
@@ -282,7 +286,6 @@ installCabalBindist :: ( MonadMask m
) )
=> DownloadInfo => DownloadInfo
-> Version -> Version
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, CopyError , CopyError
@@ -298,27 +301,29 @@ installCabalBindist :: ( MonadMask m
] ]
m m
() ()
installCabalBindist dlinfo ver PlatformRequest {..} = do installCabalBindist dlinfo ver = do
lift $ $(logDebug) [i|Requested to install cabal version #{ver}|] lift $ $(logDebug) [i|Requested to install cabal version #{ver}|]
AppState {dirs = Dirs {..}} <- lift ask AppState { dirs = dirs@Dirs {..}
, pfreq = PlatformRequest {..}
, settings } <- lift ask
whenM whenM
(lift (cabalInstalled ver) >>= \a -> liftIO $ (lift (cabalInstalled ver) >>= \a -> liftIO $
handleIO (\_ -> pure False) handleIO (\_ -> pure False)
$ fmap (\x -> a && x) $ fmap (\x -> a && x)
-- ignore when the installation is a legacy cabal (binary, not symlink) -- ignore when the installation is a legacy cabal (binary, not symlink)
$ pathIsSymbolicLink (binDir </> "cabal" <> exeExt) $ pathIsLink (binDir </> "cabal" <> exeExt)
) )
(throwE $ AlreadyInstalled Cabal ver) (throwE $ AlreadyInstalled Cabal ver)
-- download (or use cached version) -- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing dl <- liftE $ downloadCached settings dirs dlinfo Nothing
-- unpack -- unpack
tmpUnpack <- lift withGHCupTmpDir tmpUnpack <- lift withGHCupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ lift $ darwinNotarization _rPlatform tmpUnpack
-- the subdir of the archive where we do the work -- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo) workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo)
@@ -360,9 +365,7 @@ installCabalBin :: ( MonadMask m
, MonadUnliftIO m , MonadUnliftIO m
, MonadFail m , MonadFail m
) )
=> GHCupDownloads => Version
-> Version
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, CopyError , CopyError
@@ -378,9 +381,11 @@ installCabalBin :: ( MonadMask m
] ]
m m
() ()
installCabalBin bDls ver pfreq = do installCabalBin ver = do
dlinfo <- lE $ getDownloadInfo Cabal ver pfreq bDls AppState { pfreq
installCabalBindist dlinfo ver pfreq , ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
dlinfo <- lE $ getDownloadInfo Cabal ver pfreq dls
installCabalBindist dlinfo ver
-- | Like 'installHLSBin, except takes the 'DownloadInfo' as -- | Like 'installHLSBin, except takes the 'DownloadInfo' as
@@ -396,7 +401,6 @@ installHLSBindist :: ( MonadMask m
) )
=> DownloadInfo => DownloadInfo
-> Version -> Version
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, CopyError , CopyError
@@ -412,21 +416,23 @@ installHLSBindist :: ( MonadMask m
] ]
m m
() ()
installHLSBindist dlinfo ver PlatformRequest{..} = do installHLSBindist dlinfo ver = do
lift $ $(logDebug) [i|Requested to install hls version #{ver}|] lift $ $(logDebug) [i|Requested to install hls version #{ver}|]
AppState {dirs = Dirs {..}} <- lift ask AppState { dirs = dirs@Dirs {..}
, pfreq = PlatformRequest {..}
, settings } <- lift ask
whenM (lift (hlsInstalled ver)) whenM (lift (hlsInstalled ver))
(throwE $ AlreadyInstalled HLS ver) (throwE $ AlreadyInstalled HLS ver)
-- download (or use cached version) -- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing dl <- liftE $ downloadCached settings dirs dlinfo Nothing
-- unpack -- unpack
tmpUnpack <- lift withGHCupTmpDir tmpUnpack <- lift withGHCupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ lift $ darwinNotarization _rPlatform tmpUnpack
-- the subdir of the archive where we do the work -- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo) workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo)
@@ -483,9 +489,7 @@ installHLSBin :: ( MonadMask m
, MonadUnliftIO m , MonadUnliftIO m
, MonadFail m , MonadFail m
) )
=> GHCupDownloads => Version
-> Version
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, CopyError , CopyError
@@ -501,9 +505,11 @@ installHLSBin :: ( MonadMask m
] ]
m m
() ()
installHLSBin bDls ver pfreq = do installHLSBin ver = do
dlinfo <- lE $ getDownloadInfo HLS ver pfreq bDls AppState { pfreq
installHLSBindist dlinfo ver pfreq , ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
dlinfo <- lE $ getDownloadInfo HLS ver pfreq dls
installHLSBindist dlinfo ver
-- | Installs stack into @~\/.ghcup\/bin/stack-\<ver\>@ and -- | Installs stack into @~\/.ghcup\/bin/stack-\<ver\>@ and
@@ -518,9 +524,7 @@ installStackBin :: ( MonadMask m
, MonadUnliftIO m , MonadUnliftIO m
, MonadFail m , MonadFail m
) )
=> GHCupDownloads => Version
-> Version
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, CopyError , CopyError
@@ -536,9 +540,10 @@ installStackBin :: ( MonadMask m
] ]
m m
() ()
installStackBin bDls ver pfreq = do installStackBin ver = do
dlinfo <- lE $ getDownloadInfo Stack ver pfreq bDls AppState { pfreq, ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- lift ask
installStackBindist dlinfo ver pfreq dlinfo <- lE $ getDownloadInfo Stack ver pfreq dls
installStackBindist dlinfo ver
-- | Like 'installStackBin', except takes the 'DownloadInfo' as -- | Like 'installStackBin', except takes the 'DownloadInfo' as
@@ -554,7 +559,6 @@ installStackBindist :: ( MonadMask m
) )
=> DownloadInfo => DownloadInfo
-> Version -> Version
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, CopyError , CopyError
@@ -570,21 +574,24 @@ installStackBindist :: ( MonadMask m
] ]
m m
() ()
installStackBindist dlinfo ver PlatformRequest {..} = do installStackBindist dlinfo ver = do
lift $ $(logDebug) [i|Requested to install stack version #{ver}|] lift $ $(logDebug) [i|Requested to install stack version #{ver}|]
AppState {dirs = Dirs {..}} <- lift ask AppState { dirs = dirs@Dirs {..}
, pfreq = PlatformRequest {..}
, settings
} <- lift ask
whenM (lift (hlsInstalled ver)) whenM (lift (hlsInstalled ver))
(throwE $ AlreadyInstalled Stack ver) (throwE $ AlreadyInstalled Stack ver)
-- download (or use cached version) -- download (or use cached version)
dl <- liftE $ downloadCached dlinfo Nothing dl <- liftE $ downloadCached settings dirs dlinfo Nothing
-- unpack -- unpack
tmpUnpack <- lift withGHCupTmpDir tmpUnpack <- lift withGHCupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ lift $ darwinNotarization _rPlatform tmpUnpack
-- the subdir of the archive where we do the work -- the subdir of the archive where we do the work
workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo) workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo)
@@ -637,6 +644,8 @@ setGHC :: ( MonadReader AppState m
, MonadFail m , MonadFail m
, MonadIO m , MonadIO m
, MonadCatch m , MonadCatch m
, MonadMask m
, MonadUnliftIO m
) )
=> GHCTargetVersion => GHCTargetVersion
-> SetGHC -> SetGHC
@@ -678,10 +687,7 @@ setGHC ver sghc = do
let fullF = binDir </> targetFile <> exeExt let fullF = binDir </> targetFile <> exeExt
fileWithExt = file <> exeExt fileWithExt = file <> exeExt
destL <- lift $ ghcLinkDestination fileWithExt ver destL <- lift $ ghcLinkDestination fileWithExt ver
lift $ $(logDebug) [i|rm -f #{fullF}|] lift $ createLink destL fullF
liftIO $ hideError doesNotExistErrorType $ removeFile fullF
lift $ $(logDebug) [i|ln -s #{destL} #{fullF}|]
liftIO $ createFileLink destL fullF
-- create symlink for share dir -- create symlink for share dir
when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verS when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verS
@@ -705,15 +711,29 @@ setGHC ver sghc = do
let fullF = destdir </> sharedir let fullF = destdir </> sharedir
let targetF = "." </> "ghc" </> ver' </> sharedir let targetF = "." </> "ghc" </> ver' </> sharedir
$(logDebug) [i|rm -f #{fullF}|] $(logDebug) [i|rm -f #{fullF}|]
liftIO $ hideError doesNotExistErrorType $ removeFile fullF liftIO $ hideError doesNotExistErrorType $ removeDirectoryLink fullF
$(logDebug) [i|ln -s #{targetF} #{fullF}|] $(logDebug) [i|ln -s #{targetF} #{fullF}|]
liftIO $ createDirectoryLink targetF fullF 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
_ -> pure () _ -> pure ()
-- | Set the @~\/.ghcup\/bin\/cabal@ symlink. -- | Set the @~\/.ghcup\/bin\/cabal@ symlink.
setCabal :: (MonadReader AppState m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) setCabal :: ( MonadMask m
, MonadReader AppState m
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadUnliftIO m)
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
setCabal ver = do setCabal ver = do
@@ -729,15 +749,9 @@ setCabal ver = do
let cabalbin = binDir </> "cabal" <> exeExt let cabalbin = binDir </> "cabal" <> exeExt
-- delete old file (may be binary or symlink) -- create link
lift $ $(logDebug) [i|rm -f #{cabalbin}|]
liftIO $ hideError doesNotExistErrorType $ removeFile
cabalbin
-- create symlink
let destL = targetFile let destL = targetFile
lift $ $(logDebug) [i|ln -s #{destL} #{cabalbin}|] lift $ createLink destL cabalbin
liftIO $ createFileLink destL cabalbin
pure () pure ()
@@ -751,6 +765,8 @@ setHLS :: ( MonadCatch m
, MonadThrow m , MonadThrow m
, MonadFail m , MonadFail m
, MonadIO m , MonadIO m
, MonadMask m
, MonadUnliftIO m
) )
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
@@ -763,7 +779,7 @@ setHLS ver = do
oldSyms <- lift hlsSymlinks oldSyms <- lift hlsSymlinks
forM_ oldSyms $ \f -> do forM_ oldSyms $ \f -> do
lift $ $(logDebug) [i|rm #{binDir </> f}|] lift $ $(logDebug) [i|rm #{binDir </> f}|]
liftIO $ removeFile (binDir </> f) liftIO $ rmLink (binDir </> f)
-- set haskell-language-server-<ghcver> symlinks -- set haskell-language-server-<ghcver> symlinks
bins <- lift $ hlsServerBinaries ver bins <- lift $ hlsServerBinaries ver
@@ -772,28 +788,26 @@ setHLS ver = do
forM_ bins $ \f -> do forM_ bins $ \f -> do
let destL = f let destL = f
let target = (<> exeExt) . head . splitOn "~" $ f let target = (<> exeExt) . head . splitOn "~" $ f
lift $ createLink destL (binDir </> target)
lift $ $(logDebug) [i|rm -f #{binDir </> target}|]
liftIO $ hideError doesNotExistErrorType $ removeFile (binDir </> target)
lift $ $(logDebug) [i|ln -s #{destL} #{binDir </> target}|]
liftIO $ createFileLink destL (binDir </> target)
-- set haskell-language-server-wrapper symlink -- set haskell-language-server-wrapper symlink
let destL = "haskell-language-server-wrapper-" <> T.unpack (prettyVer ver) <> exeExt let destL = "haskell-language-server-wrapper-" <> T.unpack (prettyVer ver) <> exeExt
let wrapper = binDir </> "haskell-language-server-wrapper" <> exeExt let wrapper = binDir </> "haskell-language-server-wrapper" <> exeExt
lift $ $(logDebug) [i|rm -f #{wrapper}|] lift $ createLink destL wrapper
liftIO $ hideError doesNotExistErrorType $ removeFile wrapper
lift $ $(logDebug) [i|ln -s #{destL} #{wrapper}|]
liftIO $ createFileLink destL wrapper
pure () pure ()
-- | Set the @~\/.ghcup\/bin\/stack@ symlink. -- | Set the @~\/.ghcup\/bin\/stack@ symlink.
setStack :: (MonadReader AppState m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) setStack :: ( MonadMask m
, MonadReader AppState m
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadUnliftIO m
)
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
setStack ver = do setStack ver = do
@@ -809,14 +823,7 @@ setStack ver = do
let stackbin = binDir </> "stack" <> exeExt let stackbin = binDir </> "stack" <> exeExt
-- delete old file (may be binary or symlink) lift $ createLink targetFile stackbin
lift $ $(logDebug) [i|rm -f #{stackbin}|]
liftIO $ hideError doesNotExistErrorType $ removeFile
stackbin
-- create symlink
lift $ $(logDebug) [i|ln -s #{targetFile} #{stackbin}|]
liftIO $ createFileLink targetFile stackbin
pure () pure ()
@@ -865,12 +872,10 @@ listVersions :: ( MonadCatch m
, MonadIO m , MonadIO m
, MonadReader AppState m , MonadReader AppState m
) )
=> GHCupDownloads => Maybe Tool
-> Maybe Tool
-> Maybe ListCriteria -> Maybe ListCriteria
-> PlatformRequest
-> m [ListResult] -> m [ListResult]
listVersions av lt' criteria pfreq = do listVersions lt' criteria = do
-- some annoying work to avoid too much repeated IO -- some annoying work to avoid too much repeated IO
cSet <- cabalSet cSet <- cabalSet
cabals <- getInstalledCabals' cSet cabals <- getInstalledCabals' cSet
@@ -884,8 +889,9 @@ listVersions av lt' criteria pfreq = do
go lt cSet cabals hlsSet' hlses sSet stacks = do go lt cSet cabals hlsSet' hlses sSet stacks = do
case lt of case lt of
Just t -> do Just t -> do
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
-- get versions from GHCupDownloads -- get versions from GHCupDownloads
let avTools = availableToolVersions av t let avTools = availableToolVersions dls t
lr <- filter' <$> forM (Map.toList avTools) (toListResult t cSet cabals hlsSet' hlses sSet stacks) lr <- filter' <$> forM (Map.toList avTools) (toListResult t cSet cabals hlsSet' hlses sSet stacks)
case t of case t of
@@ -1047,67 +1053,71 @@ listVersions av lt' criteria pfreq = do
-> [Either FilePath Version] -> [Either FilePath Version]
-> (Version, [Tag]) -> (Version, [Tag])
-> m ListResult -> m ListResult
toListResult t cSet cabals hlsSet' hlses stackSet' stacks (v, tags) = case t of toListResult t cSet cabals hlsSet' hlses stackSet' stacks (v, tags) = do
GHC -> do AppState { pfreq
let lNoBindist = isLeft $ getDownloadInfo GHC v pfreq av , ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
let tver = mkTVer v
lSet <- fmap (maybe False (\(GHCTargetVersion _ v') -> v' == v)) $ ghcSet Nothing case t of
lInstalled <- ghcInstalled tver GHC -> do
fromSrc <- ghcSrcInstalled tver let lNoBindist = isLeft $ getDownloadInfo GHC v pfreq dls
hlsPowered <- fmap (elem v) hlsGHCVersions let tver = mkTVer v
pure ListResult { lVer = v, lCross = Nothing , lTag = tags, lTool = t, lStray = False, .. } lSet <- fmap (maybe False (\(GHCTargetVersion _ v') -> v' == v)) $ ghcSet Nothing
Cabal -> do lInstalled <- ghcInstalled tver
let lNoBindist = isLeft $ getDownloadInfo Cabal v pfreq av fromSrc <- ghcSrcInstalled tver
let lSet = cSet == Just v hlsPowered <- fmap (elem v) hlsGHCVersions
let lInstalled = elem v $ rights cabals pure ListResult { lVer = v, lCross = Nothing , lTag = tags, lTool = t, lStray = False, .. }
pure ListResult { lVer = v Cabal -> do
, lCross = Nothing let lNoBindist = isLeft $ getDownloadInfo Cabal v pfreq dls
, lTag = tags let lSet = cSet == Just v
, lTool = t let lInstalled = elem v $ rights cabals
, fromSrc = False pure ListResult { lVer = v
, lStray = False , lCross = Nothing
, hlsPowered = False , lTag = tags
, .. , lTool = t
} , fromSrc = False
GHCup -> do , lStray = False
let lSet = prettyPVP ghcUpVer == prettyVer v , hlsPowered = False
let lInstalled = lSet , ..
pure ListResult { lVer = v }
, lTag = tags GHCup -> do
, lCross = Nothing let lSet = prettyPVP ghcUpVer == prettyVer v
, lTool = t let lInstalled = lSet
, fromSrc = False pure ListResult { lVer = v
, lStray = False , lTag = tags
, lNoBindist = False , lCross = Nothing
, hlsPowered = False , lTool = t
, .. , fromSrc = False
} , lStray = False
HLS -> do , lNoBindist = False
let lNoBindist = isLeft $ getDownloadInfo HLS v pfreq av , hlsPowered = False
let lSet = hlsSet' == Just v , ..
let lInstalled = elem v $ rights hlses }
pure ListResult { lVer = v HLS -> do
, lCross = Nothing let lNoBindist = isLeft $ getDownloadInfo HLS v pfreq dls
, lTag = tags let lSet = hlsSet' == Just v
, lTool = t let lInstalled = elem v $ rights hlses
, fromSrc = False pure ListResult { lVer = v
, lStray = False , lCross = Nothing
, hlsPowered = False , lTag = tags
, .. , lTool = t
} , fromSrc = False
Stack -> do , lStray = False
let lNoBindist = isLeft $ getDownloadInfo Stack v pfreq av , hlsPowered = False
let lSet = stackSet' == Just v , ..
let lInstalled = elem v $ rights stacks }
pure ListResult { lVer = v Stack -> do
, lCross = Nothing let lNoBindist = isLeft $ getDownloadInfo Stack v pfreq dls
, lTag = tags let lSet = stackSet' == Just v
, lTool = t let lInstalled = elem v $ rights stacks
, fromSrc = False pure ListResult { lVer = v
, lStray = False , lCross = Nothing
, hlsPowered = False , lTag = tags
, .. , lTool = t
} , fromSrc = False
, lStray = False
, hlsPowered = False
, ..
}
filter' :: [ListResult] -> [ListResult] filter' :: [ListResult] -> [ListResult]
@@ -1134,6 +1144,8 @@ rmGHCVer :: ( MonadReader AppState m
, MonadIO m , MonadIO m
, MonadFail m , MonadFail m
, MonadCatch m , MonadCatch m
, MonadMask m
, MonadUnliftIO m
) )
=> GHCTargetVersion => GHCTargetVersion
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
@@ -1157,7 +1169,7 @@ rmGHCVer ver = do
-- then fix them (e.g. with an earlier version) -- then fix them (e.g. with an earlier version)
lift $ $(logInfo) [i|Removing directory recursively: #{dir}|] lift $ $(logInfo) [i|Removing directory recursively: #{dir}|]
liftIO $ removeDirectoryRecursive dir liftIO $ rmPath dir
v' <- v' <-
handle handle
@@ -1171,12 +1183,20 @@ rmGHCVer ver = do
liftIO liftIO
$ hideError doesNotExistErrorType $ hideError doesNotExistErrorType
$ removeFile (baseDir </> "share") $ rmFile (baseDir </> "share")
-- | Delete a cabal version. Will try to fix the @cabal@ symlink -- | Delete a cabal version. Will try to fix the @cabal@ symlink
-- after removal (e.g. setting it to an older version). -- after removal (e.g. setting it to an older version).
rmCabalVer :: (MonadReader AppState m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m, MonadCatch m) rmCabalVer :: ( MonadMask m
, MonadReader AppState m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
, MonadUnliftIO m
)
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
rmCabalVer ver = do rmCabalVer ver = do
@@ -1187,19 +1207,26 @@ rmCabalVer ver = do
AppState {dirs = Dirs {..}} <- lift ask AppState {dirs = Dirs {..}} <- lift ask
let cabalFile = "cabal-" <> T.unpack (prettyVer ver) <> exeExt let cabalFile = "cabal-" <> T.unpack (prettyVer ver) <> exeExt
liftIO $ hideError doesNotExistErrorType $ removeFile (binDir </> cabalFile) liftIO $ hideError doesNotExistErrorType $ rmFile (binDir </> cabalFile)
when (Just ver == cSet) $ do when (Just ver == cSet) $ do
cVers <- lift $ fmap rights getInstalledCabals cVers <- lift $ fmap rights getInstalledCabals
case headMay . reverse . sort $ cVers of case headMay . reverse . sort $ cVers of
Just latestver -> setCabal latestver Just latestver -> setCabal latestver
Nothing -> liftIO $ hideError doesNotExistErrorType $ removeFile Nothing -> liftIO $ rmLink (binDir </> "cabal" <> exeExt)
(binDir </> "cabal" <> exeExt)
-- | Delete a hls version. Will try to fix the hls symlinks -- | Delete a hls version. Will try to fix the hls symlinks
-- after removal (e.g. setting it to an older version). -- after removal (e.g. setting it to an older version).
rmHLSVer :: (MonadReader AppState m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m, MonadCatch m) rmHLSVer :: ( MonadMask m
, MonadReader AppState m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
, MonadUnliftIO m
)
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
rmHLSVer ver = do rmHLSVer ver = do
@@ -1210,15 +1237,15 @@ rmHLSVer ver = do
AppState {dirs = Dirs {..}} <- lift ask AppState {dirs = Dirs {..}} <- lift ask
bins <- lift $ hlsAllBinaries ver bins <- lift $ hlsAllBinaries ver
forM_ bins $ \f -> liftIO $ removeFile (binDir </> f) forM_ bins $ \f -> liftIO $ rmFile (binDir </> f)
when (Just ver == isHlsSet) $ do when (Just ver == isHlsSet) $ do
-- delete all set symlinks -- delete all set symlinks
oldSyms <- lift hlsSymlinks oldSyms <- lift hlsSymlinks
forM_ oldSyms $ \f -> do forM_ oldSyms $ \f -> do
let fullF = binDir </> f <> exeExt let fullF = binDir </> f
lift $ $(logDebug) [i|rm #{fullF}|] lift $ $(logDebug) [i|rm #{fullF}|]
liftIO $ removeFile fullF liftIO $ rmLink fullF
-- set latest hls -- set latest hls
hlsVers <- lift $ fmap rights getInstalledHLSs hlsVers <- lift $ fmap rights getInstalledHLSs
case headMay . reverse . sort $ hlsVers of case headMay . reverse . sort $ hlsVers of
@@ -1228,7 +1255,15 @@ rmHLSVer ver = do
-- | Delete a stack version. Will try to fix the @stack@ symlink -- | Delete a stack version. Will try to fix the @stack@ symlink
-- after removal (e.g. setting it to an older version). -- after removal (e.g. setting it to an older version).
rmStackVer :: (MonadReader AppState m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m, MonadCatch m) rmStackVer :: ( MonadMask m
, MonadReader AppState m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadFail m
, MonadCatch m
, MonadUnliftIO m
)
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
rmStackVer ver = do rmStackVer ver = do
@@ -1239,14 +1274,13 @@ rmStackVer ver = do
AppState {dirs = Dirs {..}} <- lift ask AppState {dirs = Dirs {..}} <- lift ask
let stackFile = "stack-" <> T.unpack (prettyVer ver) <> exeExt let stackFile = "stack-" <> T.unpack (prettyVer ver) <> exeExt
liftIO $ hideError doesNotExistErrorType $ removeFile (binDir </> stackFile) liftIO $ hideError doesNotExistErrorType $ rmFile (binDir </> stackFile)
when (Just ver == sSet) $ do when (Just ver == sSet) $ do
sVers <- lift $ fmap rights getInstalledStacks sVers <- lift $ fmap rights getInstalledStacks
case headMay . reverse . sort $ sVers of case headMay . reverse . sort $ sVers of
Just latestver -> setStack latestver Just latestver -> setStack latestver
Nothing -> liftIO $ hideError doesNotExistErrorType $ removeFile Nothing -> liftIO $ rmLink (binDir </> "stack" <> exeExt)
(binDir </> "stack" <> exeExt)
@@ -1255,7 +1289,7 @@ rmStackVer ver = do
------------------ ------------------
getDebugInfo :: (MonadReader AppState m, MonadLogger m, MonadCatch m, MonadIO m) getDebugInfo :: (Alternative m, MonadFail m, MonadReader AppState m, MonadLogger m, MonadCatch m, MonadIO m)
=> Excepts => Excepts
'[NoCompatiblePlatform , NoCompatibleArch , DistroNotFound] '[NoCompatiblePlatform , NoCompatibleArch , DistroNotFound]
m m
@@ -1289,14 +1323,12 @@ compileGHC :: ( MonadMask m
, MonadUnliftIO m , MonadUnliftIO m
, MonadFail m , MonadFail m
) )
=> GHCupDownloads => Either GHCTargetVersion GitBranch -- ^ version to install
-> Either GHCTargetVersion GitBranch -- ^ version to install
-> Either Version FilePath -- ^ version to bootstrap with -> Either Version FilePath -- ^ version to bootstrap with
-> Maybe Int -- ^ jobs -> Maybe Int -- ^ jobs
-> Maybe FilePath -- ^ build config -> Maybe FilePath -- ^ build config
-> Maybe FilePath -- ^ patch directory -> Maybe FilePath -- ^ patch directory
-> [Text] -- ^ additional args to ./configure -> [Text] -- ^ additional args to ./configure
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, BuildFailed , BuildFailed
@@ -1315,8 +1347,12 @@ compileGHC :: ( MonadMask m
] ]
m m
GHCTargetVersion GHCTargetVersion
compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformRequest{..} compileGHC targetGhc bstrap jobs mbuildConfig patchdir aargs
= do = do
AppState { pfreq = PlatformRequest {..}
, ghcupInfo = GHCupInfo { _ghcupDownloads = dls }
, settings
, dirs } <- lift ask
(workdir, tmpUnpack, tver) <- case targetGhc of (workdir, tmpUnpack, tver) <- case targetGhc of
-- unpack from version tarball -- unpack from version tarball
Left tver -> do Left tver -> do
@@ -1326,12 +1362,12 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
dlInfo <- dlInfo <-
preview (ix GHC % ix (tver ^. tvVersion) % viSourceDL % _Just) dls preview (ix GHC % ix (tver ^. tvVersion) % viSourceDL % _Just) dls
?? NoDownload ?? NoDownload
dl <- liftE $ downloadCached dlInfo Nothing dl <- liftE $ downloadCached settings dirs dlInfo Nothing
-- unpack -- unpack
tmpUnpack <- lift mkGhcupTmpDir tmpUnpack <- lift mkGhcupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ lift $ darwinNotarization _rPlatform tmpUnpack
workdir <- maybe (pure tmpUnpack) workdir <- maybe (pure tmpUnpack)
(liftE . intoSubdir tmpUnpack) (liftE . intoSubdir tmpUnpack)
@@ -1365,13 +1401,13 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
lEM $ git [ "submodule", "update", "--init", "--depth", "1" ] lEM $ git [ "submodule", "update", "--init", "--depth", "1" ]
lEM $ execLogged "sh" ["./boot"] (Just tmpUnpack) "ghc-bootstrap" Nothing lEM $ execLogged "sh" ["./boot"] (Just tmpUnpack) "ghc-bootstrap" Nothing
lEM $ execLogged "sh" ["./configure"] (Just tmpUnpack) "ghc-bootstrap" Nothing lEM $ execLogged "sh" ["./configure"] (Just tmpUnpack) "ghc-bootstrap" Nothing
CapturedProcess {..} <- liftIO $ makeOut CapturedProcess {..} <- lift $ makeOut
["show!", "--quiet", "VALUE=ProjectVersion" ] (Just tmpUnpack) ["show!", "--quiet", "VALUE=ProjectVersion" ] (Just tmpUnpack)
case _exitCode of case _exitCode of
ExitSuccess -> throwEither . MP.parse ghcProjectVersion "" . decUTF8Safe' $ _stdOut 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)) ExitFailure c -> fail ("Could not figure out GHC project version. Exit code was: " <> show c <> ". Error was: " <> T.unpack (decUTF8Safe' _stdErr))
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ lift $ darwinNotarization _rPlatform tmpUnpack
lift $ $(logInfo) [i|Git version #{ref} corresponds to GHC version #{prettyVer tver}|] lift $ $(logInfo) [i|Git version #{ref} corresponds to GHC version #{prettyVer tver}|]
pure (tmpUnpack, tmpUnpack, GHCTargetVersion Nothing tver) pure (tmpUnpack, tmpUnpack, GHCTargetVersion Nothing tver)
@@ -1388,7 +1424,7 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
bghc <- case bstrap of bghc <- case bstrap of
Right g -> pure $ Right g Right g -> pure $ Right g
Left bver -> pure $ Left ("ghc-" <> (T.unpack . prettyVer $ bver)) Left bver -> pure $ Left ("ghc-" <> (T.unpack . prettyVer $ bver) <> exeExt)
(bindist, bmk) <- liftE $ runBuildAction (bindist, bmk) <- liftE $ runBuildAction
tmpUnpack tmpUnpack
@@ -1406,7 +1442,6 @@ compileGHC dls targetGhc bstrap jobs mbuildConfig patchdir aargs pfreq@PlatformR
(Just $ RegexDir "ghc-.*") (Just $ RegexDir "ghc-.*")
ghcdir ghcdir
(tver ^. tvVersion) (tver ^. tvVersion)
pfreq
liftIO $ B.writeFile (ghcdir </> ghcUpSrcBuiltFile) bmk liftIO $ B.writeFile (ghcdir </> ghcUpSrcBuiltFile) bmk
@@ -1451,7 +1486,7 @@ HADDOCK_DOCS = YES|]
lift $ $(logInfo) [i|configuring build|] lift $ $(logInfo) [i|configuring build|]
liftE checkBuildConfig liftE checkBuildConfig
AppState { dirs = Dirs {..} } <- lift ask AppState { dirs = Dirs {..}, pfreq } <- lift ask
forM_ patchdir $ \dir -> liftE $ applyPatches dir workdir forM_ patchdir $ \dir -> liftE $ applyPatches dir workdir
@@ -1468,6 +1503,9 @@ HADDOCK_DOCS = YES|]
("./configure" : maybe mempty ("./configure" : maybe mempty
(\x -> ["--target=" <> T.unpack x]) (\x -> ["--target=" <> T.unpack x])
(_tvTarget tver) (_tvTarget tver)
#if defined(IS_WINDOWS)
++ ["--enable-tarballs-autodownload"]
#endif
++ fmap T.unpack aargs ++ fmap T.unpack aargs
) )
(Just workdir) (Just workdir)
@@ -1481,6 +1519,9 @@ HADDOCK_DOCS = YES|]
++ maybe mempty ++ maybe mempty
(\x -> ["--target=" <> T.unpack x]) (\x -> ["--target=" <> T.unpack x])
(_tvTarget tver) (_tvTarget tver)
#if defined(IS_WINDOWS)
++ ["--enable-tarballs-autodownload"]
#endif
++ fmap T.unpack aargs ++ fmap T.unpack aargs
) )
(Just workdir) (Just workdir)
@@ -1516,7 +1557,7 @@ HADDOCK_DOCS = YES|]
. SHA256.hashlazy . SHA256.hashlazy
$ c $ c
cTime <- liftIO getCurrentTime cTime <- liftIO getCurrentTime
let tarName = [i|ghc-#{tVerToText tver}-#{pfReqToString pfreq}-#{iso8601Show cTime}-#{cDigest}.tar#{takeExtension tar}|] let tarName = makeValid [i|ghc-#{tVerToText tver}-#{pfReqToString pfreq}-#{iso8601Show cTime}-#{cDigest}.tar#{takeExtension tar}|]
let tarPath = cacheDir </> tarName let tarPath = cacheDir </> tarName
handleIO (throwE . CopyError . show) $ liftIO $ copyFile (workdir </> tar) handleIO (throwE . CopyError . show) $ liftIO $ copyFile (workdir </> tar)
tarPath tarPath
@@ -1567,11 +1608,9 @@ upgradeGHCup :: ( MonadMask m
, MonadIO m , MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
) )
=> GHCupDownloads => Maybe FilePath -- ^ full file destination to write ghcup into
-> Maybe FilePath -- ^ full file destination to write ghcup into
-> Bool -- ^ whether to force update regardless -> Bool -- ^ whether to force update regardless
-- of currently installed version -- of currently installed version
-> PlatformRequest
-> Excepts -> Excepts
'[ CopyError '[ CopyError
, DigestError , DigestError
@@ -1581,21 +1620,24 @@ upgradeGHCup :: ( MonadMask m
] ]
m m
Version Version
upgradeGHCup dls mtarget force pfreq = do upgradeGHCup mtarget force = do
AppState {dirs = Dirs {..}} <- lift ask AppState { dirs = Dirs {..}
, pfreq
, ghcupInfo = GHCupInfo { _ghcupDownloads = dls }
, settings } <- lift ask
lift $ $(logInfo) [i|Upgrading GHCup...|] lift $ $(logInfo) [i|Upgrading GHCup...|]
let latestVer = fromJust $ fst <$> getLatest dls GHCup let latestVer = fromJust $ fst <$> getLatest dls GHCup
when (not force && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate when (not force && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate
dli <- lE $ getDownloadInfo GHCup latestVer pfreq dls dli <- lE $ getDownloadInfo GHCup latestVer pfreq dls
tmp <- lift withGHCupTmpDir tmp <- lift withGHCupTmpDir
let fn = "ghcup" <> exeExt let fn = "ghcup" <> exeExt
p <- liftE $ download dli tmp (Just fn) p <- liftE $ download settings dli tmp (Just fn)
let destDir = takeDirectory destFile let destDir = takeDirectory destFile
destFile = fromMaybe (binDir </> fn) mtarget destFile = fromMaybe (binDir </> fn) mtarget
lift $ $(logDebug) [i|mkdir -p #{destDir}|] lift $ $(logDebug) [i|mkdir -p #{destDir}|]
liftIO $ createDirRecursive' destDir liftIO $ createDirRecursive' destDir
lift $ $(logDebug) [i|rm -f #{destFile}|] lift $ $(logDebug) [i|rm -f #{destFile}|]
liftIO $ hideError NoSuchThing $ removeFile destFile liftIO $ hideError NoSuchThing $ rmFile destFile
lift $ $(logDebug) [i|cp #{p} #{destFile}|] lift $ $(logDebug) [i|cp #{p} #{destFile}|]
handleIO (throwE . CopyError . show) $ liftIO $ copyFile p handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
destFile destFile
@@ -1624,6 +1666,8 @@ postGHCInstall :: ( MonadReader AppState m
, MonadFail m , MonadFail m
, MonadIO m , MonadIO m
, MonadCatch m , MonadCatch m
, MonadMask m
, MonadUnliftIO m
) )
=> GHCTargetVersion => GHCTargetVersion
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()

View File

@@ -36,7 +36,7 @@ import GHCup.Errors
import GHCup.Types import GHCup.Types
import GHCup.Types.JSON ( ) import GHCup.Types.JSON ( )
import GHCup.Types.Optics import GHCup.Types.Optics
import GHCup.Utils import GHCup.Utils.Dirs
import GHCup.Utils.File import GHCup.Utils.File
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import GHCup.Version import GHCup.Version
@@ -53,8 +53,8 @@ import Control.Monad.Trans.Resource
hiding ( throwM ) hiding ( throwM )
import Data.Aeson import Data.Aeson
import Data.Bifunctor import Data.Bifunctor
#if defined(INTERNAL_DOWNLOADER)
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
#if defined(INTERNAL_DOWNLOADER)
import Data.CaseInsensitive ( CI ) import Data.CaseInsensitive ( CI )
#endif #endif
import Data.List.Extra import Data.List.Extra
@@ -66,6 +66,7 @@ import Data.Time.Clock.POSIX
import Data.Time.Format import Data.Time.Format
#endif #endif
import Data.Versions import Data.Versions
import Data.Word8
import GHC.IO.Exception import GHC.IO.Exception
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Optics import Optics
@@ -80,6 +81,7 @@ import System.IO.Error
import URI.ByteString import URI.ByteString
import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.SHA256 as SHA256
import qualified Data.ByteString as B
import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
@@ -110,26 +112,26 @@ getDownloadsF :: ( FromJSONKey Tool
, MonadLogger m , MonadLogger m
, MonadThrow m , MonadThrow m
, MonadFail m , MonadFail m
, MonadReader AppState m
) )
=> URLSource => Settings
-> Dirs
-> Excepts -> Excepts
'[JSONError , DownloadFailed , FileDoesNotExistError] '[JSONError , DownloadFailed , FileDoesNotExistError]
m m
GHCupInfo GHCupInfo
getDownloadsF urlSource = do getDownloadsF settings@Settings{ urlSource } dirs = do
case urlSource of case urlSource of
GHCupURL -> liftE getBase GHCupURL -> liftE $ getBase dirs settings
(OwnSource url) -> do (OwnSource url) -> do
bs <- reThrowAll DownloadFailed $ downloadBS url bs <- reThrowAll DownloadFailed $ downloadBS (downloader settings) url
lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bs) lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bs)
(OwnSpec av) -> pure av (OwnSpec av) -> pure av
(AddSource (Left ext)) -> do (AddSource (Left ext)) -> do
base <- liftE getBase base <- liftE $ getBase dirs settings
pure (mergeGhcupInfo base ext) pure (mergeGhcupInfo base ext)
(AddSource (Right uri)) -> do (AddSource (Right uri)) -> do
base <- liftE getBase base <- liftE $ getBase dirs settings
bsExt <- reThrowAll DownloadFailed $ downloadBS uri bsExt <- reThrowAll DownloadFailed $ downloadBS (downloader settings) uri
ext <- lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bsExt) ext <- lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bsExt)
pure (mergeGhcupInfo base ext) pure (mergeGhcupInfo base ext)
@@ -138,18 +140,19 @@ getDownloadsF urlSource = do
mergeGhcupInfo :: GHCupInfo -- ^ base to merge with mergeGhcupInfo :: GHCupInfo -- ^ base to merge with
-> GHCupInfo -- ^ extension overwriting the base -> GHCupInfo -- ^ extension overwriting the base
-> GHCupInfo -> GHCupInfo
mergeGhcupInfo (GHCupInfo tr base) (GHCupInfo _ ext) = mergeGhcupInfo (GHCupInfo tr base base2) (GHCupInfo _ ext ext2) =
let new = M.mapWithKey (\k a -> case M.lookup k ext of let newDownloads = M.mapWithKey (\k a -> case M.lookup k ext of
Just a' -> M.union a' a Just a' -> M.union a' a
Nothing -> a Nothing -> a
) base ) base
in GHCupInfo tr new newGlobalTools = M.union base2 ext2
in GHCupInfo tr newDownloads newGlobalTools
readFromCache :: (MonadIO m, MonadCatch m, MonadLogger m, MonadReader AppState m) readFromCache :: (MonadIO m, MonadCatch m, MonadLogger m)
=> Excepts '[JSONError, FileDoesNotExistError] m GHCupInfo => Dirs
readFromCache = do -> Excepts '[JSONError, FileDoesNotExistError] m GHCupInfo
AppState {dirs = Dirs {..}} <- lift ask readFromCache Dirs {..} = do
lift $ $(logWarn) lift $ $(logWarn)
[i|Could not get download info, trying cached version (this may not be recent!)|] [i|Could not get download info, trying cached version (this may not be recent!)|]
let path = view pathL' ghcupURL let path = view pathL' ghcupURL
@@ -162,12 +165,14 @@ readFromCache = do
lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bs) lE' JSONDecodeError $ first show $ Y.decodeEither' (L.toStrict bs)
getBase :: (MonadFail m, MonadIO m, MonadCatch m, MonadLogger m, MonadReader AppState m) getBase :: (MonadFail m, MonadIO m, MonadCatch m, MonadLogger m)
=> Excepts '[JSONError , FileDoesNotExistError] m GHCupInfo => Dirs
getBase = -> Settings
handleIO (\_ -> readFromCache) -> Excepts '[JSONError , FileDoesNotExistError] m GHCupInfo
getBase dirs@Dirs{..} Settings{ downloader } =
handleIO (\_ -> readFromCache dirs)
$ catchE @_ @'[JSONError, FileDoesNotExistError] $ catchE @_ @'[JSONError, FileDoesNotExistError]
(\(DownloadFailed _) -> readFromCache) (\(DownloadFailed _) -> readFromCache dirs)
(reThrowAll @_ @_ @'[JSONError, DownloadFailed] DownloadFailed (smartDl ghcupURL) (reThrowAll @_ @_ @'[JSONError, DownloadFailed] DownloadFailed (smartDl ghcupURL)
>>= (liftE . lE' @_ @_ @'[JSONError] JSONDecodeError . first show . Y.decodeEither' . L.toStrict)) >>= (liftE . lE' @_ @_ @'[JSONError] JSONDecodeError . first show . Y.decodeEither' . L.toStrict))
where where
@@ -185,7 +190,6 @@ getBase =
, MonadIO m1 , MonadIO m1
, MonadFail m1 , MonadFail m1
, MonadLogger m1 , MonadLogger m1
, MonadReader AppState m1
) )
=> URI => URI
-> Excepts -> Excepts
@@ -200,7 +204,6 @@ getBase =
m1 m1
L.ByteString L.ByteString
smartDl uri' = do smartDl uri' = do
AppState {dirs = Dirs {..}} <- lift ask
let path = view pathL' uri' let path = view pathL' uri'
let json_file = cacheDir </> (T.unpack . decUTF8Safe . urlBaseName $ path) let json_file = cacheDir </> (T.unpack . decUTF8Safe . urlBaseName $ path)
e <- liftIO $ doesFileExist json_file e <- liftIO $ doesFileExist json_file
@@ -235,12 +238,12 @@ getBase =
where where
dlWithMod modTime json_file = do dlWithMod modTime json_file = do
bs <- liftE $ downloadBS uri' bs <- liftE $ downloadBS downloader uri'
liftIO $ writeFileWithModTime modTime json_file bs liftIO $ writeFileWithModTime modTime json_file bs
pure bs pure bs
dlWithoutMod json_file = do dlWithoutMod json_file = do
bs <- liftE $ downloadBS uri' bs <- liftE $ downloadBS downloader uri'
liftIO $ hideError doesNotExistErrorType $ removeFile json_file liftIO $ hideError doesNotExistErrorType $ rmFile json_file
liftIO $ L.writeFile json_file bs liftIO $ L.writeFile json_file bs
liftIO $ setModificationTime json_file (posixSecondsToUTCTime (fromIntegral @Int 0)) liftIO $ setModificationTime json_file (posixSecondsToUTCTime (fromIntegral @Int 0))
pure bs pure bs
@@ -320,16 +323,16 @@ getDownloadInfo t v (PlatformRequest a p mv) dls = maybe
-- --
-- The file must not exist. -- The file must not exist.
download :: ( MonadMask m download :: ( MonadMask m
, MonadReader AppState m
, MonadThrow m , MonadThrow m
, MonadLogger m , MonadLogger m
, MonadIO m , MonadIO m
) )
=> DownloadInfo => Settings
-> DownloadInfo
-> FilePath -- ^ destination dir -> FilePath -- ^ destination dir
-> Maybe FilePath -- ^ optional filename -> Maybe FilePath -- ^ optional filename
-> Excepts '[DigestError , DownloadFailed] m FilePath -> Excepts '[DigestError , DownloadFailed] m FilePath
download dli dest mfn download settings@Settings{ downloader } dli dest mfn
| scheme == "https" = dl | scheme == "https" = dl
| scheme == "http" = dl | scheme == "http" = dl
| scheme == "file" = cp | scheme == "file" = cp
@@ -354,20 +357,20 @@ download dli dest mfn
-- download -- download
flip onException flip onException
(liftIO $ hideError doesNotExistErrorType $ removeFile destFile) (liftIO $ hideError doesNotExistErrorType $ rmFile destFile)
$ catchAllE @_ @'[ProcessError, DownloadFailed, UnsupportedScheme] $ catchAllE @_ @'[ProcessError, DownloadFailed, UnsupportedScheme]
(\e -> (\e ->
liftIO (hideError doesNotExistErrorType $ removeFile destFile) liftIO (hideError doesNotExistErrorType $ rmFile destFile)
>> (throwE . DownloadFailed $ e) >> (throwE . DownloadFailed $ e)
) $ do ) $ do
lift getDownloader >>= \case case downloader of
Curl -> do Curl -> do
o' <- liftIO getCurlOpts o' <- liftIO getCurlOpts
liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "curl" liftE $ lEM @_ @'[ProcessError] $ exec "curl"
(o' ++ ["-fL", "-o", destFile, (T.unpack . decUTF8Safe) $ serializeURIRef' $ view dlUri dli]) Nothing Nothing (o' ++ ["-fL", "-o", destFile, (T.unpack . decUTF8Safe) $ serializeURIRef' $ view dlUri dli]) Nothing Nothing
Wget -> do Wget -> do
o' <- liftIO getWgetOpts o' <- liftIO getWgetOpts
liftE $ lEM @_ @'[ProcessError] $ liftIO $ exec "wget" liftE $ lEM @_ @'[ProcessError] $ exec "wget"
(o' ++ ["-O", destFile , (T.unpack . decUTF8Safe) $ serializeURIRef' $ view dlUri dli]) Nothing Nothing (o' ++ ["-O", destFile , (T.unpack . decUTF8Safe) $ serializeURIRef' $ view dlUri dli]) Nothing Nothing
#if defined(INTERNAL_DOWNLOADER) #if defined(INTERNAL_DOWNLOADER)
Internal -> do Internal -> do
@@ -375,7 +378,7 @@ download dli dest mfn
liftE $ downloadToFile https host fullPath port destFile liftE $ downloadToFile https host fullPath port destFile
#endif #endif
liftE $ checkDigest dli destFile liftE $ checkDigest settings dli destFile
pure destFile pure destFile
-- Manage to find a file we can write the body into. -- Manage to find a file we can write the body into.
@@ -393,27 +396,40 @@ downloadCached :: ( MonadMask m
, MonadLogger m , MonadLogger m
, MonadIO m , MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
, MonadReader AppState m
) )
=> DownloadInfo => Settings
-> Dirs
-> DownloadInfo
-> Maybe FilePath -- ^ optional filename -> Maybe FilePath -- ^ optional filename
-> Excepts '[DigestError , DownloadFailed] m FilePath -> Excepts '[DigestError , DownloadFailed] m FilePath
downloadCached dli mfn = do downloadCached settings@Settings{ cache } dirs dli mfn = do
cache <- lift getCache
case cache of case cache of
True -> do True -> downloadCached' settings dirs dli mfn
AppState {dirs = Dirs {..}} <- lift ask
let fn = fromMaybe ((T.unpack . decUTF8Safe) $ urlBaseName $ view (dlUri % pathL') dli) mfn
let cachfile = cacheDir </> fn
fileExists <- liftIO $ doesFileExist cachfile
if
| fileExists -> do
liftE $ checkDigest dli cachfile
pure cachfile
| otherwise -> liftE $ download dli cacheDir mfn
False -> do False -> do
tmp <- lift withGHCupTmpDir tmp <- lift withGHCupTmpDir
liftE $ download dli tmp mfn liftE $ download settings dli tmp mfn
downloadCached' :: ( MonadMask m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadUnliftIO m
)
=> Settings
-> Dirs
-> DownloadInfo
-> Maybe FilePath -- ^ optional filename
-> Excepts '[DigestError , DownloadFailed] m FilePath
downloadCached' settings Dirs{..} dli mfn = do
let fn = fromMaybe ((T.unpack . decUTF8Safe) $ urlBaseName $ view (dlUri % pathL') dli) mfn
let cachfile = cacheDir </> fn
fileExists <- liftIO $ doesFileExist cachfile
if
| fileExists -> do
liftE $ checkDigest settings dli cachfile
pure cachfile
| otherwise -> liftE $ download settings dli cacheDir mfn
@@ -426,8 +442,9 @@ downloadCached dli mfn = do
-- | This is used for downloading the JSON. -- | This is used for downloading the JSON.
downloadBS :: (MonadReader AppState m, MonadCatch m, MonadIO m, MonadLogger m) downloadBS :: (MonadCatch m, MonadIO m, MonadLogger m)
=> URI => Downloader
-> URI
-> Excepts -> Excepts
'[ FileDoesNotExistError '[ FileDoesNotExistError
, HTTPStatusError , HTTPStatusError
@@ -439,7 +456,7 @@ downloadBS :: (MonadReader AppState m, MonadCatch m, MonadIO m, MonadLogger m)
] ]
m m
L.ByteString L.ByteString
downloadBS uri' downloadBS downloader uri'
| scheme == "https" | scheme == "https"
= dl True = dl True
| scheme == "http" | scheme == "http"
@@ -459,12 +476,12 @@ downloadBS uri'
dl _ = do dl _ = do
#endif #endif
lift $ $(logDebug) [i|downloading: #{serializeURIRef' uri'}|] lift $ $(logDebug) [i|downloading: #{serializeURIRef' uri'}|]
lift getDownloader >>= \case case downloader of
Curl -> do Curl -> do
o' <- liftIO getCurlOpts o' <- liftIO getCurlOpts
let exe = "curl" let exe = "curl"
args = o' ++ ["-sSfL", T.unpack $ decUTF8Safe $ serializeURIRef' uri'] args = o' ++ ["-sSfL", T.unpack $ decUTF8Safe $ serializeURIRef' uri']
liftIO (executeOut exe args Nothing) >>= \case lift (executeOut exe args Nothing) >>= \case
CapturedProcess ExitSuccess stdout _ -> do CapturedProcess ExitSuccess stdout _ -> do
pure stdout pure stdout
CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' exe args CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' exe args
@@ -472,7 +489,7 @@ downloadBS uri'
o' <- liftIO getWgetOpts o' <- liftIO getWgetOpts
let exe = "wget" let exe = "wget"
args = o' ++ ["-qO-", T.unpack $ decUTF8Safe $ serializeURIRef' uri'] args = o' ++ ["-qO-", T.unpack $ decUTF8Safe $ serializeURIRef' uri']
liftIO (executeOut exe args Nothing) >>= \case lift (executeOut exe args Nothing) >>= \case
CapturedProcess ExitSuccess stdout _ -> do CapturedProcess ExitSuccess stdout _ -> do
pure stdout pure stdout
CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' exe args CapturedProcess (ExitFailure i') _ _ -> throwE $ NonZeroExit i' exe args
@@ -483,12 +500,13 @@ downloadBS uri'
#endif #endif
checkDigest :: (MonadIO m, MonadThrow m, MonadLogger m, MonadReader AppState m) checkDigest :: (MonadIO m, MonadThrow m, MonadLogger m)
=> DownloadInfo => Settings
-> DownloadInfo
-> FilePath -> FilePath
-> Excepts '[DigestError] m () -> Excepts '[DigestError] m ()
checkDigest dli file = do checkDigest Settings{ noVerify } dli file = do
verify <- lift ask <&> (not . noVerify . settings) let verify = not noVerify
when verify $ do when verify $ do
let p' = takeFileName file let p' = takeFileName file
lift $ $(logInfo) [i|verifying digest of: #{p'}|] lift $ $(logInfo) [i|verifying digest of: #{p'}|]
@@ -513,3 +531,8 @@ getWgetOpts =
Just r -> pure $ splitOn " " r Just r -> pure $ splitOn " " r
Nothing -> pure [] Nothing -> pure []
urlBaseName :: ByteString -- ^ the url path (without scheme and host)
-> ByteString
urlBaseName = snd . B.breakEnd (== _slash) . urlDecode False

View File

@@ -57,7 +57,7 @@ import qualified Data.Text.IO as T
-- | Get the full platform request, consisting of architecture, distro, ... -- | Get the full platform request, consisting of architecture, distro, ...
platformRequest :: (MonadLogger m, MonadCatch m, MonadIO m) platformRequest :: (Alternative m, MonadFail m, MonadLogger m, MonadCatch m, MonadIO m)
=> Excepts => Excepts
'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] '[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound]
m m
@@ -82,7 +82,7 @@ getArchitecture = case arch of
what -> Left (NoCompatibleArch what) what -> Left (NoCompatibleArch what)
getPlatform :: (MonadLogger m, MonadCatch m, MonadIO m) getPlatform :: (Alternative m, MonadLogger m, MonadCatch m, MonadIO m, MonadFail m)
=> Excepts => Excepts
'[NoCompatiblePlatform, DistroNotFound] '[NoCompatiblePlatform, DistroNotFound]
m m
@@ -112,22 +112,21 @@ getPlatform = do
pure pfr pure pfr
where where
getMajorVersion = T.intercalate "." . take 2 . T.split (== '.') getMajorVersion = T.intercalate "." . take 2 . T.split (== '.')
getFreeBSDVersion = getFreeBSDVersion = lift $ fmap _stdOut $ executeOut "freebsd-version" [] Nothing
liftIO $ fmap _stdOut $ executeOut "freebsd-version" [] Nothing getDarwinVersion = lift $ fmap _stdOut $ executeOut "sw_vers"
getDarwinVersion = liftIO $ fmap _stdOut $ executeOut "sw_vers"
["-productVersion"] ["-productVersion"]
Nothing Nothing
getLinuxDistro :: (MonadCatch m, MonadIO m) getLinuxDistro :: (Alternative m, MonadCatch m, MonadIO m, MonadFail m)
=> Excepts '[DistroNotFound] m (LinuxDistro, Maybe Versioning) => Excepts '[DistroNotFound] m (LinuxDistro, Maybe Versioning)
getLinuxDistro = do getLinuxDistro = do
-- TODO: don't do alternative on IO, because it hides bugs -- TODO: don't do alternative on IO, because it hides bugs
(name, ver) <- handleIO (\_ -> throwE DistroNotFound) $ liftIO $ asum (name, ver) <- handleIO (\_ -> throwE DistroNotFound) $ lift $ asum
[ try_os_release [ liftIO try_os_release
, try_lsb_release_cmd , try_lsb_release_cmd
, try_redhat_release , liftIO try_redhat_release
, try_debian_version , liftIO try_debian_version
] ]
let parsedVer = ver >>= either (const Nothing) Just . versioning let parsedVer = ver >>= either (const Nothing) Just . versioning
distro = if distro = if
@@ -163,9 +162,10 @@ getLinuxDistro = do
fmap osRelease <$> parseOsRelease fmap osRelease <$> parseOsRelease
pure (T.pack name, fmap T.pack version_id) pure (T.pack name, fmap T.pack version_id)
try_lsb_release_cmd :: IO (Text, Maybe Text) try_lsb_release_cmd :: (MonadFail m, MonadIO m)
=> m (Text, Maybe Text)
try_lsb_release_cmd = do try_lsb_release_cmd = do
(Just _) <- findExecutable lsb_release_cmd (Just _) <- liftIO $ findExecutable lsb_release_cmd
name <- fmap _stdOut $ executeOut lsb_release_cmd ["-si"] Nothing name <- fmap _stdOut $ executeOut lsb_release_cmd ["-si"] Nothing
ver <- fmap _stdOut $ executeOut lsb_release_cmd ["-sr"] Nothing ver <- fmap _stdOut $ executeOut lsb_release_cmd ["-sr"] Nothing
pure (decUTF8Safe' name, Just $ decUTF8Safe' ver) pure (decUTF8Safe' name, Just $ decUTF8Safe' ver)

View File

@@ -20,16 +20,20 @@ module GHCup.Types
) )
where where
import Control.Applicative
import Control.Monad.Logger
import Data.Map.Strict ( Map ) import Data.Map.Strict ( Map )
import Data.List.NonEmpty ( NonEmpty (..) ) import Data.List.NonEmpty ( NonEmpty (..) )
import Data.Text ( Text ) import Data.Text ( Text )
import Data.Versions import Data.Versions
import Haskus.Utils.Variant.Excepts
import Text.PrettyPrint.HughesPJClass (Pretty, pPrint, text) import Text.PrettyPrint.HughesPJClass (Pretty, pPrint, text)
import URI.ByteString import URI.ByteString
#if defined(BRICK) #if defined(BRICK)
import Graphics.Vty ( Key(..) ) import Graphics.Vty ( Key(..) )
#endif #endif
import qualified Control.Monad.Trans.Class as Trans
import qualified Data.Text as T import qualified Data.Text as T
import qualified GHC.Generics as GHC import qualified GHC.Generics as GHC
@@ -52,6 +56,7 @@ data Key = KEsc | KChar Char | KBS | KEnter
data GHCupInfo = GHCupInfo data GHCupInfo = GHCupInfo
{ _toolRequirements :: ToolRequirements { _toolRequirements :: ToolRequirements
, _ghcupDownloads :: GHCupDownloads , _ghcupDownloads :: GHCupDownloads
, _globalTools :: Map GlobalTool DownloadInfo
} }
deriving (Show, GHC.Generic) deriving (Show, GHC.Generic)
@@ -100,6 +105,9 @@ data Tool = GHC
| Stack | Stack
deriving (Eq, GHC.Generic, Ord, Show, Enum, Bounded) deriving (Eq, GHC.Generic, Ord, Show, Enum, Bounded)
data GlobalTool = ShimGen
deriving (Eq, GHC.Generic, Ord, Show, Enum, Bounded)
-- | All necessary information of a tool version, including -- | All necessary information of a tool version, including
-- source download and per-architecture downloads. -- source download and per-architecture downloads.
@@ -307,6 +315,8 @@ data AppState = AppState
{ settings :: Settings { settings :: Settings
, dirs :: Dirs , dirs :: Dirs
, keyBindings :: KeyBindings , keyBindings :: KeyBindings
, ghcupInfo :: GHCupInfo
, pfreq :: PlatformRequest
} deriving (Show) } deriving (Show)
data Settings = Settings data Settings = Settings
@@ -437,3 +447,16 @@ instance Pretty Versioning where
instance Pretty Version where instance Pretty Version where
pPrint = text . T.unpack . prettyVer pPrint = text . T.unpack . prettyVer
instance (Monad m, Alternative m) => Alternative (LoggingT m) where
empty = Trans.lift empty
{-# INLINE empty #-}
m <|> n = LoggingT $ \ r -> runLoggingT m r <|> runLoggingT n r
{-# INLINE (<|>) #-}
instance MonadLogger m => MonadLogger (Excepts e m) where
monadLoggerLog a b c d = Trans.lift $ monadLoggerLog a b c d

View File

@@ -44,23 +44,16 @@ import qualified Text.Megaparsec.Char as MPC
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } { fieldLabelModifier = removeLensFieldLabel } ''Architecture deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } { fieldLabelModifier = removeLensFieldLabel } ''Architecture
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''LinuxDistro deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''LinuxDistro
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Mess
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''MChunk
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Platform
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''SemVer
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Tool
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VSep deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VSep
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VUnit deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VUnit
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VersionInfo deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''MChunk
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadInfo deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Platform
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GHCupInfo deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Mess
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Requirements deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''SemVer
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Tool
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GlobalTool
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''KeepDirs deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''KeepDirs
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Downloader deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Downloader
deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''URLSource
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "u-") . T.pack . kebab $ str' } ''UserSettings
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "k-") . T.pack . kebab $ str' } ''UserKeyBindings
deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''Key
instance ToJSON Tag where instance ToJSON Tag where
toJSON Latest = String "Latest" toJSON Latest = String "Latest"
@@ -197,6 +190,12 @@ instance ToJSONKey Tool where
instance FromJSONKey Tool where instance FromJSONKey Tool where
fromJSONKey = genericFromJSONKey defaultJSONKeyOptions fromJSONKey = genericFromJSONKey defaultJSONKeyOptions
instance ToJSONKey GlobalTool where
toJSONKey = genericToJSONKey defaultJSONKeyOptions
instance FromJSONKey GlobalTool where
fromJSONKey = genericFromJSONKey defaultJSONKeyOptions
instance ToJSON TarDir where instance ToJSON TarDir where
toJSON (RealDir p) = toJSON p toJSON (RealDir p) = toJSON p
toJSON (RegexDir r) = object ["RegexDir" .= r] toJSON (RegexDir r) = object ["RegexDir" .= r]
@@ -306,3 +305,14 @@ instance FromJSONKey (Maybe VersionRange) where
just t = case MP.parse versionRangeP "" t of just t = case MP.parse versionRangeP "" t of
Right x -> pure $ Just x Right x -> pure $ Just x
Left e -> fail $ "Failure in (Maybe VersionRange) (FromJSONKey)" <> MP.errorBundlePretty e Left e -> fail $ "Failure in (Maybe VersionRange) (FromJSONKey)" <> MP.errorBundlePretty e
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Requirements
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadInfo
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VersionInfo
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GHCupInfo
deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''URLSource
deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''Key
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "k-") . T.pack . kebab $ str' } ''UserKeyBindings
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "u-") . T.pack . kebab $ str' } ''UserSettings

View File

@@ -26,6 +26,9 @@ module GHCup.Utils
where where
#if defined(IS_WINDOWS)
import GHCup.Download
#endif
import GHCup.Errors import GHCup.Errors
import GHCup.Types import GHCup.Types
import GHCup.Types.Optics import GHCup.Types.Optics
@@ -48,6 +51,11 @@ import Control.Monad.Fail ( MonadFail )
#endif #endif
import Control.Monad.Logger import Control.Monad.Logger
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.Trans.Resource
hiding ( throwM )
#if defined(IS_WINDOWS)
import Data.Bits
#endif
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
import Data.Either import Data.Either
import Data.Foldable import Data.Foldable
@@ -58,7 +66,6 @@ import Data.Maybe
import Data.String.Interpolate import Data.String.Interpolate
import Data.Text ( Text ) import Data.Text ( Text )
import Data.Versions import Data.Versions
import Data.Word8
import GHC.IO.Exception import GHC.IO.Exception
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Optics import Optics
@@ -66,7 +73,11 @@ import Safe
import System.Directory hiding ( findFiles ) import System.Directory hiding ( findFiles )
import System.FilePath import System.FilePath
import System.IO.Error import System.IO.Error
import System.IO.Unsafe ( unsafeInterleaveIO ) #if defined(IS_WINDOWS)
import System.Win32.Console
import System.Win32.File hiding ( copyFile )
import System.Win32.Types
#endif
import Text.Regex.Posix import Text.Regex.Posix
import URI.ByteString import URI.ByteString
@@ -121,7 +132,7 @@ rmMinorSymlinks tv@GHCTargetVersion{..} = do
let f_xyz = f <> "-" <> T.unpack (prettyVer _tvVersion) <> exeExt let f_xyz = f <> "-" <> T.unpack (prettyVer _tvVersion) <> exeExt
let fullF = binDir </> f_xyz let fullF = binDir </> f_xyz
lift $ $(logDebug) [i|rm -f #{fullF}|] lift $ $(logDebug) [i|rm -f #{fullF}|]
liftIO $ hideError doesNotExistErrorType $ removeFile fullF liftIO $ hideError doesNotExistErrorType $ rmLink fullF
-- | Removes the set ghc version for the given target, if any. -- | Removes the set ghc version for the given target, if any.
@@ -141,11 +152,11 @@ rmPlain target = do
forM_ files $ \f -> do forM_ files $ \f -> do
let fullF = binDir </> f <> exeExt let fullF = binDir </> f <> exeExt
lift $ $(logDebug) [i|rm -f #{fullF}|] lift $ $(logDebug) [i|rm -f #{fullF}|]
liftIO $ hideError doesNotExistErrorType $ removeFile fullF liftIO $ hideError doesNotExistErrorType $ rmLink fullF
-- old ghcup -- old ghcup
let hdc_file = binDir </> "haddock-ghc" <> exeExt let hdc_file = binDir </> "haddock-ghc" <> exeExt
lift $ $(logDebug) [i|rm -f #{hdc_file}|] lift $ $(logDebug) [i|rm -f #{hdc_file}|]
liftIO $ hideError doesNotExistErrorType $ removeFile hdc_file liftIO $ hideError doesNotExistErrorType $ rmLink hdc_file
-- | Remove the major GHC symlink, e.g. ghc-8.6. -- | Remove the major GHC symlink, e.g. ghc-8.6.
@@ -168,7 +179,7 @@ rmMajorSymlinks tv@GHCTargetVersion{..} = do
let f_xy = f <> "-" <> T.unpack v' <> exeExt let f_xy = f <> "-" <> T.unpack v' <> exeExt
let fullF = binDir </> f_xy let fullF = binDir </> f_xy
lift $ $(logDebug) [i|rm -f #{fullF}|] lift $ $(logDebug) [i|rm -f #{fullF}|]
liftIO $ hideError doesNotExistErrorType $ removeFile fullF liftIO $ hideError doesNotExistErrorType $ rmLink fullF
@@ -205,27 +216,27 @@ ghcSet mtarget = do
-- link destination is of the form ../ghc/<ver>/bin/ghc -- link destination is of the form ../ghc/<ver>/bin/ghc
-- for old ghcup, it is ../ghc/<ver>/bin/ghc-<ver> -- for old ghcup, it is ../ghc/<ver>/bin/ghc-<ver>
liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ do liftIO $ handleIO' NoSuchThing (\_ -> pure Nothing) $ do
link <- liftIO $ getSymbolicLinkTarget ghcBin link <- liftIO $ getLinkTarget ghcBin
Just <$> ghcLinkVersion link Just <$> ghcLinkVersion link
ghcLinkVersion :: MonadThrow m => FilePath -> m GHCTargetVersion
ghcLinkVersion (T.pack . dropSuffix exeExt -> t) = throwEither $ MP.parse parser "ghcLinkVersion" t
where where
parser = ghcLinkVersion :: MonadThrow m => FilePath -> m GHCTargetVersion
(do ghcLinkVersion (T.pack . dropSuffix exeExt -> t) = throwEither $ MP.parse parser "ghcLinkVersion" t
_ <- parseUntil1 ghcSubPath where
_ <- ghcSubPath parser =
r <- parseUntil1 pathSep (do
rest <- MP.getInput _ <- parseUntil1 ghcSubPath
MP.setInput r _ <- ghcSubPath
x <- ghcTargetVerP r <- parseUntil1 pathSep
MP.setInput rest rest <- MP.getInput
pure x MP.setInput r
) x <- ghcTargetVerP
<* pathSep MP.setInput rest
<* MP.takeRest pure x
<* MP.eof )
ghcSubPath = pathSep <* MP.chunk "ghc" *> pathSep <* pathSep
<* MP.takeRest
<* MP.eof
ghcSubPath = pathSep <* MP.chunk "ghc" *> pathSep
-- | Get all installed GHCs by reading ~/.ghcup/ghc/<dir>. -- | Get all installed GHCs by reading ~/.ghcup/ghc/<dir>.
-- If a dir cannot be parsed, returns left. -- If a dir cannot be parsed, returns left.
@@ -254,7 +265,7 @@ getInstalledCabals' cs = do
bins <- liftIO $ handleIO (\_ -> pure []) $ findFiles bins <- liftIO $ handleIO (\_ -> pure []) $ findFiles
binDir binDir
(makeRegexOpts compExtended execBlank ([s|^cabal-.*$|] :: ByteString)) (makeRegexOpts compExtended execBlank ([s|^cabal-.*$|] :: ByteString))
vs <- forM bins $ \f -> case fmap (version . T.pack . dropSuffix exeExt) . stripPrefix "cabal-" $ f of vs <- forM bins $ \f -> case version . T.pack <$> (stripSuffix exeExt =<< stripPrefix "cabal-" f) of
Just (Right r) -> pure $ Right r Just (Right r) -> pure $ Right r
Just (Left _) -> pure $ Left f Just (Left _) -> pure $ Left f
Nothing -> pure $ Left f Nothing -> pure $ Left f
@@ -273,7 +284,7 @@ cabalSet :: (MonadLogger m, MonadReader AppState m, MonadIO m, MonadThrow m, Mon
cabalSet = do cabalSet = do
AppState {dirs = Dirs {..}} <- ask AppState {dirs = Dirs {..}} <- ask
let cabalbin = binDir </> "cabal" <> exeExt let cabalbin = binDir </> "cabal" <> exeExt
b <- handleIO (\_ -> pure False) $ liftIO $ pathIsSymbolicLink cabalbin b <- handleIO (\_ -> pure False) $ liftIO $ pathIsLink cabalbin
if if
| b -> do | b -> do
handleIO' NoSuchThing (\_ -> pure Nothing) $ do handleIO' NoSuchThing (\_ -> pure Nothing) $ do
@@ -281,20 +292,20 @@ cabalSet = do
if broken if broken
then pure Nothing then pure Nothing
else do else do
link <- liftIO $ getSymbolicLinkTarget cabalbin link <- liftIO $ getLinkTarget cabalbin
case linkVersion link of case linkVersion link of
Right v -> pure $ Just v Right v -> pure $ Just v
Left err -> do Left err -> do
$(logWarn) [i|Failed to parse cabal symlink target with: "#{err}". The symlink #{cabalbin} needs to point to valid cabal binary, such as 'cabal-3.4.0.0'.|] $(logWarn) [i|Failed to parse cabal symlink target with: "#{err}". The symlink #{cabalbin} needs to point to valid cabal binary, such as 'cabal-3.4.0.0'.|]
pure Nothing pure Nothing
| otherwise -> do -- legacy behavior | otherwise -> do -- legacy behavior
mc <- liftIO $ handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut mc <- handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut
cabalbin cabalbin
["--numeric-version"] ["--numeric-version"]
Nothing Nothing
fmap join $ forM mc $ \c -> if fmap join $ forM mc $ \c -> if
| not (BL.null (_stdOut c)), _exitCode c == ExitSuccess -> do | not (BL.null (_stdOut c)), _exitCode c == ExitSuccess -> do
let reportedVer = fst . B.spanEnd (== _lf) . BL.toStrict . _stdOut $ c let reportedVer = fst . B.spanEnd isNewLine . BL.toStrict . _stdOut $ c
case version $ decUTF8Safe reportedVer of case version $ decUTF8Safe reportedVer of
Left e -> throwM e Left e -> throwM e
Right r -> pure $ Just r Right r -> pure $ Just r
@@ -304,7 +315,7 @@ cabalSet = do
-- because of: -- because of:
-- https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/119 -- https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/119
linkVersion :: MonadThrow m => FilePath -> m Version linkVersion :: MonadThrow m => FilePath -> m Version
linkVersion = throwEither . MP.parse parser "" . T.pack . dropSuffix exeExt linkVersion = throwEither . MP.parse parser "linkVersion" . T.pack . dropSuffix exeExt
parser parser
= MP.try (stripAbsolutePath *> cabalParse) = MP.try (stripAbsolutePath *> cabalParse)
@@ -338,7 +349,7 @@ getInstalledHLSs = do
) )
forM bins $ \f -> forM bins $ \f ->
case case
fmap (version . T.pack . dropSuffix exeExt) . stripPrefix "haskell-language-server-wrapper-" $ f version . T.pack <$> (stripSuffix exeExt =<< stripPrefix "haskell-language-server-wrapper-" f)
of of
Just (Right r) -> pure $ Right r Just (Right r) -> pure $ Right r
Just (Left _) -> pure $ Left f Just (Left _) -> pure $ Left f
@@ -357,9 +368,7 @@ getInstalledStacks = do
([s|^stack-.*$|] :: ByteString) ([s|^stack-.*$|] :: ByteString)
) )
forM bins $ \f -> forM bins $ \f ->
case case version . T.pack <$> (stripSuffix exeExt =<< stripPrefix "stack-" f) of
fmap (version . T.pack . dropSuffix exeExt) . stripPrefix "stack-" $ f
of
Just (Right r) -> pure $ Right r Just (Right r) -> pure $ Right r
Just (Left _) -> pure $ Left f Just (Left _) -> pure $ Left f
Nothing -> pure $ Left f Nothing -> pure $ Left f
@@ -376,14 +385,27 @@ stackSet = do
if broken if broken
then pure Nothing then pure Nothing
else do else do
link <- liftIO $ getSymbolicLinkTarget stackBin link <- liftIO $ getLinkTarget stackBin
Just <$> linkVersion link Just <$> linkVersion link
where where
linkVersion :: MonadThrow m => FilePath -> m Version linkVersion :: MonadThrow m => FilePath -> m Version
linkVersion = throwEither . MP.parse parser "" . T.pack . dropSuffix exeExt linkVersion = throwEither . MP.parse parser "" . T.pack . dropSuffix exeExt
where where
parser = parser
MP.chunk "stack-" *> version' = MP.try (stripAbsolutePath *> cabalParse)
<|> MP.try (stripRelativePath *> cabalParse)
<|> cabalParse
-- parses the version of "stack-2.7.1" -> "2.7.1"
cabalParse = MP.chunk "stack-" *> version'
-- parses any path component ending with path separator,
-- e.g. "foo/"
stripPathComponet = parseUntil1 pathSep *> pathSep
-- parses an absolute path up until the last path separator,
-- e.g. "/bar/baz/foo" -> "/bar/baz/", leaving "foo"
stripAbsolutePath = pathSep *> MP.many (MP.try stripPathComponet)
-- parses a relative path up until the last path separator,
-- e.g. "bar/baz/foo" -> "bar/baz/", leaving "foo"
stripRelativePath = MP.many (MP.try stripPathComponet)
-- | Whether the given Stack version is installed. -- | Whether the given Stack version is installed.
stackInstalled :: (MonadIO m, MonadReader AppState m, MonadCatch m) => Version -> m Bool stackInstalled :: (MonadIO m, MonadReader AppState m, MonadCatch m) => Version -> m Bool
@@ -410,14 +432,27 @@ hlsSet = do
if broken if broken
then pure Nothing then pure Nothing
else do else do
link <- liftIO $ getSymbolicLinkTarget hlsBin link <- liftIO $ getLinkTarget hlsBin
Just <$> linkVersion link Just <$> linkVersion link
where where
linkVersion :: MonadThrow m => FilePath -> m Version linkVersion :: MonadThrow m => FilePath -> m Version
linkVersion = throwEither . MP.parse parser "" . T.pack . dropSuffix exeExt linkVersion = throwEither . MP.parse parser "" . T.pack . dropSuffix exeExt
where where
parser = parser
MP.chunk "haskell-language-server-wrapper-" *> version' = MP.try (stripAbsolutePath *> cabalParse)
<|> MP.try (stripRelativePath *> cabalParse)
<|> cabalParse
-- parses the version of "haskell-language-server-wrapper-1.1.0" -> "1.1.0"
cabalParse = MP.chunk "haskell-language-server-wrapper-" *> version'
-- parses any path component ending with path separator,
-- e.g. "foo/"
stripPathComponet = parseUntil1 pathSep *> pathSep
-- parses an absolute path up until the last path separator,
-- e.g. "/bar/baz/foo" -> "/bar/baz/", leaving "foo"
stripAbsolutePath = pathSep *> MP.many (MP.try stripPathComponet)
-- parses a relative path up until the last path separator,
-- e.g. "bar/baz/foo" -> "bar/baz/", leaving "foo"
stripRelativePath = MP.many (MP.try stripPathComponet)
-- | Return the GHC versions the currently selected HLS supports. -- | Return the GHC versions the currently selected HLS supports.
@@ -500,7 +535,7 @@ hlsSymlinks = do
) )
filterM filterM
( liftIO ( liftIO
. pathIsSymbolicLink . pathIsLink
. (binDir </>) . (binDir </>)
) )
oldSyms oldSyms
@@ -726,11 +761,6 @@ getDownloader = ask <&> downloader . settings
------------- -------------
urlBaseName :: ByteString -- ^ the url path (without scheme and host)
-> ByteString
urlBaseName = snd . B.breakEnd (== _slash) . urlDecode False
-- | Get tool files from @~\/.ghcup\/bin\/ghc\/\<ver\>\/bin\/\*@ -- | Get tool files from @~\/.ghcup\/bin\/ghc\/\<ver\>\/bin\/\*@
-- while ignoring @*-\<ver\>@ symlinks and accounting for cross triple prefix. -- while ignoring @*-\<ver\>@ symlinks and accounting for cross triple prefix.
-- --
@@ -810,19 +840,20 @@ make args workdir = do
let mymake = if has_gmake then "gmake" else "make" let mymake = if has_gmake then "gmake" else "make"
execLogged mymake args workdir "ghc-make" Nothing execLogged mymake args workdir "ghc-make" Nothing
makeOut :: [String] makeOut :: (MonadReader AppState m, MonadIO m)
=> [String]
-> Maybe FilePath -> Maybe FilePath
-> IO CapturedProcess -> m CapturedProcess
makeOut args workdir = do makeOut args workdir = do
spaths <- liftIO getSearchPath spaths <- liftIO getSearchPath
has_gmake <- isJust <$> liftIO (searchPath spaths "gmake") has_gmake <- isJust <$> liftIO (searchPath spaths "gmake")
let mymake = if has_gmake then "gmake" else "make" let mymake = if has_gmake then "gmake" else "make"
liftIO $ executeOut mymake args workdir executeOut mymake args workdir
-- | Try to apply patches in order. Fails with 'PatchFailed' -- | Try to apply patches in order. Fails with 'PatchFailed'
-- on first failure. -- on first failure.
applyPatches :: (MonadLogger m, MonadIO m) applyPatches :: (MonadReader AppState m, MonadLogger m, MonadIO m)
=> FilePath -- ^ dir containing patches => FilePath -- ^ dir containing patches
-> FilePath -- ^ dir to apply patches in -> FilePath -- ^ dir to apply patches in
-> Excepts '[PatchFailed] m () -> Excepts '[PatchFailed] m ()
@@ -831,7 +862,7 @@ applyPatches pdir ddir = do
forM_ (sort patches) $ \patch' -> do forM_ (sort patches) $ \patch' -> do
lift $ $(logInfo) [i|Applying patch #{patch'}|] lift $ $(logInfo) [i|Applying patch #{patch'}|]
fmap (either (const Nothing) Just) fmap (either (const Nothing) Just)
(liftIO $ exec (exec
"patch" "patch"
["-p1", "-i", patch'] ["-p1", "-i", patch']
(Just ddir) (Just ddir)
@@ -840,7 +871,10 @@ applyPatches pdir ddir = do
-- | https://gitlab.haskell.org/ghc/ghc/-/issues/17353 -- | https://gitlab.haskell.org/ghc/ghc/-/issues/17353
darwinNotarization :: Platform -> FilePath -> IO (Either ProcessError ()) darwinNotarization :: (MonadReader AppState m, MonadIO m)
=> Platform
-> FilePath
-> m (Either ProcessError ())
darwinNotarization Darwin path = exec darwinNotarization Darwin path = exec
"xattr" "xattr"
["-r", "-d", "com.apple.quarantine", path] ["-r", "-d", "com.apple.quarantine", path]
@@ -869,11 +903,11 @@ runBuildAction bdir instdir action = do
AppState { settings = Settings {..} } <- lift ask AppState { settings = Settings {..} } <- lift ask
let exAction = do let exAction = do
forM_ instdir $ \dir -> forM_ instdir $ \dir ->
liftIO $ hideError doesNotExistErrorType $ removeDirectoryRecursive dir liftIO $ hideError doesNotExistErrorType $ rmPath dir
when (keepDirs == Never) when (keepDirs == Never)
$ liftIO $ liftIO
$ hideError doesNotExistErrorType $ hideError doesNotExistErrorType
$ removeDirectoryRecursive bdir $ rmPath bdir
v <- v <-
flip onException exAction flip onException exAction
$ catchAllE $ catchAllE
@@ -882,90 +916,10 @@ runBuildAction bdir instdir action = do
throwE (BuildFailed bdir es) throwE (BuildFailed bdir es)
) action ) action
when (keepDirs == Never || keepDirs == Errors) $ liftIO $ removeDirectoryRecursive when (keepDirs == Never || keepDirs == Errors) $ liftIO $ rmPath bdir
bdir
pure v pure v
-- | More permissive version of 'createDirRecursive'. This doesn't
-- error when the destination is a symlink to a directory.
createDirRecursive' :: FilePath -> IO ()
createDirRecursive' p =
handleIO (\e -> if isAlreadyExistsError e then isSymlinkDir e else throwIO e)
. createDirectoryIfMissing True
$ p
where
isSymlinkDir e = do
ft <- pathIsSymbolicLink p
case ft of
True -> do
rp <- canonicalizePath p
rft <- doesDirectoryExist rp
case rft of
True -> pure ()
_ -> throwIO e
_ -> throwIO e
-- | Recursively copy the contents of one directory to another path.
--
-- This is a rip-off of Cabal library.
copyDirectoryRecursive :: FilePath -> FilePath -> IO ()
copyDirectoryRecursive srcDir destDir = do
srcFiles <- getDirectoryContentsRecursive srcDir
copyFilesWith copyFile destDir [ (srcDir, f)
| f <- srcFiles ]
where
-- | Common implementation of 'copyFiles', 'installOrdinaryFiles',
-- 'installExecutableFiles' and 'installMaybeExecutableFiles'.
copyFilesWith :: (FilePath -> FilePath -> IO ())
-> FilePath -> [(FilePath, FilePath)] -> IO ()
copyFilesWith doCopy targetDir srcFiles = do
-- Create parent directories for everything
let dirs = map (targetDir </>) . nub . map (takeDirectory . snd) $ srcFiles
traverse_ (createDirectoryIfMissing True) dirs
-- Copy all the files
sequence_ [ let src = srcBase </> srcFile
dest = targetDir </> srcFile
in doCopy src dest
| (srcBase, srcFile) <- srcFiles ]
-- | List all the files in a directory and all subdirectories.
--
-- The order places files in sub-directories after all the files in their
-- parent directories. The list is generated lazily so is not well defined if
-- the source directory structure changes before the list is used.
--
getDirectoryContentsRecursive :: FilePath -> IO [FilePath]
getDirectoryContentsRecursive topdir = recurseDirectories [""]
where
recurseDirectories :: [FilePath] -> IO [FilePath]
recurseDirectories [] = return []
recurseDirectories (dir:dirs) = unsafeInterleaveIO $ do
(files, dirs') <- collect [] [] =<< getDirectoryContents (topdir </> dir)
files' <- recurseDirectories (dirs' ++ dirs)
return (files ++ files')
where
collect files dirs' [] = return (reverse files
,reverse dirs')
collect files dirs' (entry:entries) | ignore entry
= collect files dirs' entries
collect files dirs' (entry:entries) = do
let dirEntry = dir </> entry
isDirectory <- doesDirectoryExist (topdir </> dirEntry)
if isDirectory
then collect files (dirEntry:dirs') entries
else collect (dirEntry:files) dirs' entries
ignore ['.'] = True
ignore ['.', '.'] = True
ignore _ = False
getVersionInfo :: Version getVersionInfo :: Version
-> Tool -> Tool
-> GHCupDownloads -> GHCupDownloads
@@ -979,15 +933,6 @@ getVersionInfo v' tool =
) )
-- Gathering monoidal values
traverseFold :: (Foldable t, Applicative m, Monoid b) => (a -> m b) -> t a -> m b
traverseFold f = foldl (\mb a -> (<>) <$> mb <*> f a) (pure mempty)
-- | Gathering monoidal values
forFold :: (Foldable t, Applicative m, Monoid b) => t a -> (a -> m b) -> m b
forFold = \t -> (`traverseFold` t)
-- | The file extension for executables. -- | The file extension for executables.
exeExt :: String exeExt :: String
#if defined(IS_WINDOWS) #if defined(IS_WINDOWS)
@@ -996,3 +941,150 @@ exeExt = ".exe"
exeExt = "" exeExt = ""
#endif #endif
-- | The file extension for executables.
exeExt' :: ByteString
#if defined(IS_WINDOWS)
exeExt' = ".exe"
#else
exeExt' = ""
#endif
-- | Enables ANSI support on windows, does nothing on unix.
--
-- Returns 'Left str' on errors and 'Right bool' on success, where
-- 'bool' markes whether ansi support was already enabled.
--
-- This function never crashes.
--
-- Rip-off of https://docs.rs/ansi_term/0.12.1/x86_64-pc-windows-msvc/src/ansi_term/windows.rs.html#10-61
enableAnsiSupport :: IO (Either String Bool)
#if defined(IS_WINDOWS)
enableAnsiSupport = handleIO (pure . Left . displayException) $ do
-- ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
-- Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
h <- createFile "CONOUT$" (gENERIC_WRITE .|. gENERIC_READ)
fILE_SHARE_WRITE Nothing oPEN_EXISTING 0 Nothing
when (h == iNVALID_HANDLE_VALUE ) $ fail "invalid handle value"
-- ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
m <- getConsoleMode h
-- VT processing not already enabled?
if ((m .&. eNABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
-- https://docs.microsoft.com/en-us/windows/console/setconsolemode
then setConsoleMode h (m .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING)
>> pure (Right False)
else pure (Right True)
#else
enableAnsiSupport = pure (Right True)
#endif
-- | On unix, we can use symlinks, so we just get the
-- symbolic link target.
--
-- On windows, we have to emulate symlinks via shims,
-- see 'createLink'.
getLinkTarget :: FilePath -> IO FilePath
getLinkTarget fp = do
#if defined(IS_WINDOWS)
content <- readFile (dropExtension fp <.> "shim")
[p] <- pure . filter ("path = " `isPrefixOf`) . lines $ content
pure $ stripNewline $ dropPrefix "path = " p
#else
getSymbolicLinkTarget fp
#endif
-- | Checks whether the path is a link.
pathIsLink :: FilePath -> IO Bool
#if defined(IS_WINDOWS)
pathIsLink fp = doesPathExist (dropExtension fp <.> "shim")
#else
pathIsLink = pathIsSymbolicLink
#endif
rmLink :: FilePath -> IO ()
#if defined(IS_WINDOWS)
rmLink fp = do
hideError doesNotExistErrorType . liftIO . rmFile $ fp
hideError doesNotExistErrorType . liftIO . rmFile $ (dropExtension fp <.> "shim")
#else
rmLink = hideError doesNotExistErrorType . liftIO . rmFile
#endif
-- | Creates a symbolic link on unix and a fake symlink on windows for
-- executables, which:
-- 1. is a shim exe
-- 2. has a corresponding .shim file in the same directory that
-- contains the target
--
-- This overwrites previously existing files.
--
-- On windows, this requires that 'ensureGlobalTools' was run beforehand.
createLink :: ( MonadMask m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadReader AppState m
, MonadUnliftIO m
, MonadFail m
)
=> FilePath -- ^ path to the target executable
-> FilePath -- ^ path to be created
-> m ()
createLink link exe = do
#if defined(IS_WINDOWS)
AppState { dirs } <- ask
let shimGen = cacheDir dirs </> "gs.exe"
let shim = dropExtension exe <.> "shim"
-- For hardlinks, link needs to be absolute.
-- If link is relative, it's relative to the target exe.
-- Note that (</>) drops lhs when rhs is absolute.
fullLink = takeDirectory exe </> link
shimContents = "path = " <> fullLink
$(logDebug) [i|rm -f #{exe}|]
liftIO $ rmLink exe
$(logDebug) [i|ln -s #{fullLink} #{exe}|]
liftIO $ copyFile shimGen exe
liftIO $ writeFile shim shimContents
#else
$(logDebug) [i|rm -f #{exe}|]
liftIO $ hideError doesNotExistErrorType $ rmFile exe
$(logDebug) [i|ln -s #{link} #{exe}|]
liftIO $ createFileLink link exe
#endif
ensureGlobalTools :: ( MonadMask m
, MonadThrow m
, MonadLogger m
, MonadIO m
, MonadReader AppState m
, MonadUnliftIO m
, MonadFail m
)
=> Excepts '[DigestError , DownloadFailed, NoDownload] m ()
ensureGlobalTools = do
#if defined(IS_WINDOWS)
AppState { ghcupInfo = GHCupInfo _ _ gTools, settings, dirs } <- lift ask
shimDownload <- liftE $ lE @_ @'[NoDownload]
$ maybe (Left NoDownload) Right $ Map.lookup ShimGen gTools
let dl = downloadCached' settings dirs shimDownload (Just "gs.exe")
void $ (\(DigestError _ _) -> do
lift $ $(logWarn) [i|Digest doesn't match, redownloading gs.exe...|]
lift $ $(logDebug) [i|rm -f #{shimDownload}|]
liftIO $ hideError doesNotExistErrorType $ rmFile (cacheDir dirs </> "gs.exe")
liftE @'[DigestError , DownloadFailed] $ dl
) `catchE` (liftE @'[DigestError , DownloadFailed] dl)
pure ()
#else
pure ()
#endif

4
lib/GHCup/Utils.hs-boot Normal file
View File

@@ -0,0 +1,4 @@
module GHCup.Utils where
getLinkTarget :: FilePath -> IO FilePath
pathIsLink :: FilePath -> IO Bool

View File

@@ -17,6 +17,7 @@ Portability : portable
-} -}
module GHCup.Utils.Dirs module GHCup.Utils.Dirs
( getDirs ( getDirs
, ghcupBaseDir
, ghcupConfigFile , ghcupConfigFile
, ghcupCacheDir , ghcupCacheDir
, ghcupGHCBaseDir , ghcupGHCBaseDir
@@ -46,10 +47,14 @@ import Data.Maybe
import Data.String.Interpolate import Data.String.Interpolate
import GHC.IO.Exception ( IOErrorType(NoSuchThing) ) import GHC.IO.Exception ( IOErrorType(NoSuchThing) )
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
#if !defined(IS_WINDOWS)
import Optics import Optics
import System.Directory import System.Directory
#endif
import System.DiskSpace import System.DiskSpace
#if !defined(IS_WINDOWS)
import System.Environment import System.Environment
#endif
import System.FilePath import System.FilePath
import System.IO.Temp import System.IO.Temp
@@ -72,6 +77,9 @@ import Control.Concurrent (threadDelay)
-- then uses 'XDG_DATA_HOME/ghcup' as per xdg spec. -- then uses 'XDG_DATA_HOME/ghcup' as per xdg spec.
ghcupBaseDir :: IO FilePath ghcupBaseDir :: IO FilePath
ghcupBaseDir = do ghcupBaseDir = do
#if defined(IS_WINDOWS)
pure ("C:\\" </> "ghcup")
#else
xdg <- useXDG xdg <- useXDG
if xdg if xdg
then do then do
@@ -86,6 +94,7 @@ ghcupBaseDir = do
Just r -> pure r Just r -> pure r
Nothing -> liftIO getHomeDirectory Nothing -> liftIO getHomeDirectory
pure (bdir </> ".ghcup") pure (bdir </> ".ghcup")
#endif
-- | ~/.ghcup by default -- | ~/.ghcup by default
@@ -94,6 +103,9 @@ ghcupBaseDir = do
-- then uses 'XDG_CONFIG_HOME/ghcup' as per xdg spec. -- then uses 'XDG_CONFIG_HOME/ghcup' as per xdg spec.
ghcupConfigDir :: IO FilePath ghcupConfigDir :: IO FilePath
ghcupConfigDir = do ghcupConfigDir = do
#if defined(IS_WINDOWS)
pure ("C:\\" </> "ghcup")
#else
xdg <- useXDG xdg <- useXDG
if xdg if xdg
then do then do
@@ -108,6 +120,7 @@ ghcupConfigDir = do
Just r -> pure r Just r -> pure r
Nothing -> liftIO getHomeDirectory Nothing -> liftIO getHomeDirectory
pure (bdir </> ".ghcup") pure (bdir </> ".ghcup")
#endif
-- | If 'GHCUP_USE_XDG_DIRS' is set (to anything), -- | If 'GHCUP_USE_XDG_DIRS' is set (to anything),
@@ -115,6 +128,9 @@ ghcupConfigDir = do
-- (which, sadly is not strictly xdg spec). -- (which, sadly is not strictly xdg spec).
ghcupBinDir :: IO FilePath ghcupBinDir :: IO FilePath
ghcupBinDir = do ghcupBinDir = do
#if defined(IS_WINDOWS)
pure ("C:\\" </> "ghcup" </> "bin")
#else
xdg <- useXDG xdg <- useXDG
if xdg if xdg
then do then do
@@ -124,6 +140,7 @@ ghcupBinDir = do
home <- liftIO getHomeDirectory home <- liftIO getHomeDirectory
pure (home </> ".local" </> "bin") pure (home </> ".local" </> "bin")
else ghcupBaseDir <&> (</> "bin") else ghcupBaseDir <&> (</> "bin")
#endif
-- | Defaults to '~/.ghcup/cache'. -- | Defaults to '~/.ghcup/cache'.
@@ -132,6 +149,9 @@ ghcupBinDir = do
-- then uses 'XDG_CACHE_HOME/ghcup' as per xdg spec. -- then uses 'XDG_CACHE_HOME/ghcup' as per xdg spec.
ghcupCacheDir :: IO FilePath ghcupCacheDir :: IO FilePath
ghcupCacheDir = do ghcupCacheDir = do
#if defined(IS_WINDOWS)
pure ("C:\\" </> "ghcup" </> "cache")
#else
xdg <- useXDG xdg <- useXDG
if xdg if xdg
then do then do
@@ -142,6 +162,7 @@ ghcupCacheDir = do
pure (home </> ".cache") pure (home </> ".cache")
pure (bdir </> "ghcup") pure (bdir </> "ghcup")
else ghcupBaseDir <&> (</> "cache") else ghcupBaseDir <&> (</> "cache")
#endif
-- | Defaults to '~/.ghcup/logs'. -- | Defaults to '~/.ghcup/logs'.
@@ -150,6 +171,9 @@ ghcupCacheDir = do
-- then uses 'XDG_CACHE_HOME/ghcup/logs' as per xdg spec. -- then uses 'XDG_CACHE_HOME/ghcup/logs' as per xdg spec.
ghcupLogsDir :: IO FilePath ghcupLogsDir :: IO FilePath
ghcupLogsDir = do ghcupLogsDir = do
#if defined(IS_WINDOWS)
pure ("C:\\" </> "ghcup" </> "logs")
#else
xdg <- useXDG xdg <- useXDG
if xdg if xdg
then do then do
@@ -160,6 +184,7 @@ ghcupLogsDir = do
pure (home </> ".cache") pure (home </> ".cache")
pure (bdir </> "ghcup" </> "logs") pure (bdir </> "ghcup" </> "logs")
else ghcupBaseDir <&> (</> "logs") else ghcupBaseDir <&> (</> "logs")
#endif
getDirs :: IO Dirs getDirs :: IO Dirs
@@ -242,7 +267,7 @@ mkGhcupTmpDir = do
withGHCupTmpDir :: (MonadUnliftIO m, MonadLogger m, MonadCatch m, MonadResource m, MonadThrow m, MonadIO m) => m FilePath withGHCupTmpDir :: (MonadUnliftIO m, MonadLogger m, MonadCatch m, MonadResource m, MonadThrow m, MonadIO m) => m FilePath
withGHCupTmpDir = snd <$> withRunInIO (\run -> run $ allocate (run mkGhcupTmpDir) removeDirectoryRecursive) withGHCupTmpDir = snd <$> withRunInIO (\run -> run $ allocate (run mkGhcupTmpDir) rmPath)
@@ -252,8 +277,10 @@ withGHCupTmpDir = snd <$> withRunInIO (\run -> run $ allocate (run mkGhcupTmpDir
-------------- --------------
#if !defined(IS_WINDOWS)
useXDG :: IO Bool useXDG :: IO Bool
useXDG = isJust <$> lookupEnv "GHCUP_USE_XDG_DIRS" useXDG = isJust <$> lookupEnv "GHCUP_USE_XDG_DIRS"
#endif
relativeSymlink :: FilePath -- ^ the path in which to create the symlink relativeSymlink :: FilePath -- ^ the path in which to create the symlink

View File

@@ -8,7 +8,6 @@ module GHCup.Utils.File.Common where
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import Control.Exception
import Control.Monad.Extra import Control.Monad.Extra
import Control.Monad.Reader import Control.Monad.Reader
import Data.Maybe import Data.Maybe
@@ -17,7 +16,6 @@ import GHC.IO.Exception
import Optics hiding ((<|), (|>)) import Optics hiding ((<|), (|>))
import System.Directory import System.Directory
import System.FilePath import System.FilePath
import System.IO.Error
import Text.PrettyPrint.HughesPJClass hiding ( (<>) ) import Text.PrettyPrint.HughesPJClass hiding ( (<>) )
import Text.Regex.Posix import Text.Regex.Posix
@@ -106,17 +104,3 @@ findFiles path regex = do
contents <- listDirectory path contents <- listDirectory path
pure $ filter (match regex) contents pure $ filter (match regex) contents
isBrokenSymlink :: FilePath -> IO Bool
isBrokenSymlink fp = do
try (pathIsSymbolicLink fp) >>= \case
Right True -> do
let symDir = takeDirectory fp
tfp <- getSymbolicLinkTarget fp
not <$> doesPathExist
-- this drops 'symDir' if 'tfp' is absolute
(symDir </> tfp)
Right b -> pure b
Left e | isDoesNotExistError e -> pure False
| otherwise -> throwIO e

View File

@@ -42,6 +42,7 @@ import System.Console.Pretty hiding ( Pretty )
import System.Console.Regions import System.Console.Regions
import System.IO.Error import System.IO.Error
import System.FilePath import System.FilePath
import System.Directory
import System.Posix.Directory import System.Posix.Directory
import System.Posix.Files import System.Posix.Files
import System.Posix.IO import System.Posix.IO
@@ -63,11 +64,12 @@ import qualified "unix-bytestring" System.Posix.IO.ByteString
-- | Execute the given command and collect the stdout, stderr and the exit code. -- | Execute the given command and collect the stdout, stderr and the exit code.
-- The command is run in a subprocess. -- The command is run in a subprocess.
executeOut :: FilePath -- ^ command as filename, e.g. 'ls' executeOut :: MonadIO m
=> FilePath -- ^ command as filename, e.g. 'ls'
-> [String] -- ^ arguments to the command -> [String] -- ^ arguments to the command
-> Maybe FilePath -- ^ chdir to this path -> Maybe FilePath -- ^ chdir to this path
-> IO CapturedProcess -> m CapturedProcess
executeOut path args chdir = captureOutStreams $ do executeOut path args chdir = liftIO $ captureOutStreams $ do
maybe (pure ()) changeWorkingDirectory chdir maybe (pure ()) changeWorkingDirectory chdir
SPP.executeFile path True args Nothing SPP.executeFile path True args Nothing
@@ -316,12 +318,13 @@ createRegularFileFd fm dest =
-- | Thin wrapper around `executeFile`. -- | Thin wrapper around `executeFile`.
exec :: String -- ^ thing to execute exec :: MonadIO m
=> String -- ^ thing to execute
-> [String] -- ^ args for the thing -> [String] -- ^ args for the thing
-> Maybe FilePath -- ^ optionally chdir into this -> Maybe FilePath -- ^ optionally chdir into this
-> Maybe [(String, String)] -- ^ optional environment -> Maybe [(String, String)] -- ^ optional environment
-> IO (Either ProcessError ()) -> m (Either ProcessError ())
exec exe args chdir env = do exec exe args chdir env = liftIO $ do
pid <- SPP.forkProcess $ do pid <- SPP.forkProcess $ do
maybe (pure ()) changeWorkingDirectory chdir maybe (pure ()) changeWorkingDirectory chdir
SPP.executeFile exe (not ("./" `isPrefixOf` exe)) args env SPP.executeFile exe (not ("./" `isPrefixOf` exe)) args env
@@ -366,3 +369,18 @@ newFilePerms =
`unionFileModes` groupReadMode `unionFileModes` groupReadMode
`unionFileModes` otherWriteMode `unionFileModes` otherWriteMode
`unionFileModes` otherReadMode `unionFileModes` otherReadMode
-- | Checks whether the binary is a broken link.
isBrokenSymlink :: FilePath -> IO Bool
isBrokenSymlink fp = do
try (pathIsSymbolicLink fp) >>= \case
Right True -> do
let symDir = takeDirectory fp
tfp <- getSymbolicLinkTarget fp
not <$> doesPathExist
-- this drops 'symDir' if 'tfp' is absolute
(symDir </> tfp)
Right b -> pure b
Left e | isDoesNotExistError e -> pure False
| otherwise -> throwIO e

View File

@@ -15,6 +15,8 @@ Some of these functions use sophisticated logging.
-} -}
module GHCup.Utils.File.Windows where module GHCup.Utils.File.Windows where
import {-# SOURCE #-} GHCup.Utils ( getLinkTarget, pathIsLink )
import GHCup.Utils.Dirs
import GHCup.Utils.File.Common import GHCup.Utils.File.Common
import GHCup.Types import GHCup.Types
@@ -23,10 +25,12 @@ import Control.DeepSeq
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad import Control.Monad
import Control.Monad.Reader import Control.Monad.Reader
import Data.List
import Foreign.C.Error import Foreign.C.Error
import GHC.IO.Exception import GHC.IO.Exception
import GHC.IO.Handle import GHC.IO.Handle
import System.Directory import System.Directory
import System.Environment
import System.FilePath import System.FilePath
import System.IO import System.IO
import System.Process import System.Process
@@ -34,6 +38,7 @@ import System.Process
import qualified Control.Exception as EX import qualified Control.Exception as EX
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Map.Strict as Map
@@ -63,7 +68,7 @@ readCreateProcessWithExitCodeBS cp input = do
std_out = CreatePipe, std_out = CreatePipe,
std_err = CreatePipe std_err = CreatePipe
} }
withCreateProcess_ "readCreateProcessWithExitCode" cp_opts $ withCreateProcess_ "readCreateProcessWithExitCodeBS" cp_opts $
\mb_inh mb_outh mb_errh ph -> \mb_inh mb_outh mb_errh ph ->
case (mb_inh, mb_outh, mb_errh) of case (mb_inh, mb_outh, mb_errh) of
(Just inh, Just outh, Just errh) -> do (Just inh, Just outh, Just errh) -> do
@@ -130,12 +135,14 @@ withForkWait async' body = do
-- | Execute the given command and collect the stdout, stderr and the exit code. -- | Execute the given command and collect the stdout, stderr and the exit code.
-- The command is run in a subprocess. -- The command is run in a subprocess.
executeOut :: FilePath -- ^ command as filename, e.g. 'ls' executeOut :: MonadIO m
=> FilePath -- ^ command as filename, e.g. 'ls'
-> [String] -- ^ arguments to the command -> [String] -- ^ arguments to the command
-> Maybe FilePath -- ^ chdir to this path -> Maybe FilePath -- ^ chdir to this path
-> IO CapturedProcess -> m CapturedProcess
executeOut path args chdir = do executeOut path args chdir = do
(exit, out, err) <- readCreateProcessWithExitCodeBS (proc path args){ cwd = chdir } "" cp <- createProcessWithMingwPath ((proc path args){ cwd = chdir })
(exit, out, err) <- liftIO $ readCreateProcessWithExitCodeBS cp ""
pure $ CapturedProcess exit out err pure $ CapturedProcess exit out err
@@ -150,15 +157,16 @@ execLogged exe args chdir lfile env = do
AppState { dirs = Dirs {..} } <- ask AppState { dirs = Dirs {..} } <- ask
let stdoutLogfile = logsDir </> lfile <> ".stdout.log" let stdoutLogfile = logsDir </> lfile <> ".stdout.log"
stderrLogfile = logsDir </> lfile <> ".stderr.log" stderrLogfile = logsDir </> lfile <> ".stderr.log"
cp <- createProcessWithMingwPath ((proc exe args)
{ cwd = chdir
, env = env
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
})
fmap (toProcessError exe args) fmap (toProcessError exe args)
$ liftIO $ liftIO
$ withCreateProcess $ withCreateProcess cp
(proc exe args){ cwd = chdir
, env = env
, std_in = CreatePipe
, std_out = CreatePipe
, std_err = CreatePipe
}
$ \_ mout merr ph -> $ \_ mout merr ph ->
case (mout, merr) of case (mout, merr) of
(Just cStdout, Just cStderr) -> do (Just cStdout, Just cStderr) -> do
@@ -184,15 +192,15 @@ execLogged exe args chdir lfile env = do
-- | Thin wrapper around `executeFile`. -- | Thin wrapper around `executeFile`.
exec :: FilePath -- ^ thing to execute exec :: MonadIO m
=> FilePath -- ^ thing to execute
-> [FilePath] -- ^ args for the thing -> [FilePath] -- ^ args for the thing
-> Maybe FilePath -- ^ optionally chdir into this -> Maybe FilePath -- ^ optionally chdir into this
-> Maybe [(String, String)] -- ^ optional environment -> Maybe [(String, String)] -- ^ optional environment
-> IO (Either ProcessError ()) -> m (Either ProcessError ())
exec exe args chdir env = do exec exe args chdir env = do
exit_code <- withCreateProcess cp <- createProcessWithMingwPath ((proc exe args) { cwd = chdir, env = env })
(proc exe args) { cwd = chdir, env = env } $ \_ _ _ p -> exit_code <- liftIO $ withCreateProcess cp $ \_ _ _ p -> waitForProcess p
waitForProcess p
pure $ toProcessError exe args exit_code pure $ toProcessError exe args exit_code
@@ -200,3 +208,33 @@ chmod_755 :: MonadIO m => FilePath -> m ()
chmod_755 fp = chmod_755 fp =
let perm = setOwnerWritable True emptyPermissions let perm = setOwnerWritable True emptyPermissions
in liftIO $ setPermissions fp perm in liftIO $ setPermissions fp perm
createProcessWithMingwPath :: MonadIO m
=> CreateProcess
-> m CreateProcess
createProcessWithMingwPath cp = do
baseDir <- liftIO ghcupBaseDir
cEnv <- Map.fromList <$> maybe (liftIO getEnvironment) pure (env cp)
let mingWPaths = [baseDir </> "msys64" </> "usr" </> "bin"
,baseDir </> "msys64" </> "mingw64" </> "bin"]
paths = ["PATH", "Path"]
curPaths = (\x -> maybe [] splitSearchPath (Map.lookup x cEnv)) =<< paths
newPath = intercalate [searchPathSeparator] (mingWPaths ++ curPaths)
envWithoutPath = foldr (\x y -> Map.delete x y) cEnv paths
envWithNewPath = Map.insert "Path" newPath envWithoutPath
liftIO $ setEnv "Path" newPath
pure $ cp { env = Just $ Map.toList envWithNewPath }
-- | Checks whether the binary is a broken link.
isBrokenSymlink :: FilePath -> IO Bool
isBrokenSymlink fp = do
b <- pathIsLink fp
if b
then do
tfp <- getLinkTarget fp
not <$> doesPathExist
-- this drops 'symDir' if 'tfp' is absolute
(takeDirectory fp </> tfp)
else pure False

View File

@@ -14,13 +14,11 @@ Here we define our main logger.
-} -}
module GHCup.Utils.Logger where module GHCup.Utils.Logger where
import GHCup.Types
import GHCup.Utils.File import GHCup.Utils.File
import GHCup.Utils.String.QQ import GHCup.Utils.String.QQ
import Control.Monad import Control.Monad
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.Logger import Control.Monad.Logger
import Prelude hiding ( appendFile ) import Prelude hiding ( appendFile )
import System.Console.Pretty import System.Console.Pretty
@@ -68,9 +66,8 @@ myLoggerT LoggerConfig {..} loggingt = runLoggingT loggingt mylogger
rawOutter outr rawOutter outr
initGHCupFileLogging :: (MonadIO m, MonadReader AppState m) => m FilePath initGHCupFileLogging :: (MonadIO m) => FilePath -> m FilePath
initGHCupFileLogging = do initGHCupFileLogging logsDir = do
AppState {dirs = Dirs {..}} <- ask
let logfile = logsDir </> "ghcup.log" let logfile = logsDir </> "ghcup.log"
liftIO $ do liftIO $ do
createDirectoryIfMissing True logsDir createDirectoryIfMissing True logsDir
@@ -80,7 +77,7 @@ initGHCupFileLogging = do
execBlank execBlank
([s|^.*\.log$|] :: B.ByteString) ([s|^.*\.log$|] :: B.ByteString)
) )
forM_ logFiles $ hideError doesNotExistErrorType . removeFile . (logsDir </>) forM_ logFiles $ hideError doesNotExistErrorType . rmFile . (logsDir </>)
writeFile logfile "" writeFile logfile ""
pure logfile pure logfile

View File

@@ -1,3 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
@@ -25,6 +26,8 @@ import Control.Monad.IO.Class
import Control.Monad.Trans.Class ( lift ) import Control.Monad.Trans.Class ( lift )
import Data.Bifunctor import Data.Bifunctor
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
import Data.List ( nub )
import Data.Foldable
import Data.String import Data.String
import Data.Text ( Text ) import Data.Text ( Text )
import Data.Versions import Data.Versions
@@ -32,6 +35,15 @@ import Data.Word8
import Haskus.Utils.Types.List import Haskus.Utils.Types.List
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import System.IO.Error import System.IO.Error
import System.IO.Unsafe
import System.Directory
import System.FilePath
#if defined(IS_WINDOWS)
import Control.Retry
import GHC.IO.Exception
#endif
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified Data.Strict.Maybe as S import qualified Data.Strict.Maybe as S
@@ -276,3 +288,139 @@ escapeVerRex = B.pack . go . B.unpack . verToBS
go (x : xs) | x == _period = [_backslash, _period] ++ go xs go (x : xs) | x == _period = [_backslash, _period] ++ go xs
| otherwise = x : go xs | otherwise = x : go xs
-- | More permissive version of 'createDirRecursive'. This doesn't
-- error when the destination is a symlink to a directory.
createDirRecursive' :: FilePath -> IO ()
createDirRecursive' p =
handleIO (\e -> if isAlreadyExistsError e then isSymlinkDir e else throwIO e)
. createDirectoryIfMissing True
$ p
where
isSymlinkDir e = do
ft <- pathIsSymbolicLink p
case ft of
True -> do
rp <- canonicalizePath p
rft <- doesDirectoryExist rp
case rft of
True -> pure ()
_ -> throwIO e
_ -> throwIO e
-- | Recursively copy the contents of one directory to another path.
--
-- This is a rip-off of Cabal library.
copyDirectoryRecursive :: FilePath -> FilePath -> IO ()
copyDirectoryRecursive srcDir destDir = do
srcFiles <- getDirectoryContentsRecursive srcDir
copyFilesWith copyFile destDir [ (srcDir, f)
| f <- srcFiles ]
where
-- | Common implementation of 'copyFiles', 'installOrdinaryFiles',
-- 'installExecutableFiles' and 'installMaybeExecutableFiles'.
copyFilesWith :: (FilePath -> FilePath -> IO ())
-> FilePath -> [(FilePath, FilePath)] -> IO ()
copyFilesWith doCopy targetDir srcFiles = do
-- Create parent directories for everything
let dirs = map (targetDir </>) . nub . map (takeDirectory . snd) $ srcFiles
traverse_ (createDirectoryIfMissing True) dirs
-- Copy all the files
sequence_ [ let src = srcBase </> srcFile
dest = targetDir </> srcFile
in doCopy src dest
| (srcBase, srcFile) <- srcFiles ]
-- | List all the files in a directory and all subdirectories.
--
-- The order places files in sub-directories after all the files in their
-- parent directories. The list is generated lazily so is not well defined if
-- the source directory structure changes before the list is used.
--
getDirectoryContentsRecursive :: FilePath -> IO [FilePath]
getDirectoryContentsRecursive topdir = recurseDirectories [""]
where
recurseDirectories :: [FilePath] -> IO [FilePath]
recurseDirectories [] = return []
recurseDirectories (dir:dirs) = unsafeInterleaveIO $ do
(files, dirs') <- collect [] [] =<< getDirectoryContents (topdir </> dir)
files' <- recurseDirectories (dirs' ++ dirs)
return (files ++ files')
where
collect files dirs' [] = return (reverse files
,reverse dirs')
collect files dirs' (entry:entries) | ignore entry
= collect files dirs' entries
collect files dirs' (entry:entries) = do
let dirEntry = dir </> entry
isDirectory <- doesDirectoryExist (topdir </> dirEntry)
if isDirectory
then collect files (dirEntry:dirs') entries
else collect (dirEntry:files) dirs' entries
ignore ['.'] = True
ignore ['.', '.'] = True
ignore _ = False
-- https://github.com/haskell/directory/issues/110
-- https://github.com/haskell/directory/issues/96
-- https://www.sqlite.org/src/info/89f1848d7f
rmPath :: (MonadIO m, MonadMask m)
=> FilePath
-> m ()
rmPath fp =
#if defined(IS_WINDOWS)
recovering (fullJitterBackoff 25000 <> limitRetries 10)
[\_ -> Handler (\e -> pure $ isPermissionError e)
,\_ -> Handler (\e -> pure (ioeGetErrorType e == UnsatisfiedConstraints))
,\_ -> Handler (\e -> pure (ioeGetErrorType e == InappropriateType))
]
(\_ -> liftIO $ removePathForcibly fp)
#else
liftIO $ removeDirectoryRecursive fp
#endif
-- https://www.sqlite.org/src/info/89f1848d7f
-- https://github.com/haskell/directory/issues/96
rmFile :: (MonadIO m, MonadMask m)
=> FilePath
-> m ()
rmFile fp =
#if defined(IS_WINDOWS)
recovering (fullJitterBackoff 25000 <> limitRetries 10)
[\_ -> Handler (\e -> pure $ isPermissionError e)
,\_ -> Handler (\e -> pure (ioeGetErrorType e == UnsatisfiedConstraints))
]
(\_ -> liftIO $ removeFile fp)
#else
liftIO $ removeFile fp
#endif
-- Gathering monoidal values
traverseFold :: (Foldable t, Applicative m, Monoid b) => (a -> m b) -> t a -> m b
traverseFold f = foldl (\mb a -> (<>) <$> mb <*> f a) (pure mempty)
-- | Gathering monoidal values
forFold :: (Foldable t, Applicative m, Monoid b) => t a -> (a -> m b) -> m b
forFold = \t -> (`traverseFold` t)
-- | Strip @\\r@ and @\\n@ from 'ByteString's
stripNewline :: String -> String
stripNewline s
| null s = []
| head s `elem` "\n\r" = stripNewline (tail s)
| otherwise = head s : stripNewline (tail s)
isNewLine :: Word8 -> Bool
isNewLine w
| w == _lf = True
| w == _cr = True
| otherwise = False

View File

@@ -25,7 +25,7 @@ import qualified Data.Text as T
-- | This reflects the API version of the YAML. -- | This reflects the API version of the YAML.
ghcupURL :: URI ghcupURL :: URI
ghcupURL = [uri|https://www.haskell.org/ghcup/data/ghcup-0.0.4.yaml|] ghcupURL = [uri|https://www.haskell.org/ghcup/data/ghcup-0.0.5.yaml|]
-- | The current ghcup version. -- | The current ghcup version.
ghcUpVer :: PVP ghcUpVer :: PVP

View File

@@ -171,6 +171,10 @@ instance Arbitrary Tool where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink shrink = genericShrink
instance Arbitrary GlobalTool where
arbitrary = genericArbitrary
shrink = genericShrink
instance Arbitrary GHCupInfo where instance Arbitrary GHCupInfo where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink shrink = genericShrink

View File

@@ -20,7 +20,7 @@
</a> </a>
<p id="pitch"> <p id="pitch">
<em>ghcup</em> is an installer for<br/> <em>ghcup</em> is the main installer for<br/>
the general purpose language <a href="https://www.haskell.org/">Haskell</a> the general purpose language <a href="https://www.haskell.org/">Haskell</a>
</p> </p>
@@ -124,7 +124,7 @@
</div> </div>
<p> <p>
Need help? <a href="http://webchat.freenode.net/?randomnick=1&channels=%23haskell&uio=d4">Ask on #haskell</a> or <a href="https://gitlab.haskell.org/haskell/ghcup-hs/issues">report a bug</a>. Need help? Ask on <a href="https://kiwiirc.com/nextclient/irc.libera.chat/#haskell-ghcup">#haskell-ghcup</a>, <a href="https://kiwiirc.com/nextclient/irc.libera.chat/#haskell">#haskell</a> or <a href="https://gitlab.haskell.org/haskell/ghcup-hs/issues">report a bug</a>.
</p> </p>
<p id="about"> <p id="about">
@@ -145,7 +145,7 @@
<noscript> <noscript>
<p id="pitch"> <p id="pitch">
<em>ghcup</em> is an installer for<br/> <em>ghcup</em> is the main installer for<br/>
the general purpose language <a href="https://www.haskell.org/">Haskell</a> the general purpose language <a href="https://www.haskell.org/">Haskell</a>
</p> </p>
@@ -171,7 +171,7 @@
</div> </div>
<p> <p>
Need help? <a href="http://webchat.freenode.net/?randomnick=1&amp;channels=%23haskell&amp;uio=d4">Ask on #haskell</a>. Need help? Ask on <a href="https://kiwiirc.com/nextclient/irc.libera.chat/#haskell-ghcup">#haskell-ghcup</a>, <a href="https://kiwiirc.com/nextclient/irc.libera.chat/#haskell">#haskell</a> or <a href="https://gitlab.haskell.org/haskell/ghcup-hs/issues">report a bug</a>.
</p> </p>
<p id="about"> <p id="about">