packages feed

ghcup 0.2.3.0 → 0.2.5.0

raw patch · 21 files changed

+242/−136 lines, 21 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- GHCup.Input.Parsers: prettyToolVer :: ToolVersion -> String
+ GHCup.Errors: instance GHC.Exception.Type.Exception GHCup.Errors.NotInstalled
+ GHCup.Query.Metadata: getFirstTagF :: ([Tag] -> Bool) -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion
+ GHCup.Query.Metadata: getLatestTag :: Tag -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion
+ GHCup.Query.Metadata: getLatestTagF :: ([Tag] -> Bool) -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion
+ GHCup.Query.Metadata: getTaggedFL :: ([Tag] -> Bool) -> Fold ToolVersionSpec (TargetVersion, VersionMetadata)
+ GHCup.Query.Metadata: getTaggedFL' :: ([Tag] -> Bool) -> Lens' a [Tag] -> Fold (Map TargetVersion a) (TargetVersion, a)
+ GHCup.Types: GHCCompat :: [PVP] -> Tag
+ GHCup.Types: [$sel:bHelp:KeyBindings] :: KeyBindings -> KeyCombination
+ GHCup.Types: [$sel:base:Latest] :: Tag -> PVP
+ GHCup.Types: [$sel:ghcCompat:Latest] :: Tag -> [PVP]
+ GHCup.Types: [$sel:kHelp:UserKeyBindings] :: UserKeyBindings -> Maybe KeyCombination
+ GHCup.Types: [$sel:unknownTag:Latest] :: Tag -> String
+ GHCup.Types: isCompat :: Tag -> Bool
+ GHCup.Types.Optics: _GHCCompat :: Prism' Tag [PVP]
- GHCup.Command.Whereis: whereIsTool :: (MonadReader env m, HasDirs env, HasLog env, HasPlatformReq env, MonadIOish m) => Tool -> TargetVersion -> Excepts '[NotInstalled, ParseError, NoInstallInfo] m FilePath
+ GHCup.Command.Whereis: whereIsTool :: (MonadReader env m, HasDirs env, HasLog env, HasPlatformReq env, MonadIOish m) => Tool -> TargetVersion -> Excepts '[NotInstalled] m FilePath
- GHCup.Query.Metadata: getFirstTag :: Tag -> GHCupDownloads -> Tool -> Maybe TargetVersion
+ GHCup.Query.Metadata: getFirstTag :: Tag -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion
- GHCup.Types: KeyBindings :: KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyBindings
+ GHCup.Types: KeyBindings :: KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyCombination -> KeyBindings
- GHCup.Types: UserKeyBindings :: Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> UserKeyBindings
+ GHCup.Types: UserKeyBindings :: Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> Maybe KeyCombination -> UserKeyBindings
- GHCup.Types.Optics: isConfigure :: forall a_i1WWG. Lens' (InstallationSpecGen a_i1WWG) (Maybe ConfigSpec)
+ GHCup.Types.Optics: isConfigure :: forall a_i1Xv8. Lens' (InstallationSpecGen a_i1Xv8) (Maybe ConfigSpec)
- GHCup.Types.Optics: isDataRules :: forall a_i1WWG. Lens' (InstallationSpecGen a_i1WWG) [InstallFileRule]
+ GHCup.Types.Optics: isDataRules :: forall a_i1Xv8. Lens' (InstallationSpecGen a_i1Xv8) [InstallFileRule]
- GHCup.Types.Optics: isExeRules :: forall a_i1WWG. Lens' (InstallationSpecGen a_i1WWG) [InstallFileRule]
+ GHCup.Types.Optics: isExeRules :: forall a_i1Xv8. Lens' (InstallationSpecGen a_i1Xv8) [InstallFileRule]
- GHCup.Types.Optics: isExeSymLinked :: forall a_a1WWI a_i1WWG. Lens (InstallationSpecGen a_i1WWG) (InstallationSpecGen a_a1WWI) [a_i1WWG] [a_a1WWI]
+ GHCup.Types.Optics: isExeSymLinked :: forall a_a1Xva a_i1Xv8. Lens (InstallationSpecGen a_i1Xv8) (InstallationSpecGen a_a1Xva) [a_i1Xv8] [a_a1Xva]
- GHCup.Types.Optics: isMake :: forall a_i1WWG. Lens' (InstallationSpecGen a_i1WWG) (Maybe MakeSpec)
+ GHCup.Types.Optics: isMake :: forall a_i1Xv8. Lens' (InstallationSpecGen a_i1Xv8) (Maybe MakeSpec)
- GHCup.Types.Optics: isPreserveMtimes :: forall a_i1WWG. Lens' (InstallationSpecGen a_i1WWG) Bool
+ GHCup.Types.Optics: isPreserveMtimes :: forall a_i1Xv8. Lens' (InstallationSpecGen a_i1Xv8) Bool

Files

CHANGELOG.md view
@@ -1,5 +1,18 @@ # Version history for ghcup +## 0.2.5.0 -- 2026-05-26++* Fix broken `ghcup whereis` on non-installed tools wrt [#1347](https://github.com/haskell/ghcup-hs/issues/1347)+* Fix HLS compatibility check on windows wrt [#1342](https://github.com/haskell/ghcup-hs/issues/1342)++## 0.2.4.0 -- 2026-05-25++* Add GHC compatibility to metadata format+  - this shows which GHC is supported by a particular HLS even if it's not installed+  - it also allows to install HLS such as `ghcup install hls ghc-9.14.1`+* Fix up/down/right/left/tab not changable wrt [#1343](https://github.com/haskell/ghcup-hs/issues/1343)+* fix bug when installing via `--url` and specifying a version that doesn't exist in the metadata wrt [#1339](https://github.com/haskell/ghcup-hs/issues/1339)+ ## 0.2.3.0 -- 2026-05-22  * [fix bug](https://github.com/haskell/ghcup-hs/pull/1338) when passing `--install-targets` causes failure
app/ghcup/Main.hs view
@@ -114,6 +114,7 @@      in KeyBindings {            bUp = fromMaybe bUp kUp          , bDown = fromMaybe bDown kDown+         , bHelp = fromMaybe bHelp kHelp          , bQuit = fromMaybe bQuit kQuit          , bLeft = fromMaybe bLeft kLeft          , bRight = fromMaybe bRight kRight
ghcup.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               ghcup-version:            0.2.3.0+version:            0.2.5.0 license:            LGPL-3.0-only license-file:       LICENSE copyright:          Julian Ospald 2024
lib-opt/GHCup/OptParse/Config.hs view
@@ -187,6 +187,7 @@        Just $ UserKeyBindings {            kUp = kUp kbl <|> kUp kbr          , kDown = kDown kbl <|> kDown kbr+         , kHelp = kHelp kbl <|> kHelp kbr          , kQuit = kQuit kbl <|> kQuit kbr          , kLeft = kLeft kbl <|> kLeft kbr          , kRight = kRight kbl <|> kRight kbr
lib-opt/GHCup/OptParse/Install.hs view
@@ -39,6 +39,7 @@ import Control.Monad.Trans.Resource import Data.Functor import Data.Maybe+import Data.Either import Data.Variant.Excepts import Options.Applicative             hiding ( style ) import Options.Applicative.Pretty.Shim ( text )@@ -392,12 +393,12 @@                "...waiting for 5 seconds, you can still abort..."              liftIO $ threadDelay 5000000 -- give the user a sec to intervene            liftE $ runBothE' (do-                     (rev, _) <- liftE $ getDownloadInfoE' instTool (TargetVersionReq tver Nothing)+                     rev <- fmap (fmap fst . veitherToEither) $ runE $ getDownloadInfoE' instTool (TargetVersionReq tver Nothing)                      installBindist                        instTool                        Nothing                        (DownloadInfo ((decUTF8Safe . serializeURIRef') uri) regexDir "" Nothing Nothing Nothing (toInstallationInputSpec <$> defaultToolInstallSpec instTool pfreq tver))-                       (TargetVersionRev tver rev)+                       (TargetVersionRev tver (fromRight 0 rev))                        (maybe GHCupInternal IsolateDir isolateDir)                        forceInstall                        addConfArgs
lib-opt/GHCup/OptParse/List.hs view
@@ -175,16 +175,16 @@           | otherwise       = Pretty.color    let-    printTag Recommended        = color Green "recommended"-    printTag Latest             = color Yellow "latest"-    printTag Prerelease         = color Red "prerelease"-    printTag Nightly            = color Red "nightly"-    printTag (Base       pvp'') = "base-" ++ T.unpack (prettyPVP pvp'')-    printTag (UnknownTag t    ) = t-    printTag LatestPrerelease   = color Red "latest-prerelease"-    printTag LatestNightly      = color Red "latest-nightly"-    printTag Experimental       = color Red "experimental"-    printTag Old                = ""+    printTag t@Recommended        = color Green $ tagToString t+    printTag t@Latest             = color Yellow $ tagToString t+    printTag t@Prerelease         = color Red $ tagToString t+    printTag t@Nightly            = color Red $ tagToString t+    printTag t@LatestPrerelease   = color Red $ tagToString t+    printTag t@LatestNightly      = color Red $ tagToString t+    printTag t@Experimental       = color Red $ tagToString t+    printTag Old                  = ""+    printTag (GHCCompat _)        = ""+    printTag t                    = tagToString t    let     rows =@@ -228,6 +228,7 @@                           then [color Red "no-bindist"]                           else mempty                         )+                     ++ (tagToString <$> filter isCompat lTag)                      ]             )         $ M.toList lr
lib-tui/GHCup/Brick/Actions.hs view
@@ -739,11 +739,11 @@        if _showAllVersions then "Don't show all versions" else "Show all versions"     , hideShowHandler' (not . _showAllVersions)     )-  , (KeyCombination (Vty.KChar 'h') [], Just $ const "help", mode .= KeyInfo)+  , (bHelp, Just $ const "help", mode .= KeyInfo)   , (KeyCombination Vty.KEnter [], Just $ const "Show tool details", mode .= Common.ToolInfo )-  , (KeyCombination KLeft [], Nothing, versionFocus .= False)-  , (KeyCombination KRight [], Nothing, versionFocus .= True)-  , (KeyCombination (Vty.KChar '\t') [], Nothing, versionFocus %= not)+  , (bLeft, Nothing, versionFocus .= False)+  , (bRight, Nothing, versionFocus .= True)+  , (bTab, Nothing, versionFocus %= not)   ]  keyHandlersVersionList :: KeyBindings@@ -763,11 +763,11 @@        if _showAllVersions then "Don't show all versions" else "Show all versions"     , hideShowHandler' (not . _showAllVersions)     )-  , (KeyCombination (Vty.KChar 'h') [], Just $ const "Help", mode .= KeyInfo)+  , (bHelp, Just $ const "Help", mode .= KeyInfo)   , (KeyCombination Vty.KEnter [], Just $ const "Advanced options", createMenuforTool )-  , (KeyCombination KLeft [], Nothing, versionFocus .= False)-  , (KeyCombination KRight [], Nothing, versionFocus .= True)-  , (KeyCombination (Vty.KChar '\t') [], Nothing, versionFocus %= not)+  , (bLeft, Nothing, versionFocus .= False)+  , (bRight, Nothing, versionFocus .= True)+  , (bTab, Nothing, versionFocus %= not)   ]  where   createMenuforTool = do
lib-tui/GHCup/Brick/App.hs view
@@ -152,12 +152,12 @@       | _versionFocus -> do           case find (\(key', _, _) -> key' == KeyCombination key mods) (Actions.keyHandlersVersionList kb) of             Just (_, _, handler) -> handler-            Nothing -> void $ Common.zoom appState $ Navigation.handler _versionFocus inner_event+            Nothing -> void $ Common.zoom appState $ Navigation.handler _versionFocus kb inner_event       | otherwise -> do           case find (\(key', _, _) -> key' == KeyCombination key mods) (Actions.keyHandlersToolList kb) of             Just (_, _, handler) -> handler-            Nothing -> void $ Common.zoom appState $ Navigation.handler _versionFocus inner_event-    inner_event -> Common.zoom appState $ Navigation.handler _versionFocus inner_event+            Nothing -> void $ Common.zoom appState $ Navigation.handler _versionFocus kb inner_event+    inner_event -> Common.zoom appState $ Navigation.handler _versionFocus kb inner_event  contextMenuHandler :: BrickEvent Name e -> EventM Name BrickState () contextMenuHandler ev = do
lib-tui/GHCup/Brick/Common.hs view
@@ -185,9 +185,12 @@   showKey :: Vty.Key -> String+showKey (Vty.KChar '\t') = "TAB" showKey (Vty.KChar c) = [c] showKey Vty.KUp = "↑" showKey Vty.KDown = "↓"+showKey Vty.KLeft = "←"+showKey Vty.KRight = "→" showKey key = tail (show key)  showMod :: Vty.Modifier -> String
lib-tui/GHCup/Brick/Widgets/KeyInfo.hs view
@@ -44,7 +44,11 @@             ],             Brick.hBox [               Brick.txt "Press "-            , Brick.txt "← and → or TAB"+            , Common.keyToWidget bLeft+            , Brick.txt " and "+            , Common.keyToWidget bRight+            , Brick.txt " or "+            , Common.keyToWidget bTab             , Brick.txtWrap " to switch between tool and version list"             ]           , Brick.hBox [
lib-tui/GHCup/Brick/Widgets/Navigation.hs view
@@ -17,7 +17,10 @@       Tool(..),       Tag(..),       tVerToText,-      tagToString, ToolDescription )+      tagToString, ToolDescription,+      isCompat,+      KeyBindings(..),+      KeyCombination(..)) import qualified GHCup.Brick.Common as Common import qualified GHCup.Brick.Attributes as Attributes import Brick@@ -35,7 +38,7 @@ import qualified Brick.Widgets.List as L import           Data.List ( intercalate, sort ) import           Data.Maybe ( mapMaybe )-import           Data.Versions ( prettyPVP, prettyVer )+import           Data.Versions ( prettyVer ) import           Prelude                 hiding ( appendFile ) import qualified Data.Text                     as T import qualified Data.Vector                   as V@@ -48,9 +51,14 @@ type BrickInternalState = BrickList (Tool, (Maybe ToolDescription, BrickList ListResult))  -- | How the navigation handler handle events-handler :: Bool -> BrickEvent Common.Name e -> EventM Common.Name BrickInternalState ()-handler False (Brick.VtyEvent e) = L.handleListEvent e-handler True (Brick.VtyEvent e) = do+handler :: Bool -> KeyBindings -> BrickEvent Common.Name e -> EventM Common.Name BrickInternalState ()+handler False KeyBindings{..} (Brick.VtyEvent e) = handleToolEvent e+ where+  handleToolEvent (Vty.EvKey key mod')+    | KeyCombination key mod' == bUp   = L.handleListEvent (Vty.EvKey Vty.KUp [])+    | KeyCombination key mod' == bDown = L.handleListEvent (Vty.EvKey Vty.KDown [])+  handleToolEvent e' = L.handleListEvent e'+handler True KeyBindings{..} (Brick.VtyEvent e) = do   bis :: BrickInternalState <- get   case L.listSelectedElement bis of     Nothing -> pure ()@@ -59,14 +67,17 @@       modify (L.listModify $ (fmap . fmap) (const updatedVlr))  where   -- need to reverse because we reversed the list+  handleVersionEvent (Vty.EvKey key mod')+    | KeyCombination key mod' == bUp   = L.handleListEvent (Vty.EvKey Vty.KDown [])+    | KeyCombination key mod' == bDown = L.handleListEvent (Vty.EvKey Vty.KUp [])   handleVersionEvent (Vty.EvKey Vty.KUp [])   = L.handleListEvent (Vty.EvKey Vty.KDown [])   handleVersionEvent (Vty.EvKey Vty.KDown []) = L.handleListEvent (Vty.EvKey Vty.KUp [])-  handleVersionEvent (Vty.EvKey Vty.KPageDown [])   = L.handleListEvent (Vty.EvKey Vty.KPageUp [])+  handleVersionEvent (Vty.EvKey Vty.KPageDown []) = L.handleListEvent (Vty.EvKey Vty.KPageUp [])   handleVersionEvent (Vty.EvKey Vty.KPageUp [])   = L.handleListEvent (Vty.EvKey Vty.KPageDown [])-  handleVersionEvent (Vty.EvKey Vty.KHome [])   = L.handleListEvent (Vty.EvKey Vty.KEnd [])-  handleVersionEvent (Vty.EvKey Vty.KEnd [])   = L.handleListEvent (Vty.EvKey Vty.KHome [])+  handleVersionEvent (Vty.EvKey Vty.KHome [])     = L.handleListEvent (Vty.EvKey Vty.KEnd [])+  handleVersionEvent (Vty.EvKey Vty.KEnd [])      = L.handleListEvent (Vty.EvKey Vty.KHome [])   handleVersionEvent e' = L.handleListEvent e'-handler _ _ = pure ()+handler _ _ _ = pure ()   -- | How to draw the navigation widget@@ -82,7 +93,7 @@   allElements = L.listElements bis   minToolSize = V.maximum $ V.map (length . prettyShow . fst) allElements   selectedTool = fmap snd $ L.listSelectedElement bis-  minTagSize = maybe 0 (\(t, (_, vlr)) -> V.maximum $ V.map (length . intercalate "," . fmap tagToString . lTag) $ L.listElements vlr) selectedTool+  minTagSize = maybe 0 (\(t, (_, vlr)) -> V.maximum $ V.map (length . intercalate "," . fmap tagToString . filter (not . isCompat) .  lTag) $ L.listElements vlr) selectedTool   minVerSizeList = maybe 0 (\(t, (_, vlr)) ->  V.maximum $ V.map (\ListResult{..} -> T.length $ tVerToText (TargetVersion lCross lVer)) $ L.listElements vlr) selectedTool   minVerHeaderSize = length $ maybe "Versions" (\(fst -> t) -> prettyShow t <> " versions") selectedTool   minVerSize = max minVerSizeList minVerHeaderSize@@ -153,16 +164,16 @@           <+> Brick.vLimit 1 (Brick.fill ' ')           ) -  printTag Recommended    = Just $ Brick.withAttr Attributes.recommendedAttr $ Brick.str "recommended"-  printTag Latest         = Just $ Brick.withAttr Attributes.latestAttr $ Brick.str "latest"-  printTag Prerelease     = Just $ Brick.withAttr Attributes.prereleaseAttr $ Brick.str "prerelease"-  printTag Nightly        = Just $ Brick.withAttr Attributes.nightlyAttr $ Brick.str "nightly"-  printTag (Base pvp'')   = Just $ Brick.str ("base-" ++ T.unpack (prettyPVP pvp''))-  printTag Old            = Nothing+  printTag Recommended      = Just $ Brick.withAttr Attributes.recommendedAttr $ Brick.str "recommended"+  printTag Latest           = Just $ Brick.withAttr Attributes.latestAttr $ Brick.str "latest"+  printTag Prerelease       = Just $ Brick.withAttr Attributes.prereleaseAttr $ Brick.str "prerelease"+  printTag Nightly          = Just $ Brick.withAttr Attributes.nightlyAttr $ Brick.str "nightly"   printTag LatestPrerelease = Just $ Brick.withAttr Attributes.latestPrereleaseAttr $ Brick.str "latest-prerelease"   printTag LatestNightly    = Just $ Brick.withAttr Attributes.latestNightlyAttr $ Brick.str "latest-nightly"   printTag Experimental     = Just $ Brick.withAttr Attributes.latestNightlyAttr $ Brick.str "experimental"-  printTag (UnknownTag t) = Just $ Brick.str t+  printTag (GHCCompat _)    = Nothing+  printTag Old              = Nothing+  printTag t                = Just $ Brick.str $ tagToString t    printTool (Tool "cabal") = Brick.str "cabal"   printTool (Tool "ghc") = Brick.str "GHC"@@ -178,4 +189,5 @@       ++ (case lReleaseDay of             Nothing -> mempty             Just d  -> [Brick.withAttr Attributes.dayAttr $ Brick.str (show d)])+      ++ (Brick.str . tagToString <$> filter isCompat lTag) 
lib/GHCup/Command/Whereis.hs view
@@ -66,14 +66,15 @@                )             => Tool             -> TargetVersion-            -> Excepts '[NotInstalled, ParseError, NoInstallInfo] m FilePath+            -> Excepts '[NotInstalled] m FilePath whereIsTool tool ver@TargetVersion {..} = do   dirs <- lift getDirs-  sSpec <- lift $ getSymlinkSpecPortable tool ver   toolDir <- fromGHCupPath <$> lift (toolInstallDestination tool ver) +  sSpec <- runE $ getSymlinkSpec tool ver+   case sSpec of-    (SymlinkSpec{..}:_) ->+    (VRight (SymlinkSpec{..}:_)) ->       pure $ toolDir </> _slTarget     _ ->       case tool of@@ -102,6 +103,5 @@         Tool "ghcup" -> do           currentRunningExecPath <- liftIO getExecutablePath           liftIO $ canonicalizePath currentRunningExecPath-        -- TODO-        _ -> pure ""+        _ -> throwE (NotInstalled tool ver) 
lib/GHCup/Errors.hs view
@@ -436,6 +436,8 @@   eBase _ = 130   eDesc _ = "The required tool is not installed" +instance Exception NotInstalled+ data UninstallFailed = UninstallFailed FilePath [FilePath]   deriving (Show) 
lib/GHCup/Input/Parsers.hs view
@@ -46,8 +46,8 @@ import Safe import System.FilePath import URI.ByteString       hiding ( parseURI )-import Text.PrettyPrint.HughesPJClass (prettyShow) +import qualified Data.Set                   as Set import qualified Data.Attoparsec.ByteString as AP import qualified Data.ByteString.UTF8       as UTF8 import qualified Data.Text                  as T@@ -109,11 +109,6 @@   | SetNext   deriving (Eq, Show) -prettyToolVer :: ToolVersion -> String-prettyToolVer (GHCVersion v')  = prettyShow v'-prettyToolVer (ToolVersion v') = prettyShow v'-prettyToolVer (ToolTag t)      = show t-prettyToolVer (ToolDay day)    = show day  toSetToolVer :: Maybe ToolVersion -> SetToolVersion toSetToolVer (Just (GHCVersion v'))  = SetGHCVersion v'@@ -215,15 +210,25 @@   second ToolDay (dayParser s') <|> second ToolTag (tagEither s') <|> second ToolVersion (toolVersionEither' s')  tagEither :: String -> Either String Tag-tagEither s' = case fmap toLower s' of-  "recommended"              -> Right Recommended-  "latest"                   -> Right Latest-  "latest-prerelease"        -> Right LatestPrerelease-  "latest-nightly"           -> Right LatestNightly-  ('b':'a':'s':'e':'-':ver') -> case pvp (T.pack ver') of-                                  Right x -> Right (Base x)-                                  Left  _ -> Left $ "Invalid PVP version for base " <> ver'-  other                      -> Left $ "Unknown tag " <> other+tagEither s' = case ls of+  "recommended"       -> Right Recommended+  "latest"            -> Right Latest+  "latest-prerelease" -> Right LatestPrerelease+  "latest-nightly"    -> Right LatestNightly+  _ -> case s' of+         ('b':'a':'s':'e':'-':ver') -> case pvp (T.pack ver') of+                                         Right x -> Right (Base x)+                                         Left  _ -> Left $ "Invalid PVP version for base " <> ver'+         ('G':'H':'C':':':xs) -> do+                                   vers <- forM (splitOn "," xs)+                                     $ \ver' -> either (\_ -> Left $ "Invalid PVP version for GHC " <> ver') pure (pvp . T.pack $ ver')+                                   pure $ GHCCompat vers+         ('g':'h':'c':'-':ver') -> case pvp (T.pack ver') of+                                         Right x -> Right (GHCCompat [x])+                                         Left  _ -> Left $ "Invalid PVP version for GHC " <> ver'+         other                      -> Left $ "Unknown tag " <> other+ where+  ls = fmap toLower s'   ghcVersionEither :: String -> Either String TargetVersion@@ -399,10 +404,33 @@ resolveVersion' (SetToolTag Recommended) _ tool = do   GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo   fmap (`TargetVersionReq` Nothing) $ getRecommended dls tool ?? TagNotFound Recommended tool-resolveVersion' (SetToolTag (Base pvp'')) _ t+resolveVersion' (SetToolTag tag@(Base _)) _ t   | t == ghc = do       GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo-      fmap (`TargetVersionReq` Nothing) $ getLatestBaseVersion dls pvp'' ?? TagNotFound (Base pvp'') ghc+      fmap (`TargetVersionReq` Nothing)+        $ getLatestTagF+            (\tags -> Prerelease `notElem` tags+                    && LatestPrerelease `notElem` tags+                    && tag `elem` tags+            )+            dls ghc Nothing ?? TagNotFound tag hls+resolveVersion' (SetToolTag (GHCCompat ghcs)) _ t+  | t == hls = do+      GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo+      fmap (`TargetVersionReq` Nothing)+        $ getLatestTagF+            (\tags ->+              if | Prerelease `elem` tags -> False+                 | LatestPrerelease `elem` tags -> False+                 | otherwise -> any+                     (\case+                       GHCCompat (Set.fromList -> ghcs') -> Set.fromList ghcs `Set.isSubsetOf` ghcs'+                       _ -> False+                     )+                     tags+            )+            dls hls Nothing ?? TagNotFound (GHCCompat ghcs) hls+ resolveVersion' SetNext _ tool = do   set <- liftE $ getSetVersion tool Nothing !!? NoToolVersionSet tool Nothing -- TODO   vers <- lift $ getInstalledVersions tool Nothing
lib/GHCup/Query/DB/HLS.hs view
@@ -5,7 +5,7 @@ module GHCup.Query.DB.HLS where  import GHCup.Legacy.Utils-import GHCup.Prelude+import GHCup.Prelude (isWindows) import GHCup.Query.DB import GHCup.Types import GHCup.Types.Optics@@ -15,7 +15,6 @@ import Data.Maybe           ( catMaybes ) import Data.Variant.Excepts ( pattern VLeft, pattern VRight, runE ) import Data.Versions        ( Version, version )-import Safe                 ( headMay )  import qualified Data.Text as T @@ -33,13 +32,27 @@   vspec <- runE $ getSymlinkSpec hls (mkTVer hlsVer)   case vspec of     VRight (fmap (\SymlinkSpec{..} -> _slLinkName) -> bins) -> do-      let extractGHCVerFromBinary bin = do-            prefix <- headMay $ splitOn "~" bin+      let extractGHCVerFromBinary (stripExe -> bin) = do+            prefix <- splitIt '~' bin             s <- stripPrefix "haskell-language-server-" prefix-            either (const Nothing) pure . version . T.pack $ s+            -- stripExe here because we might have a binary of the form+            -- haskell-language-server-9.12.2.exe~2.14.0.0+            either (const Nothing) pure . version . T.pack . stripExe $ s           ghcs = catMaybes $ extractGHCVerFromBinary <$> bins       pure ghcs     -- legacy     VLeft _ -> do       hlsGHCVersions' hlsVer+ where+  splitIt ch = go []+   where+    go prefix (x:xs)+      | x == ch = Just prefix+      | otherwise = go (prefix <> [x]) xs+    go _ [] = Nothing +  stripExe f+    | isWindows = case reverse f of+                    ('e':'x':'e':'.':r) -> reverse r+                    _ -> f+    | otherwise = f
lib/GHCup/Query/Metadata.hs view
@@ -236,19 +236,37 @@ getTaggedL' :: Tag             -> Lens' a [Tag]             -> Fold (Map.Map TargetVersion a) (TargetVersion, a)-getTaggedL' tag getTags =-  to (Map.toDescList . Map.filter (\a -> tag `elem` fromMaybe [] (preview getTags a)))-  % folding id+getTaggedL' tag = getTaggedFL' (tag `elem`)  getTaggedL :: Tag            -> Fold ToolVersionSpec (TargetVersion, VersionMetadata)-getTaggedL tag = _ToolVersionSpec % getTaggedL' tag vmTags+getTaggedL tag = getTaggedFL (tag `elem`) -getFirstTag :: Tag -> GHCupDownloads -> Tool -> Maybe TargetVersion-getFirstTag tag av tool = do-  (tv, _) <- headOf (_GHCupDownloads % ix tool % toolVersions % getTaggedL tag) av-  pure tv+getFirstTag :: Tag -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion+getFirstTag tag = getFirstTagF (tag `elem`) +getLatestTag :: Tag -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion+getLatestTag tag = getLatestTagF (tag `elem`)++getTaggedFL' :: ([Tag] -> Bool)+             -> Lens' a [Tag]+             -> Fold (Map.Map TargetVersion a) (TargetVersion, a)+getTaggedFL' tagF getTags =+  to (Map.toDescList . Map.filter (\a -> tagF $ fromMaybe [] (preview getTags a)))+  % folding id++getTaggedFL :: ([Tag] -> Bool)+            -> Fold ToolVersionSpec (TargetVersion, VersionMetadata)+getTaggedFL tagF = _ToolVersionSpec % getTaggedFL' tagF vmTags++getFirstTagF :: ([Tag] -> Bool) -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion+getFirstTagF tagF av tool target = do+  headOf (_GHCupDownloads % ix tool % toolVersions % getTaggedFL tagF % _1 % filtered (\TargetVersion{..} -> _tvTarget == target)) av++getLatestTagF :: ([Tag] -> Bool) -> GHCupDownloads -> Tool -> Maybe Text -> Maybe TargetVersion+getLatestTagF tagF av tool target = do+  maximumOf (_GHCupDownloads % ix tool % toolVersions % getTaggedFL tagF % _1 % filtered (\TargetVersion{..} -> _tvTarget == target)) av+ getRev :: GHCupDownloads -> Tool -> TargetVersion -> Maybe Int -> Maybe (VersionMetadata, Int, VersionInfo) getRev av tool tv mRev = do   vm <- preview (_GHCupDownloads % ix tool % toolVersionsL % ix tv) av@@ -273,20 +291,20 @@ getByReleaseDayFold day = to (fmap fst . Map.toDescList . Map.filter (\VersionMetadata {..} -> Just day == _vmReleaseDay)) % folding id  getLatest :: GHCupDownloads -> Tool -> Maybe TargetVersion-getLatest = getFirstTag Latest+getLatest dls tool = getFirstTag Latest dls tool Nothing  getLatestPrerelease :: GHCupDownloads -> Tool -> Maybe TargetVersion-getLatestPrerelease = getFirstTag LatestPrerelease+getLatestPrerelease dls tool = getFirstTag LatestPrerelease dls tool Nothing  getLatestNightly :: GHCupDownloads -> Tool -> Maybe TargetVersion-getLatestNightly = getFirstTag LatestNightly+getLatestNightly dls tool = getFirstTag LatestNightly dls tool Nothing  getRecommended :: GHCupDownloads -> Tool -> Maybe TargetVersion-getRecommended = getFirstTag Recommended+getRecommended dls tool = getFirstTag Recommended dls tool Nothing  -- | Gets the latest GHC with a given base version. getLatestBaseVersion :: GHCupDownloads -> PVP -> Maybe TargetVersion-getLatestBaseVersion g pvpVer = getFirstTag (Base pvpVer) g ghc+getLatestBaseVersion g pvpVer = getFirstTag (Base pvpVer) g ghc Nothing   getVersionMetadata :: TargetVersion
lib/GHCup/Types.hs view
@@ -42,6 +42,7 @@ #if defined(DHALL) import Dhall ( FromDhall, ToDhall ) #endif+import Data.List                      ( intercalate ) import Data.List.NonEmpty             ( NonEmpty (..) ) import Data.Map.Strict                ( Map ) import Data.Text                      ( Text )@@ -61,6 +62,7 @@ import qualified GHC.Generics          as GHC import           System.FilePath       ( takeFileName, (<.>) ) import qualified System.FilePath.Posix as Posix+import Data.Functor ((<&>))       -------------------------@@ -321,12 +323,17 @@   --   (a version should either be 'Nightly' or   --   'LatestNightly', but not both)   | LatestNightly -- ^ the latest nightly (unique per tool)-  | Base PVP -- ^ the base version shipped with GHC+  | Base { base :: PVP } -- ^ the base version shipped with GHC   | Old -- ^ old versions are hidden by default in TUI   | Experimental -- ^ an experimental version/bindist-  | UnknownTag String -- ^ used for upwardscompat+  | GHCCompat { ghcCompat :: [PVP] }+  | UnknownTag { unknownTag :: String } -- ^ used for upwardscompat   deriving (Eq, GHC.Generic, Ord, Show) +isCompat :: Tag -> Bool+isCompat (GHCCompat _) = True+isCompat _ = False+ instance NFData Tag  tagToString :: Tag -> String@@ -335,6 +342,7 @@ tagToString Prerelease         = "prerelease" tagToString Nightly            = "nightly" tagToString (Base       pvp'') = "base-" ++ T.unpack (prettyPVP pvp'')+tagToString (GHCCompat  ghcs)  = "GHC:[" <> intercalate "|" (ghcs <&> \ghc' -> T.unpack $ prettyPVP ghc') <> "]" tagToString (UnknownTag t    ) = t tagToString LatestPrerelease   = "latest-prerelease" tagToString LatestNightly      = "latest-nightly"@@ -347,6 +355,7 @@   pPrint Prerelease         = text "prerelease"   pPrint Nightly            = text "nightly"   pPrint (Base       pvp'') = text ("base-" ++ T.unpack (prettyPVP pvp''))+  pPrint (GHCCompat  ghcs)  = text $ "GHC:[" <> intercalate "|" (ghcs <&> \ghc' -> T.unpack $ prettyPVP ghc') <> "]"   pPrint (UnknownTag t    ) = text t   pPrint LatestPrerelease   = text "latest-prerelease"   pPrint LatestNightly      = text "latest-prerelease"@@ -883,6 +892,7 @@             , kLeft         = Just bLeft             , kRight        = Just bRight             , kTab          = Just bTab+            , kHelp         = Just bHelp             , kQuit         = Just bQuit             , kInstall      = Just bInstall             , kUninstall    = Just bUninstall@@ -916,6 +926,7 @@   , kLeft :: Maybe KeyCombination   , kRight :: Maybe KeyCombination   , kTab :: Maybe KeyCombination+  , kHelp :: Maybe KeyCombination   , kQuit :: Maybe KeyCombination   , kInstall :: Maybe KeyCombination   , kUninstall :: Maybe KeyCombination@@ -931,6 +942,7 @@   , bLeft :: KeyCombination   , bRight :: KeyCombination   , bTab :: KeyCombination+  , bHelp :: KeyCombination   , bQuit :: KeyCombination   , bInstall :: KeyCombination   , bUninstall :: KeyCombination@@ -956,6 +968,7 @@   , bLeft            = KeyCombination { key = KLeft      , mods = [] }   , bRight           = KeyCombination { key = KRight     , mods = [] }   , bTab             = KeyCombination { key = KChar '\t' , mods = [] }+  , bHelp            = KeyCombination { key = KChar 'h'  , mods = [] }   , bQuit            = KeyCombination { key = KChar 'q'  , mods = [] }   , bInstall         = KeyCombination { key = KChar 'i'  , mods = [] }   , bUninstall       = KeyCombination { key = KChar 'u'  , mods = [] }
lib/GHCup/Types/Dhall.hs view
@@ -46,7 +46,7 @@ import           GHCup.Types.Stack     () -import           Data.Versions            ( PVP, Version, pvp', version', prettyPVP, prettyVer, pvp )+import           Data.Versions            ( PVP, Version, pvp', version', prettyPVP, prettyVer ) import           Data.Void                ( Void ) import           GHCup.Prelude.MegaParsec ( ghcTargetVerP, versionRangeP ) import qualified Text.Megaparsec          as MP@@ -66,9 +66,6 @@ import           GHCup.Input.Parsers.URI ( parseURI' ) import           URI.ByteString          ( URI, serializeURIRef' ) import Text.PrettyPrint.HughesPJClass (prettyShow)-import Text.Read (readMaybe)-import qualified Data.Map.Strict as M-import qualified Dhall.Map   pattern DhallString :: T.Text -> Expr s a@@ -93,7 +90,7 @@ instance ToDhall Architecture where   injectWith = genericToDhallWithInputNormalizer defaultInterpretOptions -instance {-# OVERLAPPING #-} FromDhall Platform where+instance FromDhall Platform where   autoWith _ =     Dhall.string       { extract = extractParser' parsePlatform@@ -199,38 +196,10 @@   first (DhallErrors . NE.singleton . ExtractError . T.pack . show) . parse . T.pack $ t  instance FromDhall Tag where-  autoWith _ = Dhall.string-    { extract = \case-        DhallString "Latest"                             -> pure Latest-        DhallString "Recommended"                        -> pure Recommended-        DhallString "Prerelease"                         -> pure Prerelease-        DhallString "Nightly"                            -> pure Nightly-        DhallString "LatestPrerelease"                   -> pure LatestPrerelease-        DhallString "LatestNightly"                      -> pure LatestNightly-        DhallString "Experimental"                       -> pure Experimental-        DhallString "old"                                -> pure Old-        e@(DhallString (T.unpack -> 'b' : 'a' : 's' : 'e' : '-' : ver')) -> case pvp (T.pack ver') of-          Right x -> pure $ Base x-          Left  _ -> Dhall.typeError (Dhall.expected Dhall.string) e-        DhallString x -> pure (UnknownTag $ T.unpack x)-        e -> Dhall.typeError (Dhall.expected Dhall.string) e-    }+  autoWith _ = genericAutoWith defaultInterpretOptions  instance ToDhall Tag where-  injectWith _ = Dhall.Encoder-    { embed = \case-        Latest             -> DhallString "Latest"-        Recommended        -> DhallString "Recommended"-        Prerelease         -> DhallString "Prerelease"-        Nightly            -> DhallString "Nightly"-        Old                -> DhallString "old"-        (Base       pvp'') -> DhallString ("base-" <> prettyPVP pvp'')-        LatestPrerelease   -> DhallString "LatestPrerelease"-        LatestNightly      -> DhallString "LatestNightly"-        Experimental       -> DhallString "Experimental"-        (UnknownTag x    ) -> DhallString (T.pack x)-    , declared = Dhall.Core.Text-    }+  injectWith = genericToDhallWithInputNormalizer defaultInterpretOptions  instance FromDhall URI where   autoWith _ =
lib/GHCup/Types/JSON.hs view
@@ -34,6 +34,7 @@ import GHCup.Types.Stack                     ( SetupInfo )  import Control.Applicative ( (<|>) )+import Control.Exception (displayException) import Control.Monad import Data.Aeson          hiding ( Key ) import Data.Aeson.TH@@ -47,14 +48,15 @@ import System.FilePath     ( hasDrive, isAbsolute, splitPath ) import Text.Casing import URI.ByteString      hiding ( parseURI )+import Data.Functor.Contravariant (Contravariant(contramap)) +import qualified Data.Yaml.Aeson  as Y import qualified Data.Map.Strict          as M import qualified Data.List.NonEmpty       as NE import qualified Data.Text                as T import qualified Data.Text.Encoding.Error as E import qualified Text.Megaparsec          as MP import qualified Text.Megaparsec.Char.Lexer as L-import Data.Functor.Contravariant (Contravariant(contramap))  safePath :: FilePath -> Bool safePath fp@@ -194,27 +196,52 @@   toJSON Recommended        = String "Recommended"   toJSON Prerelease         = String "Prerelease"   toJSON Nightly            = String "Nightly"-  toJSON Old                = String "old"+  toJSON Old                = String "Old"   toJSON (Base       pvp'') = String ("base-" <> prettyPVP pvp'')+  toJSON (GHCCompat  ghcs)  = object ["GHCCompat" .= ghcs]   toJSON LatestPrerelease   = String "LatestPrerelease"   toJSON LatestNightly      = String "LatestNightly"   toJSON Experimental       = String "Experimental"   toJSON (UnknownTag x    ) = String (T.pack x)  instance FromJSON Tag where-  parseJSON = withText "Tag" $ \t -> case T.unpack t of-    "Latest"                             -> pure Latest-    "Recommended"                        -> pure Recommended-    "Prerelease"                         -> pure Prerelease-    "Nightly"                            -> pure Nightly-    "LatestPrerelease"                   -> pure LatestPrerelease-    "LatestNightly"                      -> pure LatestNightly-    "Experimental"                       -> pure Experimental-    "old"                                -> pure Old-    ('b' : 'a' : 's' : 'e' : '-' : ver') -> case pvp (T.pack ver') of-      Right x -> pure $ Base x-      Left  e -> fail . show $ e-    x -> pure (UnknownTag x)+  parseJSON v = textParser v <|> objectParser v <|> generic v <|> pure (UnknownTag "??")+   where+    textParser = withText "Tag" $ \t -> case T.unpack t of+      "Latest"                             -> pure Latest+      "Recommended"                        -> pure Recommended+      "Prerelease"                         -> pure Prerelease+      "Nightly"                            -> pure Nightly+      "LatestPrerelease"                   -> pure LatestPrerelease+      "LatestNightly"                      -> pure LatestNightly+      "Experimental"                       -> pure Experimental+      "old"                                -> pure Old+      "Old"                                -> pure Old+      ('b' : 'a' : 's' : 'e' : '-' : ver') -> case pvp (T.pack ver') of+        Right x -> pure $ Base x+        Left  e -> fail . show $ e+      x -> do+        case Y.decodeEither' @Value (E.encodeUtf8 $ T.pack x) of+          Right val -> objectParser val <|> pure (UnknownTag x)+          Left _ -> pure (UnknownTag x)+    objectParser = withObject "Tag" $ \o ->+      (do+         ghcs <- o .:: ["GHCCompat", "ghcCompat"]+         pure $ GHCCompat ghcs+      )+      <|>+      (do+         base <- o .:: ["Base", "base"]+         case pvp base of+           Right v' -> pure $ Base v'+           Left e -> fail (displayException e)+      )+      <|>+      (do+         tag <- o .:: ["UnknownTag", "unknownTag"]+         pure $ UnknownTag tag+      )+    generic = genericParseJSON defaultOptions { unwrapUnaryRecords = True }  instance ToJSON Tool where   toJSON (Tool t) = String (T.toLower . T.pack $ t)
scripts/bootstrap/bootstrap-haskell view
@@ -41,7 +41,7 @@  plat="$(uname -s)" arch=$(uname -m)-ghver="0.2.3.0"+ghver="0.2.5.0" : "${GHCUP_BASE_URL:=https://downloads.haskell.org/~ghcup}"  export GHCUP_SKIP_UPDATE_CHECK=yes@@ -448,7 +448,6 @@             die "Unknown downloader: ${BOOTSTRAP_HASKELL_DOWNLOADER}"             ;;     esac-    eghcup upgrade }  # Figures out the users login shell and sets
test/ghcup-test/GHCup/Types/JSONSpec.hs view
@@ -40,6 +40,7 @@   roundtripSpecs (Proxy @InstallationSpecInput)   roundtripSpecs (Proxy @Tool)   roundtripSpecs (Proxy @TargetVersion)+  roundtripSpecs (Proxy @Tag)   roundtripSpecs (Proxy @VersionInfo)   roundtripSpecs (Proxy @Architecture)   roundtripSpecs (Proxy @Platform)