Compare commits

..

6 Commits

27 changed files with 1057 additions and 324 deletions

View File

@@ -11,7 +11,7 @@ module BrickMain where
import GHCup import GHCup
import GHCup.Download import GHCup.Download
import GHCup.Errors import GHCup.Errors
import GHCup.Types.Optics ( getDirs ) import GHCup.Types.Optics ( getDirs, getPlatformReq )
import GHCup.Types hiding ( LeanAppState(..) ) import GHCup.Types hiding ( LeanAppState(..) )
import GHCup.Utils import GHCup.Utils
import GHCup.OptParse.Common (logGHCPostRm) import GHCup.OptParse.Common (logGHCPostRm)
@@ -30,6 +30,7 @@ import Brick.Widgets.List ( listSelectedFocusedAttr
, listAttr , listAttr
) )
import Codec.Archive import Codec.Archive
import Control.Applicative
import Control.Exception.Safe import Control.Exception.Safe
#if !MIN_VERSION_base(4,13,0) #if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail ) import Control.Monad.Fail ( MonadFail )
@@ -94,7 +95,7 @@ data BrickState = BrickState
keyHandlers :: KeyBindings keyHandlers :: KeyBindings
-> [ ( Vty.Key -> [ ( KeyCombination
, BrickSettings -> String , BrickSettings -> String
, BrickState -> EventM String BrickState () , BrickState -> EventM String BrickState ()
) )
@@ -131,6 +132,9 @@ showKey Vty.KUp = "↑"
showKey Vty.KDown = "" showKey Vty.KDown = ""
showKey key = tail (show key) showKey key = tail (show key)
showMod :: Vty.Modifier -> String
showMod = tail . show
ui :: AttrMap -> BrickState -> Widget String ui :: AttrMap -> BrickState -> Widget String
ui dimAttrs BrickState{ appSettings = as@BrickSettings{}, ..} ui dimAttrs BrickState{ appSettings = as@BrickSettings{}, ..}
@@ -147,7 +151,7 @@ ui dimAttrs BrickState{ appSettings = as@BrickSettings{}, ..}
. txtWrap . txtWrap
. T.pack . T.pack
. foldr1 (\x y -> x <> " " <> y) . foldr1 (\x y -> x <> " " <> y)
. fmap (\(key, s, _) -> showKey key <> ":" <> s as) . fmap (\(KeyCombination key mods, s, _) -> intercalate "+" (showKey key : (showMod <$> mods)) <> ":" <> s as)
$ keyHandlers appKeys $ keyHandlers appKeys
header = header =
minHSize 2 emptyWidget minHSize 2 emptyWidget
@@ -321,12 +325,12 @@ eventHandler st@BrickState{..} ev = do
(MouseDown _ Vty.BScrollDown _ _) -> (MouseDown _ Vty.BScrollDown _ _) ->
put (BrickState { appState = moveCursor 1 appState Down, .. }) put (BrickState { appState = moveCursor 1 appState Down, .. })
(VtyEvent (Vty.EvResize _ _)) -> put st (VtyEvent (Vty.EvResize _ _)) -> put st
(VtyEvent (Vty.EvKey Vty.KUp _)) -> (VtyEvent (Vty.EvKey Vty.KUp [])) ->
put BrickState{ appState = moveCursor 1 appState Up, .. } put BrickState{ appState = moveCursor 1 appState Up, .. }
(VtyEvent (Vty.EvKey Vty.KDown _)) -> (VtyEvent (Vty.EvKey Vty.KDown [])) ->
put BrickState{ appState = moveCursor 1 appState Down, .. } put BrickState{ appState = moveCursor 1 appState Down, .. }
(VtyEvent (Vty.EvKey key _)) -> (VtyEvent (Vty.EvKey key mods)) ->
case find (\(key', _, _) -> key' == key) (keyHandlers kb) of case find (\(keyCombo, _, _) -> keyCombo == KeyCombination key mods) (keyHandlers kb) of
Nothing -> put st Nothing -> put st
Just (_, _, handler) -> handler st Just (_, _, handler) -> handler st
_ -> put st _ -> put st
@@ -432,7 +436,7 @@ filterVisible v t e | lInstalled e = True
(lTool e `notElem` hiddenTools) (lTool e `notElem` hiddenTools)
install' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m) install' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m, Alternative m)
=> BrickState => BrickState
-> (Int, ListResult) -> (Int, ListResult)
-> m (Either String ()) -> m (Either String ())
@@ -463,6 +467,11 @@ install' _ (_, ListResult {..}) = do
, ToolShadowed , ToolShadowed
, UninstallFailed , UninstallFailed
, MergeFileTreeError , MergeFileTreeError
, NoCompatiblePlatform
, GHCup.Errors.ParseError
, UnsupportedSetupCombo
, DistroNotFound
, NoCompatibleArch
] ]
run (do run (do
@@ -509,7 +518,7 @@ install' _ (_, ListResult {..}) = do
<> "Also check the logs in ~/.ghcup/logs" <> "Also check the logs in ~/.ghcup/logs"
set' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m) set' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m, Alternative m)
=> BrickState => BrickState
-> (Int, ListResult) -> (Int, ListResult)
-> m (Either String ()) -> m (Either String ())
@@ -651,8 +660,10 @@ getGHCupInfo = do
r <- r <-
flip runReaderT settings flip runReaderT settings
. runE @'[DigestError, ContentLengthError, GPGError, JSONError , DownloadFailed , FileDoesNotExistError] . runE @'[DigestError, ContentLengthError, GPGError, JSONError , DownloadFailed , FileDoesNotExistError, StackPlatformDetectError]
$ liftE getDownloadsF $ do
pfreq <- lift getPlatformReq
liftE $ getDownloadsF pfreq
case r of case r of
VRight a -> pure $ Right a VRight a -> pure $ Right a

View File

@@ -42,7 +42,6 @@ import Data.Aeson.Encode.Pretty ( encodePretty )
import Data.Either import Data.Either
import Data.Functor import Data.Functor
import Data.Maybe import Data.Maybe
import Data.Versions
import GHC.IO.Encoding import GHC.IO.Encoding
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Language.Haskell.TH import Language.Haskell.TH
@@ -85,7 +84,7 @@ toSettings options = do
keepDirs = fromMaybe (fromMaybe (Types.keepDirs defaultSettings) uKeepDirs) optKeepDirs keepDirs = fromMaybe (fromMaybe (Types.keepDirs defaultSettings) uKeepDirs) optKeepDirs
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 (Types.urlSource defaultSettings) uUrlSource) (OwnSource . (:[]) . Right) optUrlSource urlSource = fromMaybe (fromMaybe (Types.urlSource defaultSettings) uUrlSource) optUrlSource
noNetwork = fromMaybe (fromMaybe (Types.noNetwork defaultSettings) uNoNetwork) optNoNetwork noNetwork = fromMaybe (fromMaybe (Types.noNetwork defaultSettings) uNoNetwork) optNoNetwork
gpgSetting = fromMaybe (fromMaybe (Types.gpgSetting defaultSettings) uGPGSetting) optGpg gpgSetting = fromMaybe (fromMaybe (Types.gpgSetting defaultSettings) uGPGSetting) optGpg
platformOverride = optPlatform <|> (uPlatformOverride <|> Types.platformOverride defaultSettings) platformOverride = optPlatform <|> (uPlatformOverride <|> Types.platformOverride defaultSettings)
@@ -211,10 +210,9 @@ Report bugs at <https://github.com/haskell/ghcup-hs/issues>|]
exitWith (ExitFailure 2) exitWith (ExitFailure 2)
ghcupInfo <- ghcupInfo <-
( flip runReaderT leanAppstate ( flip runReaderT leanAppstate . runE @'[ContentLengthError, DigestError, DistroNotFound, DownloadFailed, FileDoesNotExistError, GPGError, JSONError, NoCompatibleArch, NoCompatiblePlatform, NoDownload, GHCup.Errors.ParseError, ProcessError, UnsupportedSetupCombo, StackPlatformDetectError] $ do
. runE @'[DigestError, ContentLengthError, GPGError, JSONError , DownloadFailed, FileDoesNotExistError] liftE $ getDownloadsF pfreq
$ liftE getDownloadsF )
)
>>= \case >>= \case
VRight r -> pure r VRight r -> pure r
VLeft e -> do VLeft e -> do
@@ -341,9 +339,9 @@ Report bugs at <https://github.com/haskell/ghcup-hs/issues>|]
] m Bool ] m Bool
alreadyInstalling (Install (Right InstallOptions{..})) (GHC, ver) = cmp' GHC instVer ver alreadyInstalling (Install (Right InstallOptions{..})) (GHC, ver) = cmp' GHC instVer ver
alreadyInstalling (Install (Left (InstallGHC InstallOptions{..}))) (GHC, ver) = cmp' GHC instVer ver alreadyInstalling (Install (Left (InstallGHC InstallOptions{..}))) (GHC, ver) = cmp' GHC instVer ver
alreadyInstalling (Install (Left (InstallCabal InstallOptions{..}))) (Cabal, ver) = cmp' Cabal instVer ver alreadyInstalling (Install (Left (InstallCabal InstallOptions{..}))) (Cabal, ver) = cmp' Cabal instVer ver
alreadyInstalling (Install (Left (InstallHLS InstallOptions{..}))) (HLS, ver) = cmp' HLS instVer ver alreadyInstalling (Install (Left (InstallHLS InstallOptions{..}))) (HLS, ver) = cmp' HLS instVer ver
alreadyInstalling (Install (Left (InstallStack InstallOptions{..}))) (Stack, ver) = cmp' Stack instVer ver alreadyInstalling (Install (Left (InstallStack InstallOptions{..}))) (Stack, ver) = cmp' Stack instVer ver
alreadyInstalling (Compile (CompileGHC GHCCompileOptions{ ovewrwiteVer = Just over })) alreadyInstalling (Compile (CompileGHC GHCCompileOptions{ ovewrwiteVer = Just over }))
(GHC, ver) = cmp' GHC (Just $ GHCVersion (mkTVer over)) ver (GHC, ver) = cmp' GHC (Just $ GHCVersion (mkTVer over)) ver
alreadyInstalling (Compile (CompileGHC GHCCompileOptions{ targetGhc = GHC.SourceDist tver })) alreadyInstalling (Compile (CompileGHC GHCCompileOptions{ targetGhc = GHC.SourceDist tver }))
@@ -378,3 +376,4 @@ Report bugs at <https://github.com/haskell/ghcup-hs/issues>|]
cmp' tool instVer ver = do cmp' tool instVer ver = do
(v, _) <- liftE $ fromVersion instVer tool (v, _) <- liftE $ fromVersion instVer tool
pure (v == ver) pure (v == ver)

View File

@@ -16,6 +16,11 @@ gpg-setting: GPGNone # GPGStrict | GPGLax | GPGNone
# TUI key bindings, # TUI key bindings,
# see https://hackage.haskell.org/package/vty-5.31/docs/Graphics-Vty-Input-Events.html#t:Key # see https://hackage.haskell.org/package/vty-5.31/docs/Graphics-Vty-Input-Events.html#t:Key
# for possible values. # for possible values.
# It's also possible to define key+modifier, e.g.:
# quit:
# Key:
# KChar: c
# Mods: [MCtrl]
key-bindings: key-bindings:
up: up:
KUp: [] KUp: []
@@ -46,41 +51,45 @@ meta-cache: 300 # in seconds
# 2. Strict: fail hard # 2. Strict: fail hard
meta-mode: Lax # Strict | Lax meta-mode: Lax # Strict | Lax
# Where to get GHC/cabal/hls download info/versions from. For more detailed explanation # Where to get GHC/cabal/hls download info/versions from. This is a list that performs
# check the 'URLSource' type in the code. # union over tool versions, preferring the later entries.
url-source: url-source:
## Use the internal download uri, this is the default ## Use the internal download uri, this is the default
GHCupURL: [] - GHCupURL
## Example 1: Read download info from this location instead ## Prefer stack supplied metadata (will still use GHCup metadata for versions not existing in stack metadata)
## Accepts file/http/https scheme # - StackSetupURL
## Can also be an array of URLs or an array of 'Either GHCupInfo URL', in
## which case they are merged right-biased (overwriting duplicate versions).
# OwnSource: "file:///home/jule/git/ghcup-hs/ghcup-0.0.3.yaml"
## Example 2: Add custom tarballs to the default downloads, overwriting duplicate versions. ## Add pre-release channel
## Can also be an array of 'Either GHCupInfo URL', also see Example 3. # - https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml
# AddSource: ## Add nightly channel
# Left: # - https://ghc.gitlab.haskell.org/ghcup-metadata/ghcup-nightlies-0.0.7.yaml
# globalTools: {} ## Add cross compiler channel
# toolRequirements: {} # - https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-cross-0.0.8.yaml
# ghcupDownloads:
# GHC:
# 9.10.2:
# viTags: []
# viArch:
# A_64:
# Linux_UnknownLinux:
# unknown_versioning:
# dlUri: https://downloads.haskell.org/~ghc/7.10.3/ghc-7.10.3-x86_64-deb8-linux.tar.bz2
# dlSubdir: ghc-7.10.3
# dlHash: 01cfbad8dff1e8b34a5fdca8caeaf843b56e36af919e29cd68870d2588563db5
## Example 3: Add multiple custom download files to the default downloads via right-biased merge (overwriting duplicate ## Use dwarf bindist for 9.4.7 for ghcup metadata
## versions). # - ghcup-info:
# AddSource: # ghcupDownloads:
# - Right: "file:///home/jule/git/ghcup-hs/ghcup-prereleases.yaml" # GHC:
# - Right: "file:///home/jule/git/ghcup-hs/ghcup-custom.yaml" # 9.4.7:
# viTags: []
# viArch:
# A_64:
# Linux_UnknownLinux:
# unknown_versioning:
# dlUri: https://downloads.haskell.org/ghc/9.4.7/ghc-9.4.7-x86_64-deb10-linux-dwarf.tar.xz
# dlSubdir:
# RegexDir: "ghc-.*"
# dlHash: b261b3438ba455e3cf757f9c8dc3a06fdc061ea8ec287a65b7809e25fe18bad4
## for stack metadata and the linux64-tinfo6 bindists, use static alpine for 9.8.1
# - setup-info:
# ghc:
# linux64-tinfo6:
# 9.8.1:
# url: "https://downloads.haskell.org/~ghc/9.8.1/ghc-9.8.1-x86_64-alpine3_12-linux-static.tar.xz"
# content-length: 229037440
# sha256: b48f3d3a508d0c140d1c801e04afc65e80c0d25e7e939a8a41edb387b26b81b3
# This is a way to override platform detection, e.g. when you're running # This is a way to override platform detection, e.g. when you're running
# a Ubuntu derivate based on 18.04, you could do: # a Ubuntu derivate based on 18.04, you could do:

View File

@@ -153,8 +153,7 @@ To use a mirror, set the following option in `~/.ghcup/config.yaml`:
```yml ```yml
url-source: url-source:
# Accepts file/http/https scheme - https://some-url/ghcup-0.0.6.yaml
OwnSource: "https://some-url/ghcup-0.0.6.yaml"
``` ```
See [config.yaml](https://github.com/haskell/ghcup-hs/blob/master/data/config.yaml) See [config.yaml](https://github.com/haskell/ghcup-hs/blob/master/data/config.yaml)
@@ -184,8 +183,8 @@ This will result in `~/.ghcup/config.yaml` to contain this record:
```yml ```yml
url-source: url-source:
AddSource: - GHCupURL
- Right: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml - https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml
``` ```
You can add as many channels as you like. They are combined under *Last*, so versions from the prerelease channel You can add as many channels as you like. They are combined under *Last*, so versions from the prerelease channel
@@ -195,14 +194,13 @@ To remove the channel, delete the entire `url-source` section or set it back to
```yml ```yml
url-source: url-source:
GHCupURL: [] - GHCupURL
``` ```
If you want to combine your release channel with a mirror, you'd do it like so: If you want to combine your release channel with a mirror, you'd do it like so:
```yml ```yml
url-source: url-source:
OwnSource:
# base metadata # base metadata
- "https://mirror.sjtu.edu.cn/ghcup/yaml/ghcup/data/ghcup-0.0.6.yaml" - "https://mirror.sjtu.edu.cn/ghcup/yaml/ghcup/data/ghcup-0.0.6.yaml"
# prerelease channel # prerelease channel
@@ -246,6 +244,46 @@ stack config set install-ghc false --global
stack config set system-ghc true --global stack config set system-ghc true --global
``` ```
### Using stack's setup-info metadata to install GHC
You can now use stack's [setup-info metadata](https://github.com/commercialhaskell/stackage-content/blob/master/stack/stack-setup-2.yaml)
to install GHC. For that, you can invoke ghcup like so as a shorthand:
```sh
# ghcup will only see GHC now
ghcup -s StackSetupURL install ghc 9.4.7
# this combines both ghcup and stack metadata
ghcup -s '["GHCupURL", "StackSetupURL"]' install ghc 9.4.7
```
To make this permanent and combine it with the GHCup metadata, you can add the following to your `~/.ghcup/config.yaml`:
```yaml
url-source:
- GHCupURL
# stack versions take precedence
# you'll still have access to GHCup provided versions and tools in case they don't exist in stack metadata
- StackSetupURL
```
You can customize or add sections to the setup-info similar to how the [stack documentation](https://docs.haskellstack.org/en/stable/yaml_configuration/#setup-info) explains it. E.g. to change the 9.4.7 bindist, you might do:
```yaml
url-source:
- GHCupURL
- StackSetupURL
- setup-info:
ghc:
linux64-tinfo6:
9.4.7:
url: "https://downloads.haskell.org/~ghc/9.4.7/ghc-9.4.7-x86_64-fedora27-linux.tar.xz"
content-length: 179117892
sha256: 216b76b7c6383e6ad9ba82533f323f8550e52893a8b9fa33c7b9dc4201ac766a
```
The main caveat with using this method is that there's no guarantee that GHCup will pick a compatible HLS bindist
when you try to install HLS.
### Windows ### Windows
On windows, you may find the following config options useful too: On windows, you may find the following config options useful too:

View File

@@ -117,7 +117,9 @@ library
GHCup.Types GHCup.Types
GHCup.Types.JSON GHCup.Types.JSON
GHCup.Types.JSON.Utils GHCup.Types.JSON.Utils
GHCup.Types.JSON.Versions
GHCup.Types.Optics GHCup.Types.Optics
GHCup.Types.Stack
GHCup.Utils GHCup.Utils
GHCup.Utils.Dirs GHCup.Utils.Dirs
GHCup.Version GHCup.Version

View File

@@ -57,16 +57,13 @@ import GHCup.Types
import Control.Monad.Fail ( MonadFail ) import Control.Monad.Fail ( MonadFail )
#endif #endif
import Control.Monad.Reader import Control.Monad.Reader
import Data.Bifunctor
import Data.Either import Data.Either
import Data.Functor import Data.Functor
import Data.Maybe import Data.Maybe
import Options.Applicative hiding ( style ) import Options.Applicative hiding ( style )
import Options.Applicative.Help.Pretty ( text ) import Options.Applicative.Help.Pretty ( text )
import Prelude hiding ( appendFile ) import Prelude hiding ( appendFile )
import URI.ByteString
import qualified Data.ByteString.UTF8 as UTF8
data Options = Options data Options = Options
@@ -77,12 +74,13 @@ data Options = Options
, optMetaCache :: Maybe Integer , optMetaCache :: Maybe Integer
, optMetaMode :: Maybe MetaMode , optMetaMode :: Maybe MetaMode
, optPlatform :: Maybe PlatformRequest , optPlatform :: Maybe PlatformRequest
, optUrlSource :: Maybe URI , optUrlSource :: Maybe URLSource
, optNoVerify :: Maybe Bool , optNoVerify :: Maybe Bool
, optKeepDirs :: Maybe KeepDirs , optKeepDirs :: Maybe KeepDirs
, optsDownloader :: Maybe Downloader , optsDownloader :: Maybe Downloader
, optNoNetwork :: Maybe Bool , optNoNetwork :: Maybe Bool
, optGpg :: Maybe GPGSetting , optGpg :: Maybe GPGSetting
, optStackSetup :: Maybe Bool
-- commands -- commands
, optCommand :: Command , optCommand :: Command
} }
@@ -134,13 +132,13 @@ opts =
) )
<*> optional <*> optional
(option (option
(eitherReader parseUri) (eitherReader parseUrlSource)
( short 's' ( short 's'
<> long "url-source" <> long "url-source"
<> metavar "URL" <> metavar "URL_SOURCE"
<> help "Alternative ghcup download info url" <> help "Alternative ghcup download info"
<> internal <> internal
<> completer fileUri <> completer urlSourceCompleter
) )
) )
<*> (fmap . fmap) not (invertableSwitch "verify" (Just 'n') True (help "Disable tarball checksum verification (default: enabled)")) <*> (fmap . fmap) not (invertableSwitch "verify" (Just 'n') True (help "Disable tarball checksum verification (default: enabled)"))
@@ -178,10 +176,9 @@ opts =
"GPG verification (default: none)" "GPG verification (default: none)"
<> completer (listCompleter ["strict", "lax", "none"]) <> completer (listCompleter ["strict", "lax", "none"])
)) ))
<*> invertableSwitch "stack-setup" (Just 's') False (help "Use stack's setup info for discovering and installing GHC versions")
<*> com <*> com
where
parseUri s' =
first show $ parseURI strictURIParserOptions (UTF8.fromString s')
com :: Parser Command com :: Parser Command

View File

@@ -64,6 +64,8 @@ import URI.ByteString
import qualified Data.ByteString.UTF8 as UTF8 import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy.Encoding as LE
import qualified Data.Text.Lazy as LT
import qualified Text.Megaparsec as MP import qualified Text.Megaparsec as MP
import qualified System.FilePath.Posix as FP import qualified System.FilePath.Posix as FP
import GHCup.Version import GHCup.Version
@@ -322,6 +324,15 @@ toolCompleter = listCompleter ["ghc", "cabal", "hls", "stack"]
gitFileUri :: [String] -> Completer gitFileUri :: [String] -> Completer
gitFileUri add = mkCompleter $ fileUri' (["git://"] <> add) gitFileUri add = mkCompleter $ fileUri' (["git://"] <> add)
urlSourceCompleter :: Completer
urlSourceCompleter = mkCompleter $ urlSourceCompleter' []
urlSourceCompleter' :: [String] -> String -> IO [String]
urlSourceCompleter' add str' = do
let static = ["GHCupURL", "StackSetupURL"]
file <- fileUri' add str'
pure $ static ++ file
fileUri :: Completer fileUri :: Completer
fileUri = mkCompleter $ fileUri' [] fileUri = mkCompleter $ fileUri' []
@@ -450,13 +461,15 @@ tagCompleter tool add = listIOCompleter $ do
defaultKeyBindings defaultKeyBindings
loggerConfig loggerConfig
mGhcUpInfo <- flip runReaderT appState . runE $ getDownloadsF mpFreq <- flip runReaderT appState . runE $ platformRequest
case mGhcUpInfo of forFold mpFreq $ \pfreq -> do
VRight ghcupInfo -> do mGhcUpInfo <- flip runReaderT appState . runE $ getDownloadsF pfreq
let allTags = filter (/= Old) case mGhcUpInfo of
$ _viTags =<< M.elems (availableToolVersions (_ghcupDownloads ghcupInfo) tool) VRight ghcupInfo -> do
pure $ nub $ (add ++) $ fmap tagToString allTags let allTags = filter (/= Old)
VLeft _ -> pure (nub $ ["recommended", "latest", "latest-prerelease"] ++ add) $ _viTags =<< M.elems (availableToolVersions (_ghcupDownloads ghcupInfo) tool)
pure $ nub $ (add ++) $ fmap tagToString allTags
VLeft _ -> pure (nub $ ["recommended", "latest", "latest-prerelease"] ++ add)
versionCompleter :: [ListCriteria] -> Tool -> Completer versionCompleter :: [ListCriteria] -> Tool -> Completer
versionCompleter criteria tool = versionCompleter' criteria tool (const True) versionCompleter criteria tool = versionCompleter' criteria tool (const True)
@@ -477,8 +490,8 @@ versionCompleter' criteria tool filter' = listIOCompleter $ do
defaultKeyBindings defaultKeyBindings
loggerConfig loggerConfig
mpFreq <- flip runReaderT leanAppState . runE $ platformRequest mpFreq <- flip runReaderT leanAppState . runE $ platformRequest
mGhcUpInfo <- flip runReaderT leanAppState . runE $ getDownloadsF
forFold mpFreq $ \pfreq -> do forFold mpFreq $ \pfreq -> do
mGhcUpInfo <- flip runReaderT leanAppState . runE $ getDownloadsF pfreq
forFold mGhcUpInfo $ \ghcupInfo -> do forFold mGhcUpInfo $ \ghcupInfo -> do
let appState = AppState let appState = AppState
settings settings
@@ -817,3 +830,15 @@ logGHCPostRm ghcVer = do
let storeGhcDir = cabalStore </> ("ghc-" <> T.unpack (prettyVer $ _tvVersion ghcVer)) let storeGhcDir = cabalStore </> ("ghc-" <> T.unpack (prettyVer $ _tvVersion ghcVer))
logInfo $ T.pack $ "After removing GHC you might also want to clean up your cabal store at: " <> storeGhcDir logInfo $ T.pack $ "After removing GHC you might also want to clean up your cabal store at: " <> storeGhcDir
parseUrlSource :: String -> Either String URLSource
parseUrlSource "GHCupURL" = pure GHCupURL
parseUrlSource "StackSetupURL" = pure StackSetupURL
parseUrlSource s' = (eitherDecode . LE.encodeUtf8 . LT.pack $ s')
<|> (fmap (OwnSource . (:[]) . Right) . first show . parseURI strictURIParserOptions .UTF8.fromString $ s')
parseNewUrlSource :: String -> Either String NewURLSource
parseNewUrlSource "GHCupURL" = pure NewGHCupURL
parseNewUrlSource "StackSetupURL" = pure NewStackSetupURL
parseNewUrlSource s' = (eitherDecode . LE.encodeUtf8 . LT.pack $ s')
<|> (fmap NewURI . first show . parseURI strictURIParserOptions .UTF8.fromString $ s')

View File

@@ -32,7 +32,6 @@ import Options.Applicative hiding ( style, ParseError )
import Options.Applicative.Help.Pretty ( text ) import Options.Applicative.Help.Pretty ( text )
import Prelude hiding ( appendFile ) import Prelude hiding ( appendFile )
import System.Exit import System.Exit
import URI.ByteString hiding ( uriParser )
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.ByteString.UTF8 as UTF8 import qualified Data.ByteString.UTF8 as UTF8
@@ -51,7 +50,7 @@ data ConfigCommand
= ShowConfig = ShowConfig
| SetConfig String (Maybe String) | SetConfig String (Maybe String)
| InitConfig | InitConfig
| AddReleaseChannel Bool URI | AddReleaseChannel Bool NewURLSource
deriving (Eq, Show) deriving (Eq, Show)
@@ -75,8 +74,8 @@ configP = subparser
showP = info (pure ShowConfig) (progDesc "Show current config (default)") showP = info (pure ShowConfig) (progDesc "Show current config (default)")
setP = info argsP (progDesc "Set config KEY to VALUE (or specify as single json value)" <> footerDoc (Just $ text configSetFooter)) setP = info argsP (progDesc "Set config KEY to VALUE (or specify as single json value)" <> footerDoc (Just $ text configSetFooter))
argsP = SetConfig <$> argument str (metavar "<JSON_VALUE | YAML_KEY>") <*> optional (argument str (metavar "YAML_VALUE")) argsP = SetConfig <$> argument str (metavar "<JSON_VALUE | YAML_KEY>") <*> optional (argument str (metavar "YAML_VALUE"))
addP = info (AddReleaseChannel <$> switch (long "force" <> help "Delete existing entry (if any) and append instead of failing") <*> argument (eitherReader uriParser) (metavar "URI" <> completer fileUri)) addP = info (AddReleaseChannel <$> switch (long "force" <> help "Delete existing entry (if any) and append instead of failing") <*> argument (eitherReader parseNewUrlSource) (metavar "URL_SOURCE" <> completer urlSourceCompleter))
(progDesc "Add a release channel from a URI") (progDesc "Add a release channel, e.g. from a URI")
@@ -207,27 +206,15 @@ config configCommand settings userConf keybindings runLogger = case configComman
pure $ ExitFailure 65 pure $ ExitFailure 65
VLeft _ -> pure $ ExitFailure 65 VLeft _ -> pure $ ExitFailure 65
AddReleaseChannel force uri -> do AddReleaseChannel force new -> do
r <- runE @'[DuplicateReleaseChannel] $ do r <- runE @'[DuplicateReleaseChannel] $ do
case urlSource settings of let oldSources = fromURLSource (urlSource settings)
AddSource xs -> do let merged = oldSources ++ [new]
case checkDuplicate xs (Right uri) of case checkDuplicate oldSources new of
Duplicate Duplicate
| not force -> throwE (DuplicateReleaseChannel uri) | not force -> throwE (DuplicateReleaseChannel new)
DuplicateLast -> pure () DuplicateLast -> pure ()
_ -> lift $ doConfig (defaultUserSettings { uUrlSource = Just $ AddSource (appendUnique xs (Right uri)) }) _ -> lift $ doConfig (defaultUserSettings { uUrlSource = Just $ SimpleList merged })
GHCupURL -> do
lift $ doConfig (defaultUserSettings { uUrlSource = Just $ AddSource [Right uri] })
pure ()
OwnSource xs -> do
case checkDuplicate xs (Right uri) of
Duplicate
| not force -> throwE (DuplicateReleaseChannel uri)
DuplicateLast -> pure ()
_ -> lift $ doConfig (defaultUserSettings { uUrlSource = Just $ OwnSource (appendUnique xs (Right uri)) })
OwnSpec spec -> do
lift $ doConfig (defaultUserSettings { uUrlSource = Just $ OwnSource [Left spec, Right uri] })
pure ()
case r of case r of
VRight _ -> do VRight _ -> do
pure ExitSuccess pure ExitSuccess
@@ -242,15 +229,6 @@ config configCommand settings userConf keybindings runLogger = case configComman
| a `elem` xs = Duplicate | a `elem` xs = Duplicate
| otherwise = NoDuplicate | otherwise = NoDuplicate
-- appends the element to the end of the list, but also removes it from the existing list
appendUnique :: Eq a => [a] -> a -> [a]
appendUnique xs' e = go xs'
where
go [] = [e]
go (x:xs)
| x == e = go xs -- skip
| otherwise = x : go xs
doConfig :: MonadIO m => UserSettings -> m () doConfig :: MonadIO m => UserSettings -> m ()
doConfig usersettings = do doConfig usersettings = do
let settings' = updateSettings usersettings userConf let settings' = updateSettings usersettings userConf

View File

@@ -63,7 +63,6 @@ data InstallCommand = InstallGHC InstallOptions
--[ Options ]-- --[ Options ]--
--------------- ---------------
data InstallOptions = InstallOptions data InstallOptions = InstallOptions
{ instVer :: Maybe ToolVersion { instVer :: Maybe ToolVersion
, instBindist :: Maybe URI , instBindist :: Maybe URI
@@ -134,7 +133,7 @@ installParser =
) )
) )
) )
<|> (Right <$> installOpts Nothing) <|> (Right <$> installOpts (Just GHC))
where where
installHLSFooter :: String installHLSFooter :: String
installHLSFooter = [s|Discussion: installHLSFooter = [s|Discussion:
@@ -291,6 +290,11 @@ type InstallGHCEffects = '[ AlreadyInstalled
, UninstallFailed , UninstallFailed
, UnknownArchive , UnknownArchive
, InstallSetError , InstallSetError
, NoCompatiblePlatform
, GHCup.Errors.ParseError
, UnsupportedSetupCombo
, DistroNotFound
, NoCompatibleArch
] ]
runInstGHC :: AppState runInstGHC :: AppState
@@ -310,13 +314,13 @@ runInstGHC appstate' =
install :: Either InstallCommand InstallOptions -> Settings -> IO AppState -> (ReaderT LeanAppState IO () -> IO ()) -> IO ExitCode install :: Either InstallCommand InstallOptions -> Settings -> IO AppState -> (ReaderT LeanAppState IO () -> IO ()) -> IO ExitCode
install installCommand settings getAppState' runLogger = case installCommand of install installCommand settings getAppState' runLogger = case installCommand of
(Right iopts) -> do (Right iGHCopts) -> do
runLogger (logWarn "This is an old-style command for installing GHC. Use 'ghcup install ghc' instead.") runLogger (logWarn "This is an old-style command for installing GHC. Use 'ghcup install ghc' instead.")
installGHC iopts installGHC iGHCopts
(Left (InstallGHC iopts)) -> installGHC iopts (Left (InstallGHC iGHCopts)) -> installGHC iGHCopts
(Left (InstallCabal iopts)) -> installCabal iopts (Left (InstallCabal iopts)) -> installCabal iopts
(Left (InstallHLS iopts)) -> installHLS iopts (Left (InstallHLS iopts)) -> installHLS iopts
(Left (InstallStack iopts)) -> installStack iopts (Left (InstallStack iopts)) -> installStack iopts
where where
installGHC :: InstallOptions -> IO ExitCode installGHC :: InstallOptions -> IO ExitCode
installGHC InstallOptions{..} = do installGHC InstallOptions{..} = do

View File

@@ -14,6 +14,7 @@ module GHCup.OptParse.Prefetch where
import GHCup import GHCup
import GHCup.Errors import GHCup.Errors
import GHCup.Types import GHCup.Types
import GHCup.Types.Optics
import GHCup.Prelude.File import GHCup.Prelude.File
import GHCup.Prelude.Logger import GHCup.Prelude.Logger
import GHCup.Prelude.String.QQ import GHCup.Prelude.String.QQ
@@ -157,7 +158,9 @@ type PrefetchEffects = '[ TagNotFound
, GPGError , GPGError
, DownloadFailed , DownloadFailed
, JSONError , JSONError
, FileDoesNotExistError ] , FileDoesNotExistError
, StackPlatformDetectError
]
runPrefetch :: MonadUnliftIO m runPrefetch :: MonadUnliftIO m
@@ -210,7 +213,8 @@ prefetch prefetchCommand runAppState runLogger =
(v, _) <- liftE $ fromVersion mt Stack (v, _) <- liftE $ fromVersion mt Stack
liftE $ fetchToolBindist (_tvVersion v) Stack pfCacheDir liftE $ fetchToolBindist (_tvVersion v) Stack pfCacheDir
PrefetchMetadata -> do PrefetchMetadata -> do
_ <- liftE getDownloadsF pfreq <- lift getPlatformReq
_ <- liftE $ getDownloadsF pfreq
pure "" pure ""
) >>= \case ) >>= \case
VRight _ -> do VRight _ -> do

View File

@@ -187,6 +187,11 @@ type RunEffects = '[ AlreadyInstalled
, ProcessError , ProcessError
, UninstallFailed , UninstallFailed
, MergeFileTreeError , MergeFileTreeError
, NoCompatiblePlatform
, GHCup.Errors.ParseError
, UnsupportedSetupCombo
, DistroNotFound
, NoCompatibleArch
] ]
runLeanRUN :: (MonadUnliftIO m, MonadIO m) runLeanRUN :: (MonadUnliftIO m, MonadIO m)
@@ -226,6 +231,7 @@ run :: forall m .
, MonadCatch m , MonadCatch m
, MonadIO m , MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
, Alternative m
) )
=> RunOptions => RunOptions
-> IO AppState -> IO AppState
@@ -255,7 +261,9 @@ run RunOptions{..} runAppState leanAppstate runLogger = do
liftIO $ putStr tmp liftIO $ putStr tmp
pure ExitSuccess pure ExitSuccess
(cmd:args) -> do (cmd:args) -> do
newEnv <- liftIO $ addToPath tmp runAppendPATH newEnv <- liftIO $ addToPath [tmp] runAppendPATH
let pathVar = if isWindows then "Path" else "PATH"
forM_ (Map.lookup pathVar . Map.fromList $ newEnv) $ liftIO . setEnv pathVar
#ifndef IS_WINDOWS #ifndef IS_WINDOWS
void $ liftIO $ SPP.executeFile cmd True args (Just newEnv) void $ liftIO $ SPP.executeFile cmd True args (Just newEnv)
pure ExitSuccess pure ExitSuccess
@@ -329,6 +337,7 @@ run RunOptions{..} runAppState leanAppstate runLogger = do
, MonadThrow m , MonadThrow m
, MonadIO m , MonadIO m
, MonadCatch m , MonadCatch m
, Alternative m
) )
=> Toolchain => Toolchain
-> FilePath -> FilePath
@@ -354,6 +363,11 @@ run RunOptions{..} runAppState leanAppstate runLogger = do
, CopyError , CopyError
, UninstallFailed , UninstallFailed
, MergeFileTreeError , MergeFileTreeError
, NoCompatiblePlatform
, GHCup.Errors.ParseError
, UnsupportedSetupCombo
, DistroNotFound
, NoCompatibleArch
] (ResourceT (ReaderT AppState m)) () ] (ResourceT (ReaderT AppState m)) ()
installToolChainFull Toolchain{..} tmp = do installToolChainFull Toolchain{..} tmp = do
case ghcVer of case ghcVer of

View File

@@ -5,7 +5,6 @@
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-| {-|
Module : GHCup.Download Module : GHCup.Download
Description : Downloading Description : Downloading
@@ -31,9 +30,11 @@ import GHCup.Download.Utils
#endif #endif
import GHCup.Errors import GHCup.Errors
import GHCup.Types import GHCup.Types
import qualified GHCup.Types.Stack as Stack
import GHCup.Types.Optics import GHCup.Types.Optics
import GHCup.Types.JSON ( ) import GHCup.Types.JSON ( )
import GHCup.Utils.Dirs import GHCup.Utils.Dirs
import GHCup.Platform
import GHCup.Prelude import GHCup.Prelude
import GHCup.Prelude.File import GHCup.Prelude.File
import GHCup.Prelude.Logger.Internal import GHCup.Prelude.Logger.Internal
@@ -55,6 +56,7 @@ import Data.ByteString ( ByteString )
import Data.CaseInsensitive ( mk ) import Data.CaseInsensitive ( mk )
#endif #endif
import Data.Maybe import Data.Maybe
import Data.Either
import Data.List import Data.List
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
@@ -112,24 +114,71 @@ getDownloadsF :: ( FromJSONKey Tool
, MonadFail m , MonadFail m
, MonadMask m , MonadMask m
) )
=> Excepts => PlatformRequest
'[DigestError, ContentLengthError, GPGError, JSONError , DownloadFailed , FileDoesNotExistError] -> Excepts
'[DigestError, ContentLengthError, GPGError, JSONError , DownloadFailed , FileDoesNotExistError, StackPlatformDetectError]
m m
GHCupInfo GHCupInfo
getDownloadsF = do getDownloadsF pfreq@(PlatformRequest arch plat _) = do
Settings { urlSource } <- lift getSettings Settings { urlSource } <- lift getSettings
case urlSource of let newUrlSources = fromURLSource urlSource
GHCupURL -> liftE $ getBase ghcupURL infos <- liftE $ mapM dl' newUrlSources
(OwnSource exts) -> do keys <- if any isRight infos
ext <- liftE $ mapM (either pure getBase) exts then liftE . reThrowAll @_ @_ @'[StackPlatformDetectError] StackPlatformDetectError $ getStackPlatformKey pfreq
mergeGhcupInfo ext else pure []
(OwnSpec av) -> pure av ghcupInfos <- fmap catMaybes $ forM infos $ \case
(AddSource exts) -> do Left gi -> pure (Just gi)
base <- liftE $ getBase ghcupURL Right si -> pure $ fromStackSetupInfo si keys
ext <- liftE $ mapM (either pure getBase) exts mergeGhcupInfo ghcupInfos
mergeGhcupInfo (base:ext)
where where
dl' :: ( FromJSONKey Tool
, FromJSONKey Version
, FromJSON VersionInfo
, MonadReader env m
, HasSettings env
, HasDirs env
, MonadIO m
, MonadCatch m
, HasLog env
, MonadThrow m
, MonadFail m
, MonadMask m
)
=> NewURLSource
-> Excepts
'[DownloadFailed, GPGError, DigestError, ContentLengthError, JSONError, FileDoesNotExistError]
m (Either GHCupInfo Stack.SetupInfo)
dl' NewGHCupURL = fmap Left $ liftE $ getBase @GHCupInfo ghcupURL
dl' NewStackSetupURL = fmap Right $ liftE $ getBase @Stack.SetupInfo stackSetupURL
dl' (NewGHCupInfo gi) = pure (Left gi)
dl' (NewSetupInfo si) = pure (Right si)
dl' (NewURI uri) = catchE @JSONError (\(JSONDecodeError _) -> Right <$> getBase @Stack.SetupInfo uri)
$ fmap Left $ getBase @GHCupInfo uri
fromStackSetupInfo :: MonadThrow m
=> Stack.SetupInfo
-> [String]
-> m GHCupInfo
fromStackSetupInfo (Stack.siGHCs -> ghcDli) keys = do
let ghcVersionsPerKey = (`M.lookup` ghcDli) <$> (T.pack <$> keys)
ghcVersions = fromMaybe mempty . listToMaybe . catMaybes $ ghcVersionsPerKey
(ghcupInfo' :: M.Map GHCTargetVersion DownloadInfo) <-
M.mapKeys mkTVer <$> M.traverseMaybeWithKey (\_ a -> pure $ fromStackDownloadInfo a) ghcVersions
let ghcupDownloads' = M.singleton GHC (M.map fromDownloadInfo ghcupInfo')
pure (GHCupInfo mempty ghcupDownloads' mempty)
where
fromDownloadInfo :: DownloadInfo -> VersionInfo
fromDownloadInfo dli = let aspec = M.singleton arch (M.singleton plat (M.singleton Nothing dli))
in VersionInfo [] Nothing Nothing Nothing Nothing aspec Nothing Nothing Nothing
fromStackDownloadInfo :: MonadThrow m => Stack.GHCDownloadInfo -> m DownloadInfo
fromStackDownloadInfo (Stack.GHCDownloadInfo { gdiDownloadInfo = Stack.DownloadInfo{..} }) = do
url <- either (\e -> throwM $ ParseError (show e)) pure $ parseURI strictURIParserOptions . E.encodeUtf8 $ downloadInfoUrl
sha256 <- maybe (throwM $ DigestMissing url) (pure . E.decodeUtf8) downloadInfoSha256
pure $ DownloadInfo url (Just $ RegexDir "ghc-.*") sha256 Nothing Nothing
mergeGhcupInfo :: MonadFail m mergeGhcupInfo :: MonadFail m
=> [GHCupInfo] => [GHCupInfo]
-> m GHCupInfo -> m GHCupInfo
@@ -141,6 +190,7 @@ getDownloadsF = do
in pure $ GHCupInfo newToolReqs newDownloads newGlobalTools in pure $ GHCupInfo newToolReqs newDownloads newGlobalTools
yamlFromCache :: (MonadReader env m, HasDirs env) => URI -> m FilePath yamlFromCache :: (MonadReader env m, HasDirs env) => URI -> m FilePath
yamlFromCache uri = do yamlFromCache uri = do
Dirs{..} <- getDirs Dirs{..} <- getDirs
@@ -151,7 +201,7 @@ etagsFile :: FilePath -> FilePath
etagsFile = (<.> "etags") etagsFile = (<.> "etags")
getBase :: ( MonadReader env m getBase :: forall j m env . ( MonadReader env m
, HasDirs env , HasDirs env
, HasSettings env , HasSettings env
, MonadFail m , MonadFail m
@@ -159,9 +209,10 @@ getBase :: ( MonadReader env m
, MonadCatch m , MonadCatch m
, HasLog env , HasLog env
, MonadMask m , MonadMask m
, FromJSON j
) )
=> URI => URI
-> Excepts '[DownloadFailed, GPGError, DigestError, ContentLengthError, JSONError, FileDoesNotExistError] m GHCupInfo -> Excepts '[DownloadFailed, GPGError, DigestError, ContentLengthError, JSONError, FileDoesNotExistError] m j
getBase uri = do getBase uri = do
Settings { noNetwork, downloader, metaMode } <- lift getSettings Settings { noNetwork, downloader, metaMode } <- lift getSettings
@@ -246,7 +297,7 @@ getBase uri = do
Settings { metaCache } <- lift getSettings Settings { metaCache } <- lift getSettings
-- for local files, let's short-circuit and ignore access time -- for local files, let's short-circuit and ignore access time
if | scheme == "file" -> liftE $ download uri' (Just $ over pathL' (<> ".sig") uri') Nothing Nothing (fromGHCupPath cacheDir) Nothing True if | scheme == "file" -> liftE $ download uri' Nothing Nothing Nothing (fromGHCupPath cacheDir) Nothing True
| e -> do | e -> do
accessTime <- fmap utcTimeToPOSIXSeconds $ liftIO $ getAccessTime json_file accessTime <- fmap utcTimeToPOSIXSeconds $ liftIO $ getAccessTime json_file
let sinceLastAccess = utcTimeToPOSIXSeconds currentTime - accessTime let sinceLastAccess = utcTimeToPOSIXSeconds currentTime - accessTime
@@ -326,6 +377,7 @@ getDownloadInfo' t v = do
) )
-- | Tries to download from the given http or https url -- | Tries to download from the given http or https url
-- and saves the result in continuous memory into a file. -- and saves the result in continuous memory into a file.
-- If the filename is not provided, then we: -- If the filename is not provided, then we:
@@ -352,20 +404,15 @@ download :: ( MonadReader env m
download rawUri gpgUri eDigest eCSize dest mfn etags download rawUri gpgUri eDigest eCSize dest mfn etags
| scheme == "https" = liftE dl | scheme == "https" = liftE dl
| scheme == "http" = liftE dl | scheme == "http" = liftE dl
| scheme == "file"
, Just s <- gpgScheme
, s /= "file" = throwIO $ userError $ "gpg scheme does not match base file scheme: " <> (T.unpack . decUTF8Safe $ s)
| scheme == "file" = do | scheme == "file" = do
Settings{ gpgSetting } <- lift getSettings
let destFile' = T.unpack . decUTF8Safe $ view pathL' rawUri let destFile' = T.unpack . decUTF8Safe $ view pathL' rawUri
lift $ logDebug $ "using local file: " <> T.pack destFile' lift $ logDebug $ "using local file: " <> T.pack destFile'
liftE $ verify gpgSetting destFile' (pure . T.unpack . decUTF8Safe . view pathL') forM_ eDigest (liftE . flip checkDigest destFile')
pure destFile' pure destFile'
| otherwise = throwE $ DownloadFailed (variantFromValue UnsupportedScheme) | otherwise = throwE $ DownloadFailed (variantFromValue UnsupportedScheme)
where where
scheme = view (uriSchemeL' % schemeBSL') rawUri scheme = view (uriSchemeL' % schemeBSL') rawUri
gpgScheme = view (uriSchemeL' % schemeBSL') <$> gpgUri
dl = do dl = do
Settings{ mirrors } <- lift getSettings Settings{ mirrors } <- lift getSettings
let uri = applyMirrors mirrors rawUri let uri = applyMirrors mirrors rawUri
@@ -407,14 +454,30 @@ download rawUri gpgUri eDigest eCSize dest mfn etags
else pure (\fp -> liftE . internalDL fp) else pure (\fp -> liftE . internalDL fp)
#endif #endif
liftE $ downloadAction baseDestFile uri liftE $ downloadAction baseDestFile uri
liftE $ verify gpgSetting baseDestFile case (gpgUri, gpgSetting) of
(\uri' -> do (_, GPGNone) -> pure ()
gpgDestFile <- liftE . reThrowAll @_ @_ @'[DownloadFailed] DownloadFailed $ getDestFile uri' Nothing (Just gpgUri', _) -> do
lift $ logDebug $ "downloading: " <> (decUTF8Safe . serializeURIRef') uri' <> " as file " <> T.pack gpgDestFile gpgDestFile <- liftE . reThrowAll @_ @_ @'[DownloadFailed] DownloadFailed $ getDestFile gpgUri' Nothing
flip onException (lift $ hideError doesNotExistErrorType $ recycleFile (tmpFile gpgDestFile)) $ liftE $ flip onException
downloadAction gpgDestFile uri' (lift $ hideError doesNotExistErrorType $ recycleFile (tmpFile gpgDestFile))
pure gpgDestFile $ catchAllE @_ @'[GPGError, ProcessError, UnsupportedScheme, DownloadFailed] @'[GPGError]
) (\e -> if gpgSetting == GPGStrict then throwE (GPGError e) else lift $ logWarn $ T.pack (prettyHFError (GPGError e))
) $ do
o' <- liftIO getGpgOpts
lift $ logDebug $ "downloading: " <> (decUTF8Safe . serializeURIRef') gpgUri' <> " as file " <> T.pack gpgDestFile
liftE $ downloadAction gpgDestFile gpgUri'
lift $ logInfo $ "verifying signature of: " <> T.pack baseDestFile
let args = o' ++ ["--batch", "--verify", "--quiet", "--no-tty", gpgDestFile, baseDestFile]
cp <- lift $ executeOut "gpg" args Nothing
case cp of
CapturedProcess { _exitCode = ExitFailure i, _stdErr } -> do
lift $ logDebug $ decUTF8Safe' _stdErr
throwE (GPGError @'[ProcessError] (V (NonZeroExit i "gpg" args)))
CapturedProcess { _stdErr } -> lift $ logDebug $ decUTF8Safe' _stdErr
_ -> pure ()
forM_ eCSize (liftE . flip checkCSize baseDestFile)
forM_ eDigest (liftE . flip checkDigest baseDestFile)
pure baseDestFile pure baseDestFile
curlDL :: ( MonadCatch m curlDL :: ( MonadCatch m
@@ -612,41 +675,6 @@ download rawUri gpgUri eDigest eCSize dest mfn etags
liftIO $ hideError doesNotExistErrorType $ rmFile (etagsFile fp) liftIO $ hideError doesNotExistErrorType $ rmFile (etagsFile fp)
pure Nothing pure Nothing
verify :: ( MonadReader env m
, HasLog env
, HasDirs env
, HasSettings env
, MonadCatch m
, MonadMask m
, MonadIO m
)
=> GPGSetting
-> FilePath
-> (URI -> Excepts '[ProcessError, DownloadFailed, UnsupportedScheme] m FilePath)
-> Excepts '[DigestError, ContentLengthError, DownloadFailed, GPGError] m ()
verify gpgSetting destFile' downloadAction' = do
case (gpgUri, gpgSetting) of
(_, GPGNone) -> pure ()
(Just gpgUri', _) -> do
liftE $ catchAllE @_ @'[GPGError, ProcessError, UnsupportedScheme, DownloadFailed] @'[GPGError]
(\e -> if gpgSetting == GPGStrict then throwE (GPGError e) else lift $ logWarn $ T.pack (prettyHFError (GPGError e))
) $ do
o' <- liftIO getGpgOpts
gpgDestFile <- liftE $ downloadAction' gpgUri'
lift $ logInfo $ "verifying signature of: " <> T.pack destFile'
let args = o' ++ ["--batch", "--verify", "--quiet", "--no-tty", gpgDestFile, destFile']
cp <- lift $ executeOut "gpg" args Nothing
case cp of
CapturedProcess { _exitCode = ExitFailure i, _stdErr } -> do
lift $ logDebug $ decUTF8Safe' _stdErr
throwE (GPGError @'[ProcessError] (V (NonZeroExit i "gpg" args)))
CapturedProcess { _stdErr } -> lift $ logDebug $ decUTF8Safe' _stdErr
_ -> pure ()
forM_ eCSize (liftE . flip checkCSize destFile')
forM_ eDigest (liftE . flip checkDigest destFile')
-- | Download into tmpdir or use cached version, if it exists. If filename -- | Download into tmpdir or use cached version, if it exists. If filename
-- is omitted, infers the filename from the url. -- is omitted, infers the filename from the url.
@@ -666,7 +694,7 @@ downloadCached :: ( MonadReader env m
downloadCached dli mfn = do downloadCached dli mfn = do
Settings{ cache } <- lift getSettings Settings{ cache } <- lift getSettings
case cache of case cache of
True -> liftE $ downloadCached' dli mfn Nothing True -> downloadCached' dli mfn Nothing
False -> do False -> do
tmp <- lift withGHCupTmpDir tmp <- lift withGHCupTmpDir
liftE $ download (_dlUri dli) Nothing (Just (_dlHash dli)) (_dlCSize dli) (fromGHCupPath tmp) outputFileName False liftE $ download (_dlUri dli) Nothing (Just (_dlHash dli)) (_dlCSize dli) (fromGHCupPath tmp) outputFileName False

View File

@@ -87,6 +87,7 @@ allHFError = unlines allErrors
, let proxy = Proxy :: Proxy ToolShadowed in format proxy , let proxy = Proxy :: Proxy ToolShadowed in format proxy
, let proxy = Proxy :: Proxy ContentLengthError in format proxy , let proxy = Proxy :: Proxy ContentLengthError in format proxy
, let proxy = Proxy :: Proxy DuplicateReleaseChannel in format proxy , let proxy = Proxy :: Proxy DuplicateReleaseChannel in format proxy
, let proxy = Proxy :: Proxy UnsupportedSetupCombo in format proxy
, "" , ""
, "# high level errors (4000+)" , "# high level errors (4000+)"
, let proxy = Proxy :: Proxy DownloadFailed in format proxy , let proxy = Proxy :: Proxy DownloadFailed in format proxy
@@ -99,6 +100,7 @@ allHFError = unlines allErrors
, let proxy = Proxy :: Proxy ParseError in format proxy , let proxy = Proxy :: Proxy ParseError in format proxy
, let proxy = Proxy :: Proxy UnexpectedListLength in format proxy , let proxy = Proxy :: Proxy UnexpectedListLength in format proxy
, let proxy = Proxy :: Proxy NoUrlBase in format proxy , let proxy = Proxy :: Proxy NoUrlBase in format proxy
, let proxy = Proxy :: Proxy DigestMissing in format proxy
, "" , ""
, "# orphans (800+)" , "# orphans (800+)"
, let proxy = Proxy :: Proxy URIParseError in format proxy , let proxy = Proxy :: Proxy URIParseError in format proxy
@@ -674,18 +676,29 @@ instance HFErrorProject ContentLengthError where
eBase _ = 340 eBase _ = 340
eDesc _ = "File content length verification failed" eDesc _ = "File content length verification failed"
data DuplicateReleaseChannel = DuplicateReleaseChannel URI data DuplicateReleaseChannel = DuplicateReleaseChannel NewURLSource
deriving Show deriving Show
instance HFErrorProject DuplicateReleaseChannel where instance HFErrorProject DuplicateReleaseChannel where
eBase _ = 350 eBase _ = 350
eDesc _ = "Duplicate release channel detected when adding URI.\nGiving up. You can use '--force' to remove and append the duplicate URI (this may change order/semantics)." eDesc _ = "Duplicate release channel detected when adding new source.\nGiving up. You can use '--force' to remove and append the duplicate source (this may change order/semantics)."
instance Pretty DuplicateReleaseChannel where instance Pretty DuplicateReleaseChannel where
pPrint (DuplicateReleaseChannel uri) = pPrint (DuplicateReleaseChannel source) =
text $ "Duplicate release channel detected when adding: \n " text $ "Duplicate release channel detected when adding: \n "
<> (T.unpack . E.decodeUtf8With E.lenientDecode . serializeURIRef') uri <> show source
<> "\nGiving up. You can use '--force' to remove and append the duplicate URI (this may change order/semantics)." <> "\nGiving up. You can use '--force' to remove and append the duplicate source (this may change order/semantics)."
data UnsupportedSetupCombo = UnsupportedSetupCombo Architecture Platform
deriving Show
instance Pretty UnsupportedSetupCombo where
pPrint (UnsupportedSetupCombo arch plat) =
text "Could not find a compatible setup combo for:" <+> pPrint arch <+> pPrint plat
instance HFErrorProject UnsupportedSetupCombo where
eBase _ = 360
eDesc _ = "Could not find a compatible setup combo"
------------------------- -------------------------
--[ High-level errors ]-- --[ High-level errors ]--
@@ -774,6 +787,22 @@ instance HFErrorProject GHCupSetError where
eNum (GHCupSetError xs) = 9000 + eNum xs eNum (GHCupSetError xs) = 9000 + eNum xs
eDesc _ = "Setting the current version failed." eDesc _ = "Setting the current version failed."
-- | Executing stacks platform detection failed.
data StackPlatformDetectError = forall es . (ToVariantMaybe StackPlatformDetectError es, PopVariant StackPlatformDetectError es, Show (V es), Pretty (V es), HFErrorProject (V es)) => StackPlatformDetectError (V es)
instance Pretty StackPlatformDetectError where
pPrint (StackPlatformDetectError reason) =
case reason of
VMaybe (_ :: StackPlatformDetectError) -> pPrint reason
_ -> text "Running stack platform detection logic failed:" <+> pPrint reason
deriving instance Show StackPlatformDetectError
instance HFErrorProject StackPlatformDetectError where
eBase _ = 6000
eNum (StackPlatformDetectError xs) = 6000 + eNum xs
eDesc _ = "Running stack platform detection logic failed."
--------------------------------------------- ---------------------------------------------
--[ True Exceptions (e.g. for MonadThrow) ]-- --[ True Exceptions (e.g. for MonadThrow) ]--
@@ -821,6 +850,18 @@ instance HFErrorProject NoUrlBase where
eBase _ = 520 eBase _ = 520
eDesc _ = "URL does not have a base filename." eDesc _ = "URL does not have a base filename."
data DigestMissing = DigestMissing URI
deriving Show
instance Pretty DigestMissing where
pPrint (DigestMissing uri) =
text "Digest missing for:" <+> (text . T.unpack . E.decodeUtf8With E.lenientDecode . serializeURIRef') uri
instance Exception DigestMissing
instance HFErrorProject DigestMissing where
eBase _ = 530
eDesc _ = "An expected digest is missing."
------------------------ ------------------------

View File

@@ -74,6 +74,7 @@ import qualified Crypto.Hash.SHA256 as SHA256
import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Map.Strict as Map
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.IO as T import qualified Data.Text.IO as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
@@ -216,7 +217,9 @@ testUnpackedGHC path tver addMakeArgs = do
lift $ logInfo $ "Testing GHC version " <> tVerToText tver <> "!" lift $ logInfo $ "Testing GHC version " <> tVerToText tver <> "!"
ghcDir <- lift $ ghcupGHCDir tver ghcDir <- lift $ ghcupGHCDir tver
let ghcBinDir = fromGHCupPath ghcDir </> "bin" let ghcBinDir = fromGHCupPath ghcDir </> "bin"
env <- liftIO $ addToPath ghcBinDir False env <- liftIO $ addToPath [ghcBinDir] False
let pathVar = if isWindows then "Path" else "PATH"
forM_ (Map.lookup pathVar . Map.fromList $ env) $ liftIO . setEnv pathVar
lEM $ make' (fmap T.unpack addMakeArgs) lEM $ make' (fmap T.unpack addMakeArgs)
(Just $ fromGHCupPath path) (Just $ fromGHCupPath path)
@@ -512,6 +515,7 @@ installGHCBin :: ( MonadFail m
, MonadResource m , MonadResource m
, MonadIO m , MonadIO m
, MonadUnliftIO m , MonadUnliftIO m
, Alternative m
) )
=> GHCTargetVersion -- ^ the version to install => GHCTargetVersion -- ^ the version to install
-> InstallDir -> InstallDir
@@ -533,6 +537,11 @@ installGHCBin :: ( MonadFail m
, ProcessError , ProcessError
, UninstallFailed , UninstallFailed
, MergeFileTreeError , MergeFileTreeError
, NoCompatiblePlatform
, ParseError
, UnsupportedSetupCombo
, DistroNotFound
, NoCompatibleArch
] ]
m m
() ()

View File

@@ -28,6 +28,8 @@ import GHCup.Prelude
import GHCup.Prelude.Logger import GHCup.Prelude.Logger
import GHCup.Prelude.Process import GHCup.Prelude.Process
import GHCup.Prelude.String.QQ import GHCup.Prelude.String.QQ
import GHCup.Prelude.Version.QQ
import GHCup.Prelude.MegaParsec
#if !MIN_VERSION_base(4,13,0) #if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail ) import Control.Monad.Fail ( MonadFail )
@@ -48,11 +50,18 @@ import Prelude hiding ( abs
) )
import System.Info import System.Info
import System.OsRelease import System.OsRelease
import System.Exit
import System.FilePath
import Text.PrettyPrint.HughesPJClass ( prettyShow ) import Text.PrettyPrint.HughesPJClass ( prettyShow )
import Text.Regex.Posix import Text.Regex.Posix
import qualified Text.Megaparsec as MP
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.IO as T import qualified Data.Text.IO as T
import Data.Void
import qualified Data.List as L
@@ -197,3 +206,155 @@ getLinuxDistro = do
try_debian_version = do try_debian_version = do
ver <- T.readFile debian_version ver <- T.readFile debian_version
pure (T.pack "debian", Just ver) pure (T.pack "debian", Just ver)
getStackGhcBuilds :: (MonadReader env m, HasLog env, MonadIO m)
=> PlatformResult
-> Excepts '[ParseError, NoCompatiblePlatform, DistroNotFound, ProcessError] m [String]
getStackGhcBuilds PlatformResult{..} = do
case _platform of
Linux _ -> do
-- Some systems don't have ldconfig in the PATH, so make sure to look in
-- /sbin and /usr/sbin as well
sbinEnv <- liftIO $ addToPath sbinDirs False
ldConfig <- lift $ executeOut' "ldconfig" ["-p"] Nothing (Just sbinEnv)
firstWords <- case ldConfig of
CapturedProcess ExitSuccess so _ ->
pure . mapMaybe (listToMaybe . T.words) . T.lines . T.pack . stripNewlineEnd . T.unpack . decUTF8Safe' $ so
CapturedProcess (ExitFailure _) _ _ ->
-- throwE $ NonZeroExit c "ldconfig" ["-p" ]
pure []
let checkLib :: (MonadReader env m, HasLog env, MonadIO m) => String -> m Bool
checkLib lib
| libT `elem` firstWords = do
logDebug $ "Found shared library " <> libT <> " in 'ldconfig -p' output"
pure True
| isWindows =
-- Cannot parse /usr/lib on Windows
pure False
| otherwise = hasMatches lib usrLibDirs
-- This is a workaround for the fact that libtinfo.so.x doesn't
-- appear in the 'ldconfig -p' output on Arch or Slackware even
-- when it exists. There doesn't seem to be an easy way to get the
-- true list of directories to scan for shared libs, but this
-- works for our particular cases.
where
libT = T.pack lib
hasMatches :: (MonadReader env m, HasLog env, MonadIO m) => String -> [FilePath] -> m Bool
hasMatches lib dirs = do
matches <- filterM (liftIO . doesFileExist . (</> lib)) dirs
case matches of
[] -> logDebug ("Did not find shared library " <> libT) >> pure False
(path:_) -> logDebug ("Found shared library " <> libT <> " in " <> T.pack path) >> pure True
where
libT = T.pack lib
getLibc6Version :: MonadIO m
=> Excepts '[ParseError, ProcessError] m Version
getLibc6Version = do
CapturedProcess{..} <- lift $ executeOut "ldd" ["--version"] Nothing
case _exitCode of
ExitSuccess -> either (throwE . ParseError . show) pure
. MP.parse lddVersion "" . T.pack . stripNewlineEnd . T.unpack . decUTF8Safe' $ _stdOut
ExitFailure c -> throwE $ NonZeroExit c "ldd" ["--version" ]
-- Assumes the first line of ldd has the format:
--
-- ldd (...) nn.nn
--
-- where nn.nn corresponds to the version of libc6.
lddVersion :: MP.Parsec Void Text Version
lddVersion = do
skipWhile (/= ')')
skip (== ')')
skipSpace
version'
hasMusl <- hasMatches relFileLibcMuslx86_64So1 libDirs
mLibc6Version <- veitherToEither <$> runE getLibc6Version
case mLibc6Version of
Right libc6Version -> logDebug $ "Found shared library libc6 in version: " <> prettyVer libc6Version
Left _ -> logDebug "Did not find a version of shared library libc6."
let hasLibc6_2_32 = either (const False) (>= [vver|2.32|]) mLibc6Version
hastinfo5 <- checkLib relFileLibtinfoSo5
hastinfo6 <- checkLib relFileLibtinfoSo6
hasncurses6 <- checkLib relFileLibncurseswSo6
hasgmp5 <- checkLib relFileLibgmpSo10
hasgmp4 <- checkLib relFileLibgmpSo3
let libComponents = if hasMusl
then
[ ["musl"] ]
else
concat
[ if hastinfo6 && hasgmp5
then
if hasLibc6_2_32
then [["tinfo6"]]
else [["tinfo6-libc6-pre232"]]
else [[]]
, [ [] | hastinfo5 && hasgmp5 ]
, [ ["ncurses6"] | hasncurses6 && hasgmp5 ]
, [ ["gmp4"] | hasgmp4 ]
]
pure $ map
(\c -> case c of
[] -> []
_ -> L.intercalate "-" c)
libComponents
FreeBSD ->
case _distroVersion of
Just fVer
| fVer >= [vers|12|] -> pure []
_ -> pure ["ino64"]
Darwin -> pure []
Windows -> pure []
where
relFileLibcMuslx86_64So1 :: FilePath
relFileLibcMuslx86_64So1 = "libc.musl-x86_64.so.1"
libDirs :: [FilePath]
libDirs = ["/lib", "/lib64"]
usrLibDirs :: [FilePath]
usrLibDirs = ["/usr/lib", "/usr/lib64"]
sbinDirs :: [FilePath]
sbinDirs = ["/sbin", "/usr/sbin"]
relFileLibtinfoSo5 :: FilePath
relFileLibtinfoSo5 = "libtinfo.so.5"
relFileLibtinfoSo6 :: FilePath
relFileLibtinfoSo6 = "libtinfo.so.6"
relFileLibncurseswSo6 :: FilePath
relFileLibncurseswSo6 = "libncursesw.so.6"
relFileLibgmpSo10 :: FilePath
relFileLibgmpSo10 = "libgmp.so.10"
relFileLibgmpSo3 :: FilePath
relFileLibgmpSo3 = "libgmp.so.3"
getStackOSKey :: Monad m => PlatformRequest -> Excepts '[UnsupportedSetupCombo] m String
getStackOSKey PlatformRequest { .. } =
case (_rArch, _rPlatform) of
(A_32 , Linux _) -> pure "linux32"
(A_64 , Linux _) -> pure "linux64"
(A_32 , Darwin ) -> pure "macosx"
(A_64 , Darwin ) -> pure "macosx"
(A_32 , FreeBSD) -> pure "freebsd32"
(A_64 , FreeBSD) -> pure "freebsd64"
(A_32 , Windows) -> pure "windows32"
(A_64 , Windows) -> pure "windows64"
(A_ARM , Linux _) -> pure "linux-armv7"
(A_ARM64, Linux _) -> pure "linux-aarch64"
(A_Sparc, Linux _) -> pure "linux-sparc"
(A_ARM64, Darwin ) -> pure "macosx-aarch64"
(A_ARM64, FreeBSD) -> pure "freebsd-aarch64"
(arch', os') -> throwE $ UnsupportedSetupCombo arch' os'
getStackPlatformKey :: (MonadReader env m, MonadFail m, HasLog env, MonadCatch m, MonadIO m)
=> PlatformRequest
-> Excepts '[UnsupportedSetupCombo, ParseError, NoCompatiblePlatform, NoCompatibleArch, DistroNotFound, ProcessError] m [String]
getStackPlatformKey pfreq@PlatformRequest{..} = do
osKey <- liftE $ getStackOSKey pfreq
builds <- liftE $ getStackGhcBuilds (PlatformResult _rPlatform _rVersion)
let builds' = (\build -> if null build then osKey else osKey <> "-" <> build) <$> builds
logDebug $ "Potential GHC builds: " <> mconcat (L.intersperse ", " $ fmap T.pack builds')
pure builds'

View File

@@ -43,6 +43,10 @@ import Control.Monad.Reader
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Text.PrettyPrint.HughesPJClass ( Pretty ) import Text.PrettyPrint.HughesPJClass ( Pretty )
import qualified Data.Text as T import qualified Data.Text as T
import System.Environment (getEnvironment)
import qualified Data.Map.Strict as Map
import System.FilePath
import Data.List (intercalate)
@@ -88,3 +92,25 @@ throwSomeE :: forall es' es a m. (Monad m, LiftVariant es' es) => V es' -> Excep
{-# INLINABLE throwSomeE #-} {-# INLINABLE throwSomeE #-}
throwSomeE = Excepts . pure . VLeft . liftVariant throwSomeE = Excepts . pure . VLeft . liftVariant
#endif #endif
addToPath :: [FilePath]
-> Bool -- ^ if False will prepend
-> IO [(String, String)]
addToPath paths append = do
cEnv <- getEnvironment
return $ addToPath' cEnv paths append
addToPath' :: [(String, String)]
-> [FilePath]
-> Bool -- ^ if False will prepend
-> [(String, String)]
addToPath' cEnv' newPaths append =
let cEnv = Map.fromList cEnv'
paths = ["PATH", "Path"]
curPaths = (\x -> maybe [] splitSearchPath (Map.lookup x cEnv)) =<< paths
{- HLINT ignore "Redundant bracket" -}
newPath = intercalate [searchPathSeparator] (if append then (curPaths ++ newPaths) else (newPaths ++ curPaths))
envWithoutPath = foldr (\x y -> Map.delete x y) cEnv paths
pathVar = if isWindows then "Path" else "PATH"
envWithNewPath = Map.toList $ Map.insert pathVar newPath envWithoutPath
in envWithNewPath

View File

@@ -120,3 +120,17 @@ verP suffix = do
pathSep :: MP.Parsec Void Text Char pathSep :: MP.Parsec Void Text Char
pathSep = MP.oneOf pathSeparators pathSep = MP.oneOf pathSeparators
skipWhile :: (Char -> Bool) -> MP.Parsec Void Text ()
skipWhile f = void $ MP.takeWhileP Nothing f
skip :: (Char -> Bool) -> MP.Parsec Void Text ()
skip f = void $ MP.satisfy f
skipSpace :: MP.Parsec Void Text ()
skipSpace = void $ MP.satisfy isSpace
isSpace :: Char -> Bool
isSpace c = (c == ' ') || ('\t' <= c && c <= '\r')
{-# INLINE isSpace #-}

View File

@@ -11,6 +11,7 @@ Portability : portable
-} -}
module GHCup.Prelude.Process ( module GHCup.Prelude.Process (
executeOut, executeOut,
executeOut',
execLogged, execLogged,
exec, exec,
toProcessError, toProcessError,

View File

@@ -70,6 +70,16 @@ 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
executeOut' :: MonadIO m
=> FilePath -- ^ command as filename, e.g. 'ls'
-> [String] -- ^ arguments to the command
-> Maybe FilePath -- ^ chdir to this path
-> Maybe [(String, String)]
-> m CapturedProcess
executeOut' path args chdir env = liftIO $ captureOutStreams $ do
maybe (pure ()) changeWorkingDirectory chdir
SPP.executeFile path True args env
execLogged :: ( MonadReader env m execLogged :: ( MonadReader env m
, HasSettings env , HasSettings env
@@ -169,7 +179,7 @@ execLogged exe args chdir lfile env = do
overwriteNthLine n str = pos1 <> moveLineUp n <> clearLine <> str <> moveLineDown n <> pos1 overwriteNthLine n str = pos1 <> moveLineUp n <> clearLine <> str <> moveLineDown n <> pos1
blue :: ByteString -> ByteString blue :: ByteString -> ByteString
blue bs blue bs
| no_color = bs | no_color = bs
| otherwise = "\x1b[0;34m" <> bs <> "\x1b[0m" | otherwise = "\x1b[0;34m" <> bs <> "\x1b[0m"

View File

@@ -140,8 +140,16 @@ executeOut :: MonadIO m
-> [String] -- ^ arguments to the command -> [String] -- ^ arguments to the command
-> Maybe FilePath -- ^ chdir to this path -> Maybe FilePath -- ^ chdir to this path
-> m CapturedProcess -> m CapturedProcess
executeOut path args chdir = do executeOut path args chdir = executeOut' path args chdir Nothing
cp <- createProcessWithMingwPath ((proc path args){ cwd = chdir })
executeOut' :: MonadIO m
=> FilePath -- ^ command as filename, e.g. 'ls'
-> [String] -- ^ arguments to the command
-> Maybe FilePath -- ^ chdir to this path
-> Maybe [(String, String)]
-> m CapturedProcess
executeOut' path args chdir env' = do
cp <- createProcessWithMingwPath ((proc path args){ cwd = chdir, env = env' })
(exit, out, err) <- liftIO $ readCreateProcessWithExitCodeBS cp "" (exit, out, err) <- liftIO $ readCreateProcessWithExitCodeBS cp ""
pure $ CapturedProcess exit out err pure $ CapturedProcess exit out err

View File

@@ -22,10 +22,12 @@ module GHCup.Types
( module GHCup.Types ( module GHCup.Types
#if defined(BRICK) #if defined(BRICK)
, Key(..) , Key(..)
, Modifier(..)
#endif #endif
) )
where where
import GHCup.Types.Stack ( SetupInfo )
import {-# SOURCE #-} GHCup.Utils.Dirs ( fromGHCupPath, GHCupPath ) import {-# SOURCE #-} GHCup.Utils.Dirs ( fromGHCupPath, GHCupPath )
import Control.DeepSeq ( NFData, rnf ) import Control.DeepSeq ( NFData, rnf )
@@ -39,14 +41,13 @@ import Optics ( makeLenses )
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(..), Modifier(..) )
#endif #endif
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T import qualified Data.Text as T
import qualified GHC.Generics as GHC import qualified GHC.Generics as GHC
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import Data.Foldable (foldMap)
#if !defined(BRICK) #if !defined(BRICK)
data Key = KEsc | KChar Char | KBS | KEnter data Key = KEsc | KChar Char | KBS | KEnter
@@ -55,8 +56,15 @@ data Key = KEsc | KChar Char | KBS | KEnter
| KFun Int | KBackTab | KPrtScr | KPause | KIns | KFun Int | KBackTab | KPrtScr | KPause | KIns
| KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu | KHome | KPageUp | KDel | KEnd | KPageDown | KBegin | KMenu
deriving (Eq,Show,Read,Ord,GHC.Generic) deriving (Eq,Show,Read,Ord,GHC.Generic)
data Modifier = MShift | MCtrl | MMeta | MAlt
deriving (Eq,Show,Read,Ord,GHC.Generic)
#endif #endif
data KeyCombination = KeyCombination { key :: Key, mods :: [Modifier] }
deriving (Eq,Show,Read,Ord,GHC.Generic)
-------------------- --------------------
--[ GHCInfo Tree ]-- --[ GHCInfo Tree ]--
@@ -193,7 +201,7 @@ instance Pretty Tag where
pPrint (Base pvp'') = text ("base-" ++ T.unpack (prettyPVP pvp'')) pPrint (Base pvp'') = text ("base-" ++ T.unpack (prettyPVP pvp''))
pPrint (UnknownTag t ) = text t pPrint (UnknownTag t ) = text t
pPrint LatestPrerelease = text "latest-prerelease" pPrint LatestPrerelease = text "latest-prerelease"
pPrint LatestNightly = text "latest-prerelease" pPrint LatestNightly = text "latest-prerelease"
pPrint Old = mempty pPrint Old = mempty
data Architecture = A_64 data Architecture = A_64
@@ -334,15 +342,41 @@ instance Pretty TarDir where
-- | Where to fetch GHCupDownloads from. -- | Where to fetch GHCupDownloads from.
data URLSource = GHCupURL data URLSource = GHCupURL
| OwnSource [Either GHCupInfo URI] -- ^ complete source list | StackSetupURL
| OwnSpec GHCupInfo | OwnSource [Either (Either GHCupInfo SetupInfo) URI] -- ^ complete source list
| AddSource [Either GHCupInfo URI] -- ^ merge with GHCupURL | OwnSpec (Either GHCupInfo SetupInfo)
deriving (GHC.Generic, Show) | AddSource [Either (Either GHCupInfo SetupInfo) URI] -- ^ merge with GHCupURL
| SimpleList [NewURLSource]
deriving (Eq, GHC.Generic, Show)
data NewURLSource = NewGHCupURL
| NewStackSetupURL
| NewGHCupInfo GHCupInfo
| NewSetupInfo SetupInfo
| NewURI URI
deriving (Eq, GHC.Generic, Show)
instance NFData NewURLSource
fromURLSource :: URLSource -> [NewURLSource]
fromURLSource GHCupURL = [NewGHCupURL]
fromURLSource StackSetupURL = [NewStackSetupURL]
fromURLSource (OwnSource arr) = convert' <$> arr
fromURLSource (AddSource arr) = NewGHCupURL:(convert' <$> arr)
fromURLSource (SimpleList arr) = arr
fromURLSource (OwnSpec (Left gi)) = [NewGHCupInfo gi]
fromURLSource (OwnSpec (Right si)) = [NewSetupInfo si]
convert' :: Either (Either GHCupInfo SetupInfo) URI -> NewURLSource
convert' (Left (Left gi)) = NewGHCupInfo gi
convert' (Left (Right si)) = NewSetupInfo si
convert' (Right uri) = NewURI uri
instance NFData URLSource instance NFData URLSource
instance NFData (URIRef Absolute) where instance NFData (URIRef Absolute) where
rnf (URI !_ !_ !_ !_ !_) = () rnf (URI !_ !_ !_ !_ !_) = ()
data MetaMode = Strict data MetaMode = Strict
| Lax | Lax
deriving (Show, Read, Eq, GHC.Generic) deriving (Show, Read, Eq, GHC.Generic)
@@ -415,47 +449,51 @@ fromSettings Settings{..} (Just KeyBindings{..}) =
} }
data UserKeyBindings = UserKeyBindings data UserKeyBindings = UserKeyBindings
{ kUp :: Maybe Key { kUp :: Maybe KeyCombination
, kDown :: Maybe Key , kDown :: Maybe KeyCombination
, kQuit :: Maybe Key , kQuit :: Maybe KeyCombination
, kInstall :: Maybe Key , kInstall :: Maybe KeyCombination
, kUninstall :: Maybe Key , kUninstall :: Maybe KeyCombination
, kSet :: Maybe Key , kSet :: Maybe KeyCombination
, kChangelog :: Maybe Key , kChangelog :: Maybe KeyCombination
, kShowAll :: Maybe Key , kShowAll :: Maybe KeyCombination
, kShowAllTools :: Maybe Key , kShowAllTools :: Maybe KeyCombination
} }
deriving (Show, GHC.Generic) deriving (Show, GHC.Generic)
data KeyBindings = KeyBindings data KeyBindings = KeyBindings
{ bUp :: Key { bUp :: KeyCombination
, bDown :: Key , bDown :: KeyCombination
, bQuit :: Key , bQuit :: KeyCombination
, bInstall :: Key , bInstall :: KeyCombination
, bUninstall :: Key , bUninstall :: KeyCombination
, bSet :: Key , bSet :: KeyCombination
, bChangelog :: Key , bChangelog :: KeyCombination
, bShowAllVersions :: Key , bShowAllVersions :: KeyCombination
, bShowAllTools :: Key , bShowAllTools :: KeyCombination
} }
deriving (Show, GHC.Generic) deriving (Show, GHC.Generic)
instance NFData KeyBindings instance NFData KeyBindings
#if defined(IS_WINDOWS) || !defined(BRICK) #if defined(IS_WINDOWS) || !defined(BRICK)
instance NFData Key instance NFData Key
instance NFData Modifier
#endif #endif
instance NFData KeyCombination
defaultKeyBindings :: KeyBindings defaultKeyBindings :: KeyBindings
defaultKeyBindings = KeyBindings defaultKeyBindings = KeyBindings
{ bUp = KUp { bUp = KeyCombination { key = KUp , mods = [] }
, bDown = KDown , bDown = KeyCombination { key = KDown , mods = [] }
, bQuit = KChar 'q' , bQuit = KeyCombination { key = KChar 'q', mods = [] }
, bInstall = KChar 'i' , bInstall = KeyCombination { key = KChar 'i', mods = [] }
, bUninstall = KChar 'u' , bUninstall = KeyCombination { key = KChar 'u', mods = [] }
, bSet = KChar 's' , bSet = KeyCombination { key = KChar 's', mods = [] }
, bChangelog = KChar 'c' , bChangelog = KeyCombination { key = KChar 'c', mods = [] }
, bShowAllVersions = KChar 'a' , bShowAllVersions = KeyCombination { key = KChar 'a', mods = [] }
, bShowAllTools = KChar 't' , bShowAllTools = KeyCombination { key = KChar 't', mods = [] }
} }
data AppState = AppState data AppState = AppState
@@ -749,3 +787,4 @@ instance Pretty ToolVersion where
data BuildSystem = Hadrian data BuildSystem = Hadrian
| Make | Make
deriving (Show, Eq) deriving (Show, Eq)

View File

@@ -22,7 +22,9 @@ Portability : portable
module GHCup.Types.JSON where module GHCup.Types.JSON where
import GHCup.Types import GHCup.Types
import GHCup.Types.Stack (SetupInfo)
import GHCup.Types.JSON.Utils import GHCup.Types.JSON.Utils
import GHCup.Types.JSON.Versions ()
import GHCup.Prelude.MegaParsec import GHCup.Prelude.MegaParsec
import Control.Applicative ( (<|>) ) import Control.Applicative ( (<|>) )
@@ -31,7 +33,9 @@ import Data.Aeson.TH
import Data.Aeson.Types hiding (Key) import Data.Aeson.Types hiding (Key)
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
import Data.List.NonEmpty ( NonEmpty(..) ) import Data.List.NonEmpty ( NonEmpty(..) )
import Data.Maybe
import Data.Text.Encoding as E import Data.Text.Encoding as E
import Data.Foldable
import Data.Versions import Data.Versions
import Data.Void import Data.Void
import URI.ByteString import URI.ByteString
@@ -112,34 +116,6 @@ instance FromJSONKey GHCTargetVersion where
Right x -> pure x Right x -> pure x
Left e -> fail $ "Failure in GHCTargetVersion (FromJSONKey)" <> show e Left e -> fail $ "Failure in GHCTargetVersion (FromJSONKey)" <> show e
instance ToJSON Versioning where
toJSON = toJSON . prettyV
instance FromJSON Versioning where
parseJSON = withText "Versioning" $ \t -> case versioning t of
Right x -> pure x
Left e -> fail $ "Failure in GHCTargetVersion (FromJSON)" <> show e
instance ToJSONKey Versioning where
toJSONKey = toJSONKeyText $ \x -> prettyV x
instance FromJSONKey Versioning where
fromJSONKey = FromJSONKeyTextParser $ \t -> case versioning t of
Right x -> pure x
Left e -> fail $ "Failure in Versioning (FromJSONKey)" <> show e
instance ToJSONKey (Maybe Versioning) where
toJSONKey = toJSONKeyText $ \case
Just x -> prettyV x
Nothing -> T.pack "unknown_versioning"
instance FromJSONKey (Maybe Versioning) where
fromJSONKey = FromJSONKeyTextParser $ \t ->
if t == T.pack "unknown_versioning" then pure Nothing else just t
where
just t = case versioning t of
Right x -> pure $ Just x
Left e -> fail $ "Failure in (Maybe Versioning) (FromJSONKey)" <> show e
instance ToJSONKey Platform where instance ToJSONKey Platform where
toJSONKey = toJSONKeyText $ \case toJSONKey = toJSONKeyText $ \case
@@ -176,43 +152,6 @@ instance ToJSONKey Architecture where
instance FromJSONKey Architecture where instance FromJSONKey Architecture where
fromJSONKey = genericFromJSONKey defaultJSONKeyOptions fromJSONKey = genericFromJSONKey defaultJSONKeyOptions
instance ToJSONKey (Maybe Version) where
toJSONKey = toJSONKeyText $ \case
Just x -> prettyVer x
Nothing -> T.pack "unknown_version"
instance FromJSONKey (Maybe Version) where
fromJSONKey = FromJSONKeyTextParser $ \t ->
if t == T.pack "unknown_version" then pure Nothing else just t
where
just t = case version t of
Right x -> pure $ Just x
Left e -> fail $ "Failure in (Maybe Version) (FromJSONKey)" <> show e
instance ToJSON Version where
toJSON = toJSON . prettyVer
instance FromJSON Version where
parseJSON = withText "Version" $ \t -> case version t of
Right x -> pure x
Left e -> fail $ "Failure in Version (FromJSON)" <> show e
instance ToJSONKey Version where
toJSONKey = toJSONKeyText $ \x -> prettyVer x
instance FromJSONKey Version where
fromJSONKey = FromJSONKeyTextParser $ \t -> case version t of
Right x -> pure x
Left e -> fail $ "Failure in Version (FromJSONKey)" <> show e
instance ToJSON PVP where
toJSON = toJSON . prettyPVP
instance FromJSON PVP where
parseJSON = withText "PVP" $ \t -> case pvp t of
Right x -> pure x
Left e -> fail $ "Failure in PVP (FromJSON)" <> show e
instance ToJSONKey Tool where instance ToJSONKey Tool where
toJSONKey = genericToJSONKey defaultJSONKeyOptions toJSONKey = genericToJSONKey defaultJSONKeyOptions
@@ -342,33 +281,64 @@ instance FromJSONKey (Maybe VersionRange) where
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 } ''Requirements
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadInfo deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadInfo
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VersionInfo deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''VersionInfo
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GHCupInfo
deriveToJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''URLSource instance FromJSON GHCupInfo where
parseJSON = withObject "GHCupInfo" $ \o -> do
toolRequirements' <- o .:? "toolRequirements"
globalTools' <- o .:? "globalTools"
ghcupDownloads' <- o .: "ghcupDownloads"
pure (GHCupInfo (fromMaybe mempty toolRequirements') ghcupDownloads' (fromMaybe mempty globalTools'))
deriveToJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''GHCupInfo
instance ToJSON NewURLSource where
toJSON NewGHCupURL = String "GHCupURL"
toJSON NewStackSetupURL = String "StackSetupURL"
toJSON (NewGHCupInfo gi) = object [ "ghcup-info" .= gi ]
toJSON (NewSetupInfo si) = object [ "setup-info" .= si ]
toJSON (NewURI uri) = toJSON uri
instance ToJSON URLSource where
toJSON = toJSON . fromURLSource
deriveJSON defaultOptions { sumEncoding = ObjectWithSingleField } ''Key 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 { sumEncoding = ObjectWithSingleField } ''Modifier
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel, unwrapUnaryRecords = True } ''Port deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel, unwrapUnaryRecords = True } ''Port
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel, unwrapUnaryRecords = True } ''Host deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel, unwrapUnaryRecords = True } ''Host
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''UserInfo deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''UserInfo
deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' (T.unpack . T.toLower) . T.stripPrefix (T.pack "authority") . T.pack $ str' } ''Authority deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' (T.unpack . T.toLower) . T.stripPrefix (T.pack "authority") . T.pack $ str' } ''Authority
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadMirror deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadMirror
deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadMirrors deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''DownloadMirrors
deriveToJSON defaultOptions { fieldLabelModifier = kebab } ''Settings
deriveToJSON defaultOptions { fieldLabelModifier = drop 2 . kebab } ''KeyBindings -- move under key-bindings key
instance FromJSON URLSource where instance FromJSON URLSource where
parseJSON v = parseJSON v =
parseGHCupURL v parseGHCupURL v
<|> parseStackURL v
<|> parseOwnSourceLegacy v <|> parseOwnSourceLegacy v
<|> parseOwnSourceNew1 v <|> parseOwnSourceNew1 v
<|> parseOwnSourceNew2 v <|> parseOwnSourceNew2 v
<|> parseOwnSpec v <|> parseOwnSpec v
<|> legacyParseAddSource v <|> legacyParseAddSource v
<|> newParseAddSource v <|> newParseAddSource v
-- new since Stack SetupInfo
<|> parseOwnSpecNew v
<|> parseOwnSourceNew3 v
<|> newParseAddSource2 v
-- more lenient versions
<|> parseOwnSpecLenient v
<|> parseOwnSourceLenient v
<|> parseAddSourceLenient v
-- simplified list
<|> parseNewUrlSource v
<|> parseNewUrlSource' v
where where
convert'' :: Either GHCupInfo URI -> Either (Either GHCupInfo SetupInfo) URI
convert'' (Left gi) = Left (Left gi)
convert'' (Right uri) = Right uri
parseOwnSourceLegacy = withObject "URLSource" $ \o -> do parseOwnSourceLegacy = withObject "URLSource" $ \o -> do
r :: URI <- o .: "OwnSource" r :: URI <- o .: "OwnSource"
pure (OwnSource [Right r]) pure (OwnSource [Right r])
@@ -377,18 +347,100 @@ instance FromJSON URLSource where
pure (OwnSource (fmap Right r)) pure (OwnSource (fmap Right r))
parseOwnSourceNew2 = withObject "URLSource" $ \o -> do parseOwnSourceNew2 = withObject "URLSource" $ \o -> do
r :: [Either GHCupInfo URI] <- o .: "OwnSource" r :: [Either GHCupInfo URI] <- o .: "OwnSource"
pure (OwnSource r) pure (OwnSource (convert'' <$> r))
parseOwnSpec = withObject "URLSource" $ \o -> do parseOwnSpec = withObject "URLSource" $ \o -> do
r :: GHCupInfo <- o .: "OwnSpec" r :: GHCupInfo <- o .: "OwnSpec"
pure (OwnSpec r) pure (OwnSpec $ Left r)
parseGHCupURL = withObject "URLSource" $ \o -> do parseGHCupURL = withObject "URLSource" $ \o -> do
_ :: [Value] <- o .: "GHCupURL" _ :: [Value] <- o .: "GHCupURL"
pure GHCupURL pure GHCupURL
parseStackURL = withObject "URLSource" $ \o -> do
_ :: [Value] <- o .: "StackSetupURL"
pure StackSetupURL
legacyParseAddSource = withObject "URLSource" $ \o -> do legacyParseAddSource = withObject "URLSource" $ \o -> do
r :: Either GHCupInfo URI <- o .: "AddSource" r :: Either GHCupInfo URI <- o .: "AddSource"
pure (AddSource [r]) pure (AddSource [convert'' r])
newParseAddSource = withObject "URLSource" $ \o -> do newParseAddSource = withObject "URLSource" $ \o -> do
r :: [Either GHCupInfo URI] <- o .: "AddSource" r :: [Either GHCupInfo URI] <- o .: "AddSource"
pure (AddSource (convert'' <$> r))
-- new since Stack SetupInfo
parseOwnSpecNew = withObject "URLSource" $ \o -> do
r :: Either GHCupInfo SetupInfo <- o .: "OwnSpec"
pure (OwnSpec r)
parseOwnSourceNew3 = withObject "URLSource" $ \o -> do
r :: [Either (Either GHCupInfo SetupInfo) URI] <- o .: "OwnSource"
pure (OwnSource r)
newParseAddSource2 = withObject "URLSource" $ \o -> do
r :: [Either (Either GHCupInfo SetupInfo) URI] <- o .: "AddSource"
pure (AddSource r) pure (AddSource r)
-- more lenient versions
parseOwnSpecLenient = withObject "URLSource" $ \o -> do
spec :: Object <- o .: "OwnSpec"
OwnSpec <$> lenientInfoParser spec
parseOwnSourceLenient = withObject "URLSource" $ \o -> do
mown :: Array <- o .: "OwnSource"
OwnSource . toList <$> mapM lenientInfoUriParser mown
parseAddSourceLenient = withObject "URLSource" $ \o -> do
madd :: Array <- o .: "AddSource"
AddSource . toList <$> mapM lenientInfoUriParser madd
-- simplified
parseNewUrlSource = withArray "URLSource" $ \a -> do
SimpleList . toList <$> mapM parseJSON a
parseNewUrlSource' v' = SimpleList .(:[]) <$> parseJSON v'
lenientInfoUriParser :: Value -> Parser (Either (Either GHCupInfo SetupInfo) URI)
lenientInfoUriParser (Object o) = Left <$> lenientInfoParser o
lenientInfoUriParser v@(String _) = Right <$> parseJSON v
lenientInfoUriParser _ = fail "Unexpected json in lenientInfoUriParser"
lenientInfoParser :: Object -> Parser (Either GHCupInfo SetupInfo)
lenientInfoParser o = do
setup_info :: Maybe Object <- o .:? "setup-info"
case setup_info of
Nothing -> do
r <- parseJSON (Object o)
pure $ Left r
Just setup_info' -> do
r <- parseJSON (Object setup_info')
pure $ Right r
instance FromJSON NewURLSource where
parseJSON v = uri v <|> url v <|> gi v <|> si v
where
uri = withText "NewURLSource" $ \t -> NewURI <$> parseJSON (String t)
url = withText "NewURLSource" $ \t -> case T.unpack t of
"GHCupURL" -> pure NewGHCupURL
"StackSetupURL" -> pure NewStackSetupURL
t' -> fail $ "Unexpected text value in NewURLSource: " <> t'
gi = withObject "NewURLSource" $ \o -> do
ginfo :: GHCupInfo <- o .: "ghcup-info"
pure $ NewGHCupInfo ginfo
si = withObject "NewURLSource" $ \o -> do
sinfo :: SetupInfo <- o .: "setup-info"
pure $ NewSetupInfo sinfo
instance FromJSON KeyCombination where
parseJSON v = proper v <|> simple v
where
simple = withObject "KeyCombination" $ \o -> do
k <- parseJSON (Object o)
pure (KeyCombination k [])
proper = withObject "KeyCombination" $ \o -> do
k <- o .: "Key"
m <- o .: "Mods"
pure $ KeyCombination k m
instance ToJSON KeyCombination where
toJSON (KeyCombination k m) = object ["Key" .= k, "Mods" .= m]
deriveToJSON defaultOptions { fieldLabelModifier = drop 2 . kebab } ''KeyBindings -- move under key-bindings 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 deriveJSON defaultOptions { fieldLabelModifier = \str' -> maybe str' T.unpack . T.stripPrefix (T.pack "u-") . T.pack . kebab $ str' } ''UserSettings
deriveToJSON defaultOptions { fieldLabelModifier = kebab } ''Settings

View File

@@ -0,0 +1,90 @@
{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-|
Module : GHCup.Types.JSON.Versions
Description : GHCup Version JSON types/instances
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : portable
-}
module GHCup.Types.JSON.Versions where
import Data.Aeson hiding (Key)
import Data.Aeson.Types hiding (Key)
import Data.Versions
import qualified Data.Text as T
instance ToJSON Versioning where
toJSON = toJSON . prettyV
instance FromJSON Versioning where
parseJSON = withText "Versioning" $ \t -> case versioning t of
Right x -> pure x
Left e -> fail $ "Failure in GHCTargetVersion (FromJSON)" <> show e
instance ToJSONKey Versioning where
toJSONKey = toJSONKeyText $ \x -> prettyV x
instance FromJSONKey Versioning where
fromJSONKey = FromJSONKeyTextParser $ \t -> case versioning t of
Right x -> pure x
Left e -> fail $ "Failure in Versioning (FromJSONKey)" <> show e
instance ToJSONKey (Maybe Versioning) where
toJSONKey = toJSONKeyText $ \case
Just x -> prettyV x
Nothing -> T.pack "unknown_versioning"
instance FromJSONKey (Maybe Versioning) where
fromJSONKey = FromJSONKeyTextParser $ \t ->
if t == T.pack "unknown_versioning" then pure Nothing else just t
where
just t = case versioning t of
Right x -> pure $ Just x
Left e -> fail $ "Failure in (Maybe Versioning) (FromJSONKey)" <> show e
instance ToJSONKey (Maybe Version) where
toJSONKey = toJSONKeyText $ \case
Just x -> prettyVer x
Nothing -> T.pack "unknown_version"
instance FromJSONKey (Maybe Version) where
fromJSONKey = FromJSONKeyTextParser $ \t ->
if t == T.pack "unknown_version" then pure Nothing else just t
where
just t = case version t of
Right x -> pure $ Just x
Left e -> fail $ "Failure in (Maybe Version) (FromJSONKey)" <> show e
instance ToJSON Version where
toJSON = toJSON . prettyVer
instance FromJSON Version where
parseJSON = withText "Version" $ \t -> case version t of
Right x -> pure x
Left e -> fail $ "Failure in Version (FromJSON)" <> show e
instance ToJSONKey Version where
toJSONKey = toJSONKeyText $ \x -> prettyVer x
instance FromJSONKey Version where
fromJSONKey = FromJSONKeyTextParser $ \t -> case version t of
Right x -> pure x
Left e -> fail $ "Failure in Version (FromJSONKey)" <> show e
instance ToJSON PVP where
toJSON = toJSON . prettyPVP
instance FromJSON PVP where
parseJSON = withText "PVP" $ \t -> case pvp t of
Right x -> pure x
Left e -> fail $ "Failure in PVP (FromJSON)" <> show e

180
lib/GHCup/Types/Stack.hs Normal file
View File

@@ -0,0 +1,180 @@
{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleInstances #-}
{-|
Module : GHCup.Types.Stack
Description : GHCup types.Stack
Copyright : (c) Julian Ospald, 2023
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : portable
-}
module GHCup.Types.Stack where
import GHCup.Types.JSON.Versions ()
import Control.Applicative
import Control.DeepSeq ( NFData )
import Data.ByteString
import Data.Aeson
import Data.Aeson.Types
import Data.Map.Strict ( Map )
import Data.Text ( Text )
import Data.Text.Encoding
import Data.Versions
import qualified Data.Map as Map
import qualified GHC.Generics as GHC
--------------------------------------
--[ Stack download info copy pasta ]--
--------------------------------------
data SetupInfo = SetupInfo
{ siSevenzExe :: Maybe DownloadInfo
, siSevenzDll :: Maybe DownloadInfo
, siMsys2 :: Map Text VersionedDownloadInfo
, siGHCs :: Map Text (Map Version GHCDownloadInfo)
, siStack :: Map Text (Map Version DownloadInfo)
}
deriving (Show, Eq, GHC.Generic)
instance NFData SetupInfo
instance FromJSON SetupInfo where
parseJSON = withObject "SetupInfo" $ \o -> do
siSevenzExe <- o .:? "sevenzexe-info"
siSevenzDll <- o .:? "sevenzdll-info"
siMsys2 <- o .:? "msys2" .!= mempty
siGHCs <- o .:? "ghc" .!= mempty
siStack <- o .:? "stack" .!= mempty
pure SetupInfo {..}
instance ToJSON SetupInfo where
toJSON (SetupInfo {..}) = object [ "sevenzexe-info" .= siSevenzExe
, "sevenzdll-info" .= siSevenzDll
, "msys2" .= siMsys2
, "ghc" .= siGHCs
, "stack" .= siStack
]
-- | For the @siGHCs@ field maps are deeply merged. For all fields the values
-- from the first @SetupInfo@ win.
instance Semigroup SetupInfo where
l <> r =
SetupInfo
{ siSevenzExe = siSevenzExe l <|> siSevenzExe r
, siSevenzDll = siSevenzDll l <|> siSevenzDll r
, siMsys2 = siMsys2 l <> siMsys2 r
, siGHCs = Map.unionWith (<>) (siGHCs l) (siGHCs r)
, siStack = Map.unionWith (<>) (siStack l) (siStack r) }
instance Monoid SetupInfo where
mempty =
SetupInfo
{ siSevenzExe = Nothing
, siSevenzDll = Nothing
, siMsys2 = Map.empty
, siGHCs = Map.empty
, siStack = Map.empty
}
mappend = (<>)
-- | Build of the compiler distribution (e.g. standard, gmp4, tinfo6)
-- | Information for a file to download.
data DownloadInfo = DownloadInfo
{ downloadInfoUrl :: Text
-- ^ URL or absolute file path
, downloadInfoContentLength :: Maybe Int
, downloadInfoSha1 :: Maybe ByteString
, downloadInfoSha256 :: Maybe ByteString
}
deriving (Show, Eq, GHC.Generic)
instance ToJSON DownloadInfo where
toJSON (DownloadInfo {..}) = object [ "url" .= downloadInfoUrl
, "content-length" .= downloadInfoContentLength
, "sha1" .= (decodeUtf8 <$> downloadInfoSha1)
, "sha256" .= (decodeUtf8 <$> downloadInfoSha256)
]
instance NFData DownloadInfo
instance FromJSON DownloadInfo where
parseJSON = withObject "DownloadInfo" parseDownloadInfoFromObject
-- | Parse JSON in existing object for 'DownloadInfo'
parseDownloadInfoFromObject :: Object -> Parser DownloadInfo
parseDownloadInfoFromObject o = do
url <- o .: "url"
contentLength <- o .:? "content-length"
sha1TextMay <- o .:? "sha1"
sha256TextMay <- o .:? "sha256"
pure
DownloadInfo
{ downloadInfoUrl = url
, downloadInfoContentLength = contentLength
, downloadInfoSha1 = fmap encodeUtf8 sha1TextMay
, downloadInfoSha256 = fmap encodeUtf8 sha256TextMay
}
data VersionedDownloadInfo = VersionedDownloadInfo
{ vdiVersion :: Version
, vdiDownloadInfo :: DownloadInfo
}
deriving (Show, Eq, GHC.Generic)
instance ToJSON VersionedDownloadInfo where
toJSON (VersionedDownloadInfo {vdiDownloadInfo = DownloadInfo{..}, ..})
= object [ "version" .= vdiVersion
, "url" .= downloadInfoUrl
, "content-length" .= downloadInfoContentLength
, "sha1" .= (decodeUtf8 <$> downloadInfoSha1)
, "sha256" .= (decodeUtf8 <$> downloadInfoSha256)
]
instance NFData VersionedDownloadInfo
instance FromJSON VersionedDownloadInfo where
parseJSON = withObject "VersionedDownloadInfo" $ \o -> do
ver' <- o .: "version"
downloadInfo <- parseDownloadInfoFromObject o
pure VersionedDownloadInfo
{ vdiVersion = ver'
, vdiDownloadInfo = downloadInfo
}
data GHCDownloadInfo = GHCDownloadInfo
{ gdiConfigureOpts :: [Text]
, gdiConfigureEnv :: Map Text Text
, gdiDownloadInfo :: DownloadInfo
}
deriving (Show, Eq, GHC.Generic)
instance NFData GHCDownloadInfo
instance ToJSON GHCDownloadInfo where
toJSON (GHCDownloadInfo {gdiDownloadInfo = DownloadInfo {..}, ..})
= object [ "configure-opts" .= gdiConfigureOpts
, "configure-env" .= gdiConfigureEnv
, "url" .= downloadInfoUrl
, "content-length" .= downloadInfoContentLength
, "sha1" .= (decodeUtf8 <$> downloadInfoSha1)
, "sha256" .= (decodeUtf8 <$> downloadInfoSha256)
]
instance FromJSON GHCDownloadInfo where
parseJSON = withObject "GHCDownloadInfo" $ \o -> do
configureOpts <- o .:? "configure-opts" .!= mempty
configureEnv <- o .:? "configure-env" .!= mempty
downloadInfo <- parseDownloadInfoFromObject o
pure GHCDownloadInfo
{ gdiConfigureOpts = configureOpts
, gdiConfigureEnv = configureEnv
, gdiDownloadInfo = downloadInfo
}

View File

@@ -49,7 +49,6 @@ import GHCup.Prelude.Logger.Internal
import GHCup.Prelude.MegaParsec import GHCup.Prelude.MegaParsec
import GHCup.Prelude.Process import GHCup.Prelude.Process
import GHCup.Prelude.String.QQ import GHCup.Prelude.String.QQ
import Codec.Archive hiding ( Directory ) import Codec.Archive hiding ( Directory )
import Control.Applicative import Control.Applicative
import Control.Exception.Safe import Control.Exception.Safe
@@ -90,9 +89,9 @@ import qualified Data.Text.Encoding as E
import qualified Text.Megaparsec as MP import qualified Text.Megaparsec as MP
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import qualified Streamly.Prelude as S import qualified Streamly.Prelude as S
import Control.DeepSeq (force) import Control.DeepSeq (force)
import GHC.IO (evaluate) import GHC.IO (evaluate)
import System.Environment (getEnvironment, setEnv)
import Data.Time (Day(..), diffDays, addDays) import Data.Time (Day(..), diffDays, addDays)
@@ -1321,22 +1320,6 @@ warnAboutHlsCompatibility = do
addToPath :: FilePath
-> Bool -- ^ if False will prepend
-> IO [(String, String)]
addToPath path append = do
cEnv <- Map.fromList <$> getEnvironment
let paths = ["PATH", "Path"]
curPaths = (\x -> maybe [] splitSearchPath (Map.lookup x cEnv)) =<< paths
{- HLINT ignore "Redundant bracket" -}
newPath = intercalate [searchPathSeparator] (if append then (curPaths ++ [path]) else (path : curPaths))
envWithoutPath = foldr (\x y -> Map.delete x y) cEnv paths
pathVar = if isWindows then "Path" else "PATH"
envWithNewPath = Map.toList $ Map.insert pathVar newPath envWithoutPath
liftIO $ setEnv pathVar newPath
return envWithNewPath
----------- -----------
--[ Git ]-- --[ Git ]--
----------- -----------

View File

@@ -36,6 +36,9 @@ import Data.Void (Void)
ghcupURL :: URI ghcupURL :: URI
ghcupURL = [uri|https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-0.0.8.yaml|] ghcupURL = [uri|https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-0.0.8.yaml|]
stackSetupURL :: URI
stackSetupURL = [uri|https://raw.githubusercontent.com/commercialhaskell/stackage-content/master/stack/stack-setup-2.yaml|]
-- | The current ghcup version. -- | The current ghcup version.
ghcUpVer :: V.PVP ghcUpVer :: V.PVP
ghcUpVer = V.PVP . NE.fromList . fmap fromIntegral $ versionBranch version ghcUpVer = V.PVP . NE.fromList . fmap fromIntegral $ versionBranch version

View File

@@ -5,6 +5,7 @@ module ConfigTest where
import Test.Tasty import Test.Tasty
import Test.Tasty.HUnit import Test.Tasty.HUnit
import GHCup.OptParse import GHCup.OptParse
import GHCup.Types (NewURLSource(..))
import Utils import Utils
import Control.Monad.IO.Class import Control.Monad.IO.Class
import URI.ByteString.QQ import URI.ByteString.QQ
@@ -23,7 +24,13 @@ checkList =
, ("config init", InitConfig) , ("config init", InitConfig)
, ("config show", ShowConfig) , ("config show", ShowConfig)
, ("config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml" , ("config add-release-channel https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml"
, AddReleaseChannel False [uri|https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml|] , AddReleaseChannel False (NewURI [uri|https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.7.yaml|])
)
, ("config add-release-channel GHCupURL"
, AddReleaseChannel False NewGHCupURL
)
, ("config add-release-channel StackSetupURL"
, AddReleaseChannel False NewStackSetupURL
) )
, ("config set cache true", SetConfig "cache" (Just "true")) , ("config set cache true", SetConfig "cache" (Just "true"))
] ]