From d30d2ac8a5c52cb022d380b4af4212fb75d80d8c Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 28 Jul 2020 20:55:00 +0200 Subject: [PATCH 01/19] Add cabal-install-3.4.0.0-rc1 --- app/ghcup-gen/Validate.hs | 1 + app/ghcup/BrickMain.hs | 2 ++ app/ghcup/Main.hs | 1 + lib/GHCup/Types.hs | 1 + lib/GHCup/Types/JSON.hs | 2 ++ 5 files changed, 7 insertions(+) diff --git a/app/ghcup-gen/Validate.hs b/app/ghcup-gen/Validate.hs index bd1ee0e..4dc214f 100644 --- a/app/ghcup-gen/Validate.hs +++ b/app/ghcup-gen/Validate.hs @@ -111,6 +111,7 @@ validate dls = do where isUniqueTag Latest = True isUniqueTag Recommended = True + isUniqueTag Prerelease = False isUniqueTag (Base _) = False isUniqueTag (UnknownTag _) = False diff --git a/app/ghcup/BrickMain.hs b/app/ghcup/BrickMain.hs index 67eea69..a40cb1b 100644 --- a/app/ghcup/BrickMain.hs +++ b/app/ghcup/BrickMain.hs @@ -112,6 +112,7 @@ ui AppState {..} = printTag Recommended = withAttr "recommended" $ str "recommended" printTag Latest = withAttr "latest" $ str "latest" + printTag Prerelease = withAttr "prerelease" $ str "prerelease" printTag (Base pvp'') = str ("base-" ++ T.unpack (prettyPVP pvp'')) printTag (UnknownTag t ) = str t @@ -137,6 +138,7 @@ defaultAttributes = attrMap , ("installed" , Vty.defAttr `Vty.withForeColor` Vty.green) , ("recommended" , Vty.defAttr `Vty.withForeColor` Vty.green) , ("latest" , Vty.defAttr `Vty.withForeColor` Vty.yellow) + , ("prerelease" , Vty.defAttr `Vty.withForeColor` Vty.red) , ("help" , Vty.defAttr `Vty.withStyle` Vty.italic) ] diff --git a/app/ghcup/Main.hs b/app/ghcup/Main.hs index 7cb639d..cf42698 100644 --- a/app/ghcup/Main.hs +++ b/app/ghcup/Main.hs @@ -1422,6 +1422,7 @@ printListResult raw lr = do where printTag Recommended = color' Green "recommended" printTag Latest = color' Yellow "latest" + printTag Prerelease = color' Red "prerelease" printTag (Base pvp'') = "base-" ++ T.unpack (prettyPVP pvp'') printTag (UnknownTag t ) = t color' = case raw of diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs index df6a8e5..070b2ec 100644 --- a/lib/GHCup/Types.hs +++ b/lib/GHCup/Types.hs @@ -92,6 +92,7 @@ data VersionInfo = VersionInfo -- | A tag. These are currently attached to a version of a tool. data Tag = Latest | Recommended + | Prerelease | Base PVP | UnknownTag String -- ^ used for upwardscompat deriving (Ord, Eq, Show) -- FIXME: manual JSON instance diff --git a/lib/GHCup/Types/JSON.hs b/lib/GHCup/Types/JSON.hs index c87e33d..c055894 100644 --- a/lib/GHCup/Types/JSON.hs +++ b/lib/GHCup/Types/JSON.hs @@ -53,6 +53,7 @@ deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Requir instance ToJSON Tag where toJSON Latest = String "Latest" toJSON Recommended = String "Recommended" + toJSON Prerelease = String "Prerelease" toJSON (Base pvp'') = String ("base-" <> prettyPVP pvp'') toJSON (UnknownTag x ) = String (T.pack x) @@ -60,6 +61,7 @@ instance FromJSON Tag where parseJSON = withText "Tag" $ \t -> case T.unpack t of "Latest" -> pure Latest "Recommended" -> pure Recommended + "Prerelease" -> pure Prerelease ('b' : 'a' : 's' : 'e' : '-' : ver') -> case pvp (T.pack ver') of Right x -> pure $ Base x Left e -> fail . show $ e From af811f3dbc950149191c81ea21bd10adbc3743dc Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 28 Jul 2020 21:40:26 +0200 Subject: [PATCH 02/19] `nub` result in getInstalledCabals --- lib/GHCup/Utils.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index 0f04867..052a057 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -245,7 +245,7 @@ getInstalledCabals = do Just (Left _) -> pure $ Left f Nothing -> pure $ Left f cs <- cabalSet -- for legacy cabal - pure $ maybe vs (\x -> Right x:vs) cs + pure $ maybe vs (\x -> nub $ Right x:vs) cs -- | Whether the given cabal version is installed. From 86b0e4b31bc6233f43257b8222f7750c4f906de4 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 28 Jul 2020 21:44:25 +0200 Subject: [PATCH 03/19] Fix `cabalSet` for pre-release versions --- lib/GHCup/Utils.hs | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index 052a057..9b9534d 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -259,18 +259,33 @@ cabalInstalled ver = do cabalSet :: (MonadIO m, MonadThrow m) => m (Maybe Version) cabalSet = do cabalbin <- ( [rel|cabal|]) <$> liftIO ghcupBinDir - mc <- liftIO $ handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut - cabalbin - ["--numeric-version"] - Nothing - fmap join $ forM mc $ \c -> if - | not (B.null (_stdOut c)) - , _exitCode c == ExitSuccess -> do - let reportedVer = fst . B.spanEnd (== _lf) . _stdOut $ c - case version $ decUTF8Safe reportedVer of - Left e -> throwM e - Right r -> pure $ Just r - | otherwise -> pure Nothing + b <- fmap (== SymbolicLink) $ liftIO $ getFileType cabalbin + if + | b -> do + liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ do + link <- readSymbolicLink $ toFilePath cabalbin + Just <$> linkVersion link + | otherwise -> do -- legacy behavior + mc <- liftIO $ handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut + cabalbin + ["--numeric-version"] + Nothing + fmap join $ forM mc $ \c -> if + | not (B.null (_stdOut c)), _exitCode c == ExitSuccess -> do + let reportedVer = fst . B.spanEnd (== _lf) . _stdOut $ c + case version $ decUTF8Safe reportedVer of + Left e -> throwM e + Right r -> pure $ Just r + | otherwise -> pure Nothing + where + linkVersion :: MonadThrow m => ByteString -> m Version + linkVersion bs = do + t <- throwEither $ E.decodeUtf8' bs + throwEither $ MP.parse parser "" t + where + parser = + MP.chunk "cabal-" *> version' + From 7d334c18f5edf68bbc9220cbabe92cda4a007dd7 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 28 Jul 2020 21:53:54 +0200 Subject: [PATCH 04/19] Don't stop TUI on subcommand failure --- app/ghcup/BrickMain.hs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/ghcup/BrickMain.hs b/app/ghcup/BrickMain.hs index a40cb1b..c3f526d 100644 --- a/app/ghcup/BrickMain.hs +++ b/app/ghcup/BrickMain.hs @@ -175,19 +175,18 @@ withIOAction :: (AppState -> (Int, ListResult) -> IO (Either String a)) withIOAction action as = case listSelectedElement (lr as) of Nothing -> continue as Just (ix, e) -> suspendAndResume $ do - r <- action as (ix, e) - case r of - Left err -> throwIO $ userError err - Right _ -> do - apps <- (fmap . fmap) - (\AppState {..} -> AppState { lr = listMoveTo ix lr, .. }) - $ getAppState Nothing (pfreq as) - case apps of - Right nas -> do - putStrLn "Press enter to continue" - _ <- getLine - pure nas - Left err -> throwIO $ userError err + action as (ix, e) >>= \case + Left err -> putStrLn $ ("Error: " <> err) + Right _ -> putStrLn "Success" + apps <- (fmap . fmap) + (\AppState {..} -> AppState { lr = listMoveTo ix lr, .. }) + $ getAppState Nothing (pfreq as) + case apps of + Right nas -> do + putStrLn "Press enter to continue" + _ <- getLine + pure nas + Left err -> throwIO $ userError err install' :: AppState -> (Int, ListResult) -> IO (Either String ()) From aac8f760adbc16857ff1042847f45d03c7e26c69 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 29 Jul 2020 01:43:00 +0200 Subject: [PATCH 05/19] Add xdg support wrt #39 --- README.md | 11 +++++ app/ghcup/Main.hs | 16 ++++--- bootstrap-haskell | 39 +++++++++++------ lib/GHCup.hs | 6 +-- lib/GHCup/Download.hs | 2 +- lib/GHCup/Utils.hs | 61 +++++++++++++++----------- lib/GHCup/Utils/Dirs.hs | 90 ++++++++++++++++++++++++++++++++++++--- lib/GHCup/Utils/File.hs | 9 ++++ lib/GHCup/Utils/Logger.hs | 2 +- 9 files changed, 179 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 1f6cfc2..a726d17 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Similar in scope to [rustup](https://github.com/rust-lang-nursery/rustup.rs), [p * [Manpages](#manpages) * [Shell-completion](#shell-completion) * [Cross support](#cross-support) + * [XDG support](#xdg-support) * [Design goals](#design-goals) * [How](#how) * [Known users](#known-users) @@ -96,6 +97,16 @@ For distributions with non-standard locations of cross toolchain and libraries, this may need some tweaking of `build.mk` or configure args. See `ghcup compile ghc --help` for further information. +### Cross support + +To enable XDG style directories, set the environment variable `GHCUP_USE_XDG_DIRS` to anything. + +Then you can control the locations via XDG environment variables as such: + +* `XDG_DATA_HOME`: GHCs will be unpacked in `ghcup/ghc` subdir +* `XDG_CACHE_HOME`: logs and download files will be stored in `ghcup` subdir +* `XDG_BIN_HOME`: binaries end up here (default: `~/.local/bin`) + ## Design goals 1. simplicity diff --git a/app/ghcup/Main.hs b/app/ghcup/Main.hs index cf42698..5fd5172 100644 --- a/app/ghcup/Main.hs +++ b/app/ghcup/Main.hs @@ -903,9 +903,11 @@ Report bugs at |] >>= \opt@Options {..} -> do let settings@Settings{..} = toSettings opt + logsDir <- toFilePath <$> ghcupLogsDir + -- create ~/.ghcup dir ghcdir <- ghcupBaseDir - createDirIfMissing newDirPerms ghcdir + createDirRecursive newDirPerms ghcdir -- logger interpreter logfile <- initGHCupFileLogging [rel|ghcup.log|] @@ -1079,7 +1081,7 @@ Report bugs at |] case keepDirs of Never -> runLogger ($(logError) [i|Build failed with #{e}|]) _ -> runLogger ($(logError) [i|Build failed with #{e} - Check the logs at ~/.ghcup/logs and the build directory #{tmpdir} for more clues. + Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues. Make sure to clean up #{tmpdir} afterwards.|]) pure $ ExitFailure 3 VLeft (V NoDownload) -> do @@ -1092,7 +1094,7 @@ Report bugs at |] VLeft e -> do runLogger $ do $(logError) [i|#{e}|] - $(logError) [i|Also check the logs in ~/.ghcup/logs|] + $(logError) [i|Also check the logs in #{logsDir}|] pure $ ExitFailure 3 @@ -1121,7 +1123,7 @@ Report bugs at |] VLeft e -> do runLogger $ do $(logError) [i|#{e}|] - $(logError) [i|Also check the logs in ~/.ghcup/logs|] + $(logError) [i|Also check the logs in #{logsDir}|] pure $ ExitFailure 4 let setGHC' SetOptions{..} = @@ -1237,9 +1239,9 @@ Report bugs at |] VLeft (V (BuildFailed tmpdir e)) -> do case keepDirs of Never -> runLogger ($(logError) [i|Build failed with #{e} -Check the logs at ~/.ghcup/logs|]) +Check the logs at #{logsDir}|]) _ -> runLogger ($(logError) [i|Build failed with #{e} -Check the logs at ~/.ghcup/logs and the build directory #{tmpdir} for more clues. +Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues. Make sure to clean up #{tmpdir} afterwards.|]) pure $ ExitFailure 9 VLeft e -> do @@ -1261,7 +1263,7 @@ Make sure to clean up #{tmpdir} afterwards.|]) case keepDirs of Never -> runLogger ($(logError) [i|Build failed with #{e}|]) _ -> runLogger ($(logError) [i|Build failed with #{e} -Check the logs at ~/.ghcup/logs and the build directory #{tmpdir} for more clues. +Check the logs at #{logsDir} and the build directory #{tmpdir} for more clues. Make sure to clean up #{tmpdir} afterwards.|]) pure $ ExitFailure 10 VLeft e -> do diff --git a/bootstrap-haskell b/bootstrap-haskell index e968d29..7b0c8cc 100755 --- a/bootstrap-haskell +++ b/bootstrap-haskell @@ -4,6 +4,17 @@ ( : "${GHCUP_INSTALL_BASE_PREFIX:=$HOME}" + +export GHCUP_USE_XDG_DIRS + +if [ -n "${GHCUP_USE_XDG_DIRS}" ] ; then + GHCUP_DIR=${XDG_DATA_HOME:=$HOME/.local}/ghcup + GHCUP_BIN=${XDG_BIN_HOME:=$HOME/.local/bin} +else + GHCUP_DIR=${GHCUP_INSTALL_BASE_PREFIX}/.ghcup + GHCUP_BIN=${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin +fi + : "${BOOTSTRAP_HASKELL_GHC_VERSION:=recommended}" : "${BOOTSTRAP_HASKELL_CABAL_VERSION:=recommended}" @@ -83,15 +94,15 @@ download_ghcup() { ;; esac - edo curl -Lf "${_url}" > "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/bin/ghcup + edo curl -Lf "${_url}" > "${GHCUP_BIN}"/ghcup - edo chmod +x "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/bin/ghcup + edo chmod +x "${GHCUP_BIN}"/ghcup - cat <<-EOF > "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/env || die "Failed to create env file" - export PATH="\$HOME/.cabal/bin:\${GHCUP_INSTALL_BASE_PREFIX:=\$HOME}/.ghcup/bin:\$PATH" + cat <<-EOF > "${GHCUP_DIR}"/env || die "Failed to create env file" + export PATH="\$HOME/.cabal/bin:${GHCUP_BIN}:\$PATH" EOF # shellcheck disable=SC1090 - edo . "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/env + edo . "${GHCUP_DIR}"/env eghcup upgrade unset _plat _arch _url _ghver _base_url @@ -106,8 +117,12 @@ echo " * ghcup - The Haskell toolchain installer (for managing GHC/cabal versio echo " * ghc - The Glasgow Haskell Compiler" echo " * cabal - The Cabal build tool" echo -echo "ghcup installs only into the following directory, which can be removed anytime:" -echo " $GHCUP_INSTALL_BASE_PREFIX/.ghcup" +if [ -z "${GHCUP_USE_XDG_DIRS}" ] ; then + echo "ghcup installs only into the following directory, which can be removed anytime:" + echo " $GHCUP_INSTALL_BASE_PREFIX/.ghcup" +else + echo "ghcup installs into XDG directories as long as 'GHCUP_USE_XDG_DIRS' is set" +fi echo if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then @@ -119,7 +134,7 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then read -r answer /dev/null 2>&1 ; then if [ -z "${BOOTSTRAP_HASKELL_NO_UPGRADE}" ] ; then @@ -156,7 +171,7 @@ printf "\\033[0;35m%s\\033[0m\\n" "" if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then echo "In order to run ghc and cabal, you need to adjust your PATH variable." - echo "You may want to source '$GHCUP_INSTALL_BASE_PREFIX/.ghcup/env' in your shell" + echo "You may want to source '$GHCUP_DIR/env' in your shell" echo "configuration to do so (e.g. ~/.bashrc)." case $SHELL in @@ -199,14 +214,14 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then "") break ;; fish) echo "set -q GHCUP_INSTALL_BASE_PREFIX[1]; or set GHCUP_INSTALL_BASE_PREFIX \$HOME" >> "${GHCUP_PROFILE_FILE}" - echo "test -f \$GHCUP_INSTALL_BASE_PREFIX/.ghcup/env ; and set -gx PATH \$HOME/.cabal/bin \$GHCUP_INSTALL_BASE_PREFIX/.ghcup/bin \$PATH" >> "${GHCUP_PROFILE_FILE}" + echo "test -f $GHCUP_DIR/env ; and set -gx PATH \$HOME/.cabal/bin $GHCUP_BIN/bin \$PATH" >> "${GHCUP_PROFILE_FILE}" break ;; *) - echo "[ -f \"\${GHCUP_INSTALL_BASE_PREFIX:=\$HOME}/.ghcup/env\" ] && source \"\${GHCUP_INSTALL_BASE_PREFIX:=\$HOME}/.ghcup/env\"" >> "${GHCUP_PROFILE_FILE}" + echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\"" >> "${GHCUP_PROFILE_FILE}" break ;; esac printf "\\033[0;35m%s\\033[0m\\n" "OK! ${GHCUP_PROFILE_FILE} has been modified. Restart your terminal for the changes to take effect," - printf "\\033[0;35m%s\\033[0m\\n" "or type \"source ${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/env\" to apply them in your current terminal session." + printf "\\033[0;35m%s\\033[0m\\n" "or type \"source ${GHCUP_DIR}/env\" to apply them in your current terminal session." exit 0;; [Nn]*) exit 0;; diff --git a/lib/GHCup.hs b/lib/GHCup.hs index 8c55626..d712e50 100644 --- a/lib/GHCup.hs +++ b/lib/GHCup.hs @@ -270,7 +270,7 @@ installCabalBindist dlinfo ver (PlatformRequest {..}) = do installCabal' path inst = do lift $ $(logInfo) "Installing cabal" let cabalFile = [rel|cabal|] - liftIO $ createDirIfMissing newDirPerms inst + liftIO $ createDirRecursive newDirPerms inst destFileName <- lift $ parseRel (toFilePath cabalFile <> "-" <> verToBS ver) handleIO (throwE . CopyError . show) $ liftIO $ copyFile (path cabalFile) @@ -360,7 +360,7 @@ setGHC ver sghc = do -- create symlink let fullF = bindir targetFile - let destL = ghcLinkDestination (toFilePath file) ver + destL <- ghcLinkDestination (toFilePath file) ver lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|] liftIO $ createSymlink fullF destL @@ -631,7 +631,7 @@ rmCabalVer :: (MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) => Version -> Excepts '[NotInstalled] m () rmCabalVer ver = do - whenM (fmap not $ liftIO $ cabalInstalled ver) $ throwE (NotInstalled GHC (prettyVer ver)) + whenM (fmap not $ liftIO $ cabalInstalled ver) $ throwE (NotInstalled Cabal (prettyVer ver)) cSet <- liftIO cabalSet diff --git a/lib/GHCup/Download.hs b/lib/GHCup/Download.hs index bf9f4f1..e06e6ed 100644 --- a/lib/GHCup/Download.hs +++ b/lib/GHCup/Download.hs @@ -226,7 +226,7 @@ getDownloads urlSource = do else -- access in less than 5 minutes, re-use file liftIO $ readFile json_file else do - liftIO $ createDirIfMissing newDirPerms cacheDir + liftIO $ createDirRecursive newDirPerms cacheDir getModTime >>= \case Just modTime -> dlWithMod modTime json_file Nothing -> do diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index 9b9534d..fb94e6c 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -97,11 +97,15 @@ import qualified Text.Megaparsec as MP -- | The symlink destination of a ghc tool. -ghcLinkDestination :: ByteString -- ^ the tool, such as 'ghc', 'haddock' etc. +ghcLinkDestination :: (MonadThrow m, MonadIO m) + => ByteString -- ^ the tool, such as 'ghc', 'haddock' etc. -> GHCTargetVersion - -> ByteString -ghcLinkDestination tool ver = - "../ghc/" <> E.encodeUtf8 (prettyTVer ver) <> "/bin/" <> tool + -> m ByteString +ghcLinkDestination tool ver = do + t <- parseRel tool + bin <- liftIO ghcupBinDir + ghcd <- liftIO $ ghcupGHCDir ver + pure (relativeSymlink bin (ghcd [rel|bin|] t)) -- | Removes the minor GHC symlinks, e.g. ghc-8.6.5. @@ -201,25 +205,26 @@ ghcSet mtarget = do liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ do link <- readSymbolicLink $ toFilePath ghcBin Just <$> ghcLinkVersion link + +ghcLinkVersion :: MonadThrow m => ByteString -> m GHCTargetVersion +ghcLinkVersion bs = do + t <- throwEither $ E.decodeUtf8' bs + throwEither $ MP.parse parser "ghcLinkVersion" t where - ghcLinkVersion :: MonadThrow m => ByteString -> m GHCTargetVersion - ghcLinkVersion bs = do - t <- throwEither $ E.decodeUtf8' bs - throwEither $ MP.parse parser "" t - where - parser = - MP.chunk "../ghc/" - *> (do - r <- parseUntil1 (MP.chunk "/") - rest <- MP.getInput - MP.setInput r - x <- ghcTargetVerP - MP.setInput rest - pure x - ) - <* MP.chunk "/" - <* MP.takeRest - <* MP.eof + parser = + (do + _ <- parseUntil1 (MP.chunk "/ghc/") + _ <- MP.chunk "/ghc/" + r <- parseUntil1 (MP.chunk "/") + rest <- MP.getInput + MP.setInput r + x <- ghcTargetVerP + MP.setInput rest + pure x + ) + <* MP.chunk "/" + <* MP.takeRest + <* MP.eof -- | Get all installed GHCs by reading ~/.ghcup/ghc/. @@ -256,15 +261,19 @@ cabalInstalled ver = do -- Return the currently set cabal version, if any. -cabalSet :: (MonadIO m, MonadThrow m) => m (Maybe Version) +cabalSet :: (MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version) cabalSet = do cabalbin <- ( [rel|cabal|]) <$> liftIO ghcupBinDir - b <- fmap (== SymbolicLink) $ liftIO $ getFileType cabalbin + b <- handleIO (\_ -> pure False) $ fmap (== SymbolicLink) $ liftIO $ getFileType cabalbin if | b -> do liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ do - link <- readSymbolicLink $ toFilePath cabalbin - Just <$> linkVersion link + broken <- isBrokenSymlink cabalbin + if broken + then pure Nothing + else do + link <- readSymbolicLink $ toFilePath cabalbin + Just <$> linkVersion link | otherwise -> do -- legacy behavior mc <- liftIO $ handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut cabalbin diff --git a/lib/GHCup/Utils/Dirs.hs b/lib/GHCup/Utils/Dirs.hs index f3c0b12..dc9d4cf 100644 --- a/lib/GHCup/Utils/Dirs.hs +++ b/lib/GHCup/Utils/Dirs.hs @@ -24,6 +24,7 @@ import Control.Exception.Safe import Control.Monad import Control.Monad.Reader import Control.Monad.Trans.Resource +import Data.ByteString ( ByteString ) import Data.Maybe import HPath import HPath.IO @@ -35,6 +36,7 @@ import Prelude hiding ( abs import System.Posix.Env.ByteString ( getEnv , getEnvDefault ) +import System.Posix.FilePath hiding ( () ) import System.Posix.Temp.ByteString ( mkdtemp ) import qualified Data.ByteString.UTF8 as UTF8 @@ -51,12 +53,25 @@ import qualified Text.Megaparsec as MP -- | ~/.ghcup by default +-- +-- If 'GHCUP_USE_XDG_DIRS' is set (to anything), +-- then uses 'XDG_DATA_HOME/ghcup' as per xdg spec. ghcupBaseDir :: IO (Path Abs) ghcupBaseDir = do - bdir <- getEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case - Just r -> parseAbs r - Nothing -> liftIO getHomeDirectory - pure (bdir [rel|.ghcup|]) + xdg <- useXDG + if xdg + then do + bdir <- getEnv "XDG_DATA_HOME" >>= \case + Just r -> parseAbs r + Nothing -> do + home <- liftIO getHomeDirectory + pure (home [rel|.local/share|]) + pure (bdir [rel|ghcup|]) + else do + bdir <- getEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case + Just r -> parseAbs r + Nothing -> liftIO getHomeDirectory + pure (bdir [rel|.ghcup|]) -- | ~/.ghcup/ghc by default. @@ -82,14 +97,54 @@ parseGHCupGHCDir (toFilePath -> f) = do throwEither $ MP.parse ghcTargetVerP "" fp +-- | If 'GHCUP_USE_XDG_DIRS' is set (to anything), +-- then uses 'XDG_BIN_HOME' env var or defaults to '~/.local/bin' +-- (which, sadly is not strictly xdg spec). ghcupBinDir :: IO (Path Abs) -ghcupBinDir = ghcupBaseDir <&> ( [rel|bin|]) +ghcupBinDir = do + xdg <- useXDG + if xdg + then do + getEnv "XDG_BIN_HOME" >>= \case + Just r -> parseAbs r + Nothing -> do + home <- liftIO getHomeDirectory + pure (home [rel|.local/bin|]) + else ghcupBaseDir <&> ( [rel|bin|]) +-- | Defaults to '~/.ghcup/cache'. +-- +-- If 'GHCUP_USE_XDG_DIRS' is set (to anything), +-- then uses 'XDG_CACHE_HOME/ghcup' as per xdg spec. ghcupCacheDir :: IO (Path Abs) -ghcupCacheDir = ghcupBaseDir <&> ( [rel|cache|]) +ghcupCacheDir = do + xdg <- useXDG + if xdg + then do + bdir <- getEnv "XDG_CACHE_HOME" >>= \case + Just r -> parseAbs r + Nothing -> do + home <- liftIO getHomeDirectory + pure (home [rel|.cache|]) + pure (bdir [rel|ghcup|]) + else ghcupBaseDir <&> ( [rel|cache|]) +-- | Defaults to '~/.ghcup/logs'. +-- +-- If 'GHCUP_USE_XDG_DIRS' is set (to anything), +-- then uses 'XDG_CACHE_HOME/ghcup/logs' as per xdg spec. ghcupLogsDir :: IO (Path Abs) -ghcupLogsDir = ghcupBaseDir <&> ( [rel|logs|]) +ghcupLogsDir = do + xdg <- useXDG + if xdg + then do + bdir <- getEnv "XDG_CACHE_HOME" >>= \case + Just r -> parseAbs r + Nothing -> do + home <- liftIO getHomeDirectory + pure (home [rel|.cache|]) + pure (bdir [rel|ghcup/logs|]) + else ghcupBaseDir <&> ( [rel|logs|]) mkGhcupTmpDir :: (MonadThrow m, MonadIO m) => m (Path Abs) @@ -116,3 +171,24 @@ getHomeDirectory = do Nothing -> do h <- PU.homeDirectory <$> (PU.getEffectiveUserID >>= PU.getUserEntryForID) parseAbs $ UTF8.fromString h -- this is a guess + + +useXDG :: IO Bool +useXDG = isJust <$> getEnv "GHCUP_USE_XDG_DIRS" + + +relativeSymlink :: Path Abs -- ^ the path in which to create the symlink + -> Path Abs -- ^ the symlink destination + -> ByteString +relativeSymlink (toFilePath -> p1) (toFilePath -> p2) = + let d1 = splitDirectories p1 + d2 = splitDirectories p2 + common = takeWhile (\(x, y) -> x == y) $ zip d1 d2 + cPrefix = drop (length common) d1 + in joinPath (replicate (length cPrefix) "..") + <> joinPath ("/" : (drop (length common) d2)) + + + + + diff --git a/lib/GHCup/Utils/File.hs b/lib/GHCup/Utils/File.hs index eec8362..706ccda 100644 --- a/lib/GHCup/Utils/File.hs +++ b/lib/GHCup/Utils/File.hs @@ -427,3 +427,12 @@ findFiles' path parser = do Right p' -> isJust $ MP.parseMaybe parser p') $ dirContentsStream dirStream pure $ join $ fmap parseRel f + + +isBrokenSymlink :: Path Abs -> IO Bool +isBrokenSymlink p = + handleIO + (\e -> if ioeGetErrorType e == NoSuchThing then pure True else throwIO e) + $ do + _ <- canonicalizePath p + pure False diff --git a/lib/GHCup/Utils/Logger.hs b/lib/GHCup/Utils/Logger.hs index 45f49dc..0b7088f 100644 --- a/lib/GHCup/Utils/Logger.hs +++ b/lib/GHCup/Utils/Logger.hs @@ -65,7 +65,7 @@ initGHCupFileLogging :: Path Rel -> IO (Path Abs) initGHCupFileLogging context = do logs <- ghcupLogsDir let logfile = logs context - createDirIfMissing newDirPerms logs + createDirRecursive newDirPerms logs hideError doesNotExistErrorType $ deleteFile logfile createRegularFile newFilePerms logfile pure logfile From 9050c9792a879d8d253c514ed9112f50025b9504 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 29 Jul 2020 21:07:05 +0200 Subject: [PATCH 06/19] Improve bootstrap-haskell --- bootstrap-haskell | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/bootstrap-haskell b/bootstrap-haskell index 7b0c8cc..467a119 100755 --- a/bootstrap-haskell +++ b/bootstrap-haskell @@ -40,6 +40,22 @@ _eghcup() { fi } +_done() { + echo + echo "All done!" + echo + echo "To start a simple repl, run:" + echo " ghci" + echo + echo "To start a new haskell project in the current directory, run:" + echo " cabal init --interactive" + echo + echo "To install other GHC versions, run:" + echo " ghcup tui" + + exit 0 +} + download_ghcup() { _plat="$(uname -s)" _arch=$(uname -m) @@ -113,15 +129,18 @@ echo echo "Welcome to Haskell!" echo echo "This script will download and install the following binaries:" -echo " * ghcup - The Haskell toolchain installer (for managing GHC/cabal versions)" +echo " * ghcup - The Haskell toolchain installer" +echo " (for managing GHC/cabal versions)" echo " * ghc - The Glasgow Haskell Compiler" echo " * cabal - The Cabal build tool" echo if [ -z "${GHCUP_USE_XDG_DIRS}" ] ; then - echo "ghcup installs only into the following directory, which can be removed anytime:" + echo "ghcup installs only into the following directory," + echo "which can be removed anytime:" echo " $GHCUP_INSTALL_BASE_PREFIX/.ghcup" else - echo "ghcup installs into XDG directories as long as 'GHCUP_USE_XDG_DIRS' is set" + echo "ghcup installs into XDG directories as long as" + echo "'GHCUP_USE_XDG_DIRS' is set." fi echo @@ -189,13 +208,13 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then GHCUP_PROFILE_FILE="$HOME/.zshrc" MY_SHELL="zsh" else - exit 0 + _done fi ;; */fish) # login shell is fish GHCUP_PROFILE_FILE="$HOME/.config/fish/config.fish" MY_SHELL="fish" ;; - *) exit 0 ;; + *) _done ;; esac @@ -222,9 +241,10 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then esac printf "\\033[0;35m%s\\033[0m\\n" "OK! ${GHCUP_PROFILE_FILE} has been modified. Restart your terminal for the changes to take effect," printf "\\033[0;35m%s\\033[0m\\n" "or type \"source ${GHCUP_DIR}/env\" to apply them in your current terminal session." - exit 0;; + _done + ;; [Nn]*) - exit 0;; + _done ;; *) echo "Please type YES or NO and press enter.";; esac From b9d7d7d007a728cd09b9c9ad2fc5c43a553ae5ce Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Thu, 30 Jul 2020 20:04:02 +0200 Subject: [PATCH 07/19] Fix licences in module haddock --- lib/GHCup.hs | 2 +- lib/GHCup/Download.hs | 2 +- lib/GHCup/Errors.hs | 2 +- lib/GHCup/Platform.hs | 2 +- lib/GHCup/Requirements.hs | 2 +- lib/GHCup/Types.hs | 2 +- lib/GHCup/Types/JSON.hs | 2 +- lib/GHCup/Types/Optics.hs | 2 +- lib/GHCup/Utils.hs | 2 +- lib/GHCup/Utils/Dirs.hs | 2 +- lib/GHCup/Utils/File.hs | 2 +- lib/GHCup/Utils/Logger.hs | 2 +- lib/GHCup/Utils/MegaParsec.hs | 2 +- lib/GHCup/Utils/Prelude.hs | 2 +- lib/GHCup/Utils/String/QQ.hs | 2 +- lib/GHCup/Utils/Version/QQ.hs | 2 +- lib/GHCup/Version.hs | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/GHCup.hs b/lib/GHCup.hs index d712e50..bdfacd5 100644 --- a/lib/GHCup.hs +++ b/lib/GHCup.hs @@ -15,7 +15,7 @@ Module : GHCup Description : GHCup installation functions Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Download.hs b/lib/GHCup/Download.hs index e06e6ed..45ff181 100644 --- a/lib/GHCup/Download.hs +++ b/lib/GHCup/Download.hs @@ -13,7 +13,7 @@ Module : GHCup.Download Description : Downloading Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Errors.hs b/lib/GHCup/Errors.hs index 4108121..5f09bfb 100644 --- a/lib/GHCup/Errors.hs +++ b/lib/GHCup/Errors.hs @@ -7,7 +7,7 @@ Module : GHCup.Errors Description : GHCup error types Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Platform.hs b/lib/GHCup/Platform.hs index 6f69eef..1fa8c14 100644 --- a/lib/GHCup/Platform.hs +++ b/lib/GHCup/Platform.hs @@ -10,7 +10,7 @@ Module : GHCup.Plaform Description : Retrieving platform information Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Requirements.hs b/lib/GHCup/Requirements.hs index 24f4761..83753f8 100644 --- a/lib/GHCup/Requirements.hs +++ b/lib/GHCup/Requirements.hs @@ -4,7 +4,7 @@ Module : GHCup.Requirements Description : Requirements utilities Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs index 070b2ec..ed69f03 100644 --- a/lib/GHCup/Types.hs +++ b/lib/GHCup/Types.hs @@ -6,7 +6,7 @@ Module : GHCup.Types Description : GHCup types Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Types/JSON.hs b/lib/GHCup/Types/JSON.hs index c055894..1f27bee 100644 --- a/lib/GHCup/Types/JSON.hs +++ b/lib/GHCup/Types/JSON.hs @@ -14,7 +14,7 @@ Module : GHCup.Types.JSON Description : GHCup JSON types/instances Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Types/Optics.hs b/lib/GHCup/Types/Optics.hs index 5fb344e..2486175 100644 --- a/lib/GHCup/Types/Optics.hs +++ b/lib/GHCup/Types/Optics.hs @@ -4,7 +4,7 @@ Module : GHCup.Types.Optics Description : GHCup optics Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index fb94e6c..f251ab9 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -10,7 +10,7 @@ Module : GHCup.Utils Description : GHCup domain specific utilities Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/Dirs.hs b/lib/GHCup/Utils/Dirs.hs index dc9d4cf..814fc6f 100644 --- a/lib/GHCup/Utils/Dirs.hs +++ b/lib/GHCup/Utils/Dirs.hs @@ -6,7 +6,7 @@ Module : GHCup.Utils.Dirs Description : Definition of GHCup directories Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/File.hs b/lib/GHCup/Utils/File.hs index 706ccda..be16bd5 100644 --- a/lib/GHCup/Utils/File.hs +++ b/lib/GHCup/Utils/File.hs @@ -7,7 +7,7 @@ Module : GHCup.Utils.File Description : File and unix APIs Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/Logger.hs b/lib/GHCup/Utils/Logger.hs index 0b7088f..2e58cc1 100644 --- a/lib/GHCup/Utils/Logger.hs +++ b/lib/GHCup/Utils/Logger.hs @@ -4,7 +4,7 @@ Module : GHCup.Utils.Logger Description : logger definition Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/MegaParsec.hs b/lib/GHCup/Utils/MegaParsec.hs index d652361..ac379fe 100644 --- a/lib/GHCup/Utils/MegaParsec.hs +++ b/lib/GHCup/Utils/MegaParsec.hs @@ -5,7 +5,7 @@ Module : GHCup.Utils.MegaParsec Description : MegaParsec utilities Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/Prelude.hs b/lib/GHCup/Utils/Prelude.hs index 20fd896..6bd913b 100644 --- a/lib/GHCup/Utils/Prelude.hs +++ b/lib/GHCup/Utils/Prelude.hs @@ -12,7 +12,7 @@ Module : GHCup.Utils.Prelude Description : MegaParsec utilities Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/String/QQ.hs b/lib/GHCup/Utils/String/QQ.hs index 6cff357..85f566f 100644 --- a/lib/GHCup/Utils/String/QQ.hs +++ b/lib/GHCup/Utils/String/QQ.hs @@ -4,7 +4,7 @@ Module : GHCup.Utils.String.QQ Description : String quasi quoters Copyright : (c) Audrey Tang 2019, Julian Ospald 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Utils/Version/QQ.hs b/lib/GHCup/Utils/Version/QQ.hs index 663460e..73912cc 100644 --- a/lib/GHCup/Utils/Version/QQ.hs +++ b/lib/GHCup/Utils/Version/QQ.hs @@ -11,7 +11,7 @@ Module : GHCup.Utils.Version.QQ Description : Version quasi-quoters Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX diff --git a/lib/GHCup/Version.hs b/lib/GHCup/Version.hs index 55a923e..24d4ad7 100644 --- a/lib/GHCup/Version.hs +++ b/lib/GHCup/Version.hs @@ -5,7 +5,7 @@ Module : GHCup.Version Description : Static version information Copyright : (c) Julian Ospald, 2020 -License : GPL-3 +License : LGPL-3.0 Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX From 122c54b51e46a70949f3a37612d947850f80cf92 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Thu, 30 Jul 2020 20:04:37 +0200 Subject: [PATCH 08/19] Refactor --- lib/GHCup/Utils.hs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index f251ab9..53b9523 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -376,17 +376,16 @@ unpackToDir dest av = do #if defined(TAR) let untar :: MonadIO m => BL.ByteString -> Excepts '[] m () untar = liftIO . Tar.unpack (toFilePath dest) . Tar.read + + rf :: MonadIO m => Path Abs -> Excepts '[] m BL.ByteString + rf = liftIO . readFile #else let untar :: MonadIO m => BL.ByteString -> Excepts '[ArchiveResult] m () untar = lEM . liftIO . runArchiveM . unpackToDirLazy (T.unpack . decUTF8Safe . toFilePath $ dest) -#endif -#if defined(TAR) - rf :: MonadIO m => Path Abs -> Excepts '[] m BL.ByteString -#else rf :: MonadIO m => Path Abs -> Excepts '[ArchiveResult] m BL.ByteString -#endif rf = liftIO . readFile +#endif -- extract, depending on file extension if From 7163b77837a98105a5b3eac36b5f3d60fb547e8e Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Fri, 31 Jul 2020 20:10:40 +0200 Subject: [PATCH 09/19] Only query directories once --- lib/GHCup/Types.hs | 13 +++++- lib/GHCup/Utils.hs | 14 ++---- lib/GHCup/Utils/Dirs.hs | 99 +++++++++++++++++++++++++++-------------- 3 files changed, 82 insertions(+), 44 deletions(-) diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs index ed69f03..0a49cfc 100644 --- a/lib/GHCup/Types.hs +++ b/lib/GHCup/Types.hs @@ -158,14 +158,25 @@ data URLSource = GHCupURL data Settings = Settings - { cache :: Bool + { -- * set by user + cache :: Bool , noVerify :: Bool , keepDirs :: KeepDirs , downloader :: Downloader , verbose :: Bool + + -- * set on app start + , dirs :: Dirs } deriving Show +data Dirs = Dirs + { baseDir :: Path Abs + , binDir :: Path Abs + , cacheDir :: Path Abs + , logsDir :: Path Abs + } + deriving Show data KeepDirs = Always | Errors diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index 53b9523..8c02886 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -576,24 +576,18 @@ runBuildAction :: (Show (V e), MonadReader Settings m, MonadIO m, MonadMask m) -> Excepts '[BuildFailed] m a runBuildAction bdir instdir action = do Settings {..} <- lift ask - v <- flip - onException - (do + let exAction = do forM_ instdir $ \dir -> liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive dir when (keepDirs == Never) $ liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive bdir - ) + v <- + flip onException exAction $ catchAllE (\es -> do - forM_ instdir $ \dir -> - liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive dir - when (keepDirs == Never) - $ liftIO - $ hideError doesNotExistErrorType - $ deleteDirRecursive bdir + exAction throwE (BuildFailed bdir es) ) $ action diff --git a/lib/GHCup/Utils/Dirs.hs b/lib/GHCup/Utils/Dirs.hs index 814fc6f..0e72cf8 100644 --- a/lib/GHCup/Utils/Dirs.hs +++ b/lib/GHCup/Utils/Dirs.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ViewPatterns #-} @@ -11,7 +12,16 @@ Maintainer : hasufell@hasufell.de Stability : experimental Portability : POSIX -} -module GHCup.Utils.Dirs where +module GHCup.Utils.Dirs + ( getDirs + , ghcupGHCBaseDir + , ghcupGHCDir + , parseGHCupGHCDir + , mkGhcupTmpDir + , withGHCupTmpDir + , relativeSymlink + ) +where import GHCup.Types @@ -36,7 +46,7 @@ import Prelude hiding ( abs import System.Posix.Env.ByteString ( getEnv , getEnvDefault ) -import System.Posix.FilePath hiding ( () ) +import System.Posix.FilePath hiding ( () ) import System.Posix.Temp.ByteString ( mkdtemp ) import qualified Data.ByteString.UTF8 as UTF8 @@ -47,9 +57,9 @@ import qualified Text.Megaparsec as MP - ------------------------- - --[ GHCup directories ]-- - ------------------------- + ------------------------------ + --[ GHCup base directories ]-- + ------------------------------ -- | ~/.ghcup by default @@ -74,29 +84,6 @@ ghcupBaseDir = do pure (bdir [rel|.ghcup|]) --- | ~/.ghcup/ghc by default. -ghcupGHCBaseDir :: IO (Path Abs) -ghcupGHCBaseDir = ghcupBaseDir <&> ( [rel|ghc|]) - - --- | Gets '~/.ghcup/ghc/'. --- The dir may be of the form --- * armv7-unknown-linux-gnueabihf-8.8.3 --- * 8.8.4 -ghcupGHCDir :: GHCTargetVersion -> IO (Path Abs) -ghcupGHCDir ver = do - ghcbasedir <- ghcupGHCBaseDir - verdir <- parseRel $ E.encodeUtf8 (prettyTVer ver) - pure (ghcbasedir verdir) - - --- | See 'ghcupToolParser'. -parseGHCupGHCDir :: MonadThrow m => Path Rel -> m GHCTargetVersion -parseGHCupGHCDir (toFilePath -> f) = do - fp <- throwEither $ E.decodeUtf8' f - throwEither $ MP.parse ghcTargetVerP "" fp - - -- | If 'GHCUP_USE_XDG_DIRS' is set (to anything), -- then uses 'XDG_BIN_HOME' env var or defaults to '~/.local/bin' -- (which, sadly is not strictly xdg spec). @@ -112,6 +99,7 @@ ghcupBinDir = do pure (home [rel|.local/bin|]) else ghcupBaseDir <&> ( [rel|bin|]) + -- | Defaults to '~/.ghcup/cache'. -- -- If 'GHCUP_USE_XDG_DIRS' is set (to anything), @@ -129,6 +117,7 @@ ghcupCacheDir = do pure (bdir [rel|ghcup|]) else ghcupBaseDir <&> ( [rel|cache|]) + -- | Defaults to '~/.ghcup/logs'. -- -- If 'GHCUP_USE_XDG_DIRS' is set (to anything), @@ -147,6 +136,49 @@ ghcupLogsDir = do else ghcupBaseDir <&> ( [rel|logs|]) +getDirs :: IO Dirs +getDirs = do + baseDir <- ghcupBaseDir + binDir <- ghcupBinDir + cacheDir <- ghcupCacheDir + logsDir <- ghcupLogsDir + pure Dirs { .. } + + + + ------------------------- + --[ GHCup directories ]-- + ------------------------- + + +-- | ~/.ghcup/ghc by default. +ghcupGHCBaseDir :: (MonadReader Settings m) => m (Path Abs) +ghcupGHCBaseDir = do + Settings {..} <- ask + pure (baseDir dirs [rel|ghc|]) + + +-- | Gets '~/.ghcup/ghc/'. +-- The dir may be of the form +-- * armv7-unknown-linux-gnueabihf-8.8.3 +-- * 8.8.4 +ghcupGHCDir :: (MonadReader Settings m, MonadThrow m) + => GHCTargetVersion + -> m (Path Abs) +ghcupGHCDir ver = do + Settings {..} <- ask + ghcbasedir <- ghcupGHCBaseDir + verdir <- parseRel $ E.encodeUtf8 (prettyTVer ver) + pure (ghcbasedir verdir) + + +-- | See 'ghcupToolParser'. +parseGHCupGHCDir :: MonadThrow m => Path Rel -> m GHCTargetVersion +parseGHCupGHCDir (toFilePath -> f) = do + fp <- throwEither $ E.decodeUtf8' f + throwEither $ MP.parse ghcTargetVerP "" fp + + mkGhcupTmpDir :: (MonadThrow m, MonadIO m) => m (Path Abs) mkGhcupTmpDir = do tmpdir <- liftIO $ getEnvDefault "TMPDIR" "/tmp" @@ -158,6 +190,8 @@ withGHCupTmpDir :: (MonadResource m, MonadThrow m, MonadIO m) => m (Path Abs) withGHCupTmpDir = snd <$> allocate mkGhcupTmpDir deleteDirRecursive + + -------------- --[ Others ]-- -------------- @@ -181,14 +215,13 @@ relativeSymlink :: Path Abs -- ^ the path in which to create the symlink -> Path Abs -- ^ the symlink destination -> ByteString relativeSymlink (toFilePath -> p1) (toFilePath -> p2) = - let d1 = splitDirectories p1 - d2 = splitDirectories p2 - common = takeWhile (\(x, y) -> x == y) $ zip d1 d2 - cPrefix = drop (length common) d1 + let d1 = splitDirectories p1 + d2 = splitDirectories p2 + common = takeWhile (\(x, y) -> x == y) $ zip d1 d2 + cPrefix = drop (length common) d1 in joinPath (replicate (length cPrefix) "..") <> joinPath ("/" : (drop (length common) d2)) - From cafedd73a2fbabb5e87850613bb8b91e17c9efac Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Wed, 5 Aug 2020 21:50:39 +0200 Subject: [PATCH 10/19] Use Settings to avoid querying dirs every time --- app/ghcup-gen/Validate.hs | 4 +- app/ghcup/BrickMain.hs | 7 ++- app/ghcup/Main.hs | 28 +++++---- lib/GHCup.hs | 119 ++++++++++++++++++++------------------ lib/GHCup/Download.hs | 10 ++-- lib/GHCup/Types.hs | 4 +- lib/GHCup/Utils.hs | 72 ++++++++++++----------- lib/GHCup/Utils/File.hs | 6 +- lib/GHCup/Utils/Logger.hs | 20 ++++--- 9 files changed, 140 insertions(+), 130 deletions(-) diff --git a/app/ghcup-gen/Validate.hs b/app/ghcup-gen/Validate.hs index 4dc214f..cec2130 100644 --- a/app/ghcup-gen/Validate.hs +++ b/app/ghcup-gen/Validate.hs @@ -7,6 +7,7 @@ module Validate where import GHCup import GHCup.Download import GHCup.Types +import GHCup.Utils.Dirs import GHCup.Utils.Logger import Control.Exception.Safe @@ -180,7 +181,8 @@ validateTarballs dls = do where downloadAll dli = do - let settings = Settings True False Never Curl False + dirs <- liftIO getDirs + let settings = Settings True False Never Curl False dirs let runLogger = myLoggerT LoggerConfig { lcPrintDebug = True , colorOutter = B.hPut stderr , rawOutter = (\_ -> pure ()) diff --git a/app/ghcup/BrickMain.hs b/app/ghcup/BrickMain.hs index c3f526d..f563ced 100644 --- a/app/ghcup/BrickMain.hs +++ b/app/ghcup/BrickMain.hs @@ -297,14 +297,15 @@ uri' = unsafePerformIO (newIORef Nothing) settings' :: IORef Settings {-# NOINLINE settings' #-} -settings' = unsafePerformIO - (newIORef Settings { cache = True +settings' = unsafePerformIO $ do + dirs <- getDirs + newIORef Settings { cache = True , noVerify = False , keepDirs = Never , downloader = Curl , verbose = False + , .. } - ) logger' :: IORef LoggerConfig diff --git a/app/ghcup/Main.hs b/app/ghcup/Main.hs index 5fd5172..7464ae5 100644 --- a/app/ghcup/Main.hs +++ b/app/ghcup/Main.hs @@ -1,6 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} @@ -818,14 +819,15 @@ bindistParser :: String -> Either String DownloadInfo bindistParser = eitherDecode . BLU.fromString -toSettings :: Options -> Settings -toSettings Options {..} = +toSettings :: Options -> IO Settings +toSettings Options {..} = do let cache = optCache noVerify = optNoVerify keepDirs = optKeepDirs downloader = optsDownloader verbose = optVerbose - in Settings { .. } + dirs <- getDirs + pure $ Settings { .. } upgradeOptsP :: Parser UpgradeOpts @@ -901,16 +903,13 @@ Report bugs at |] (footerDoc (Just $ text main_footer)) ) >>= \opt@Options {..} -> do - let settings@Settings{..} = toSettings opt - - logsDir <- toFilePath <$> ghcupLogsDir + settings@Settings{dirs = Dirs{..}, ..} <- toSettings opt -- create ~/.ghcup dir - ghcdir <- ghcupBaseDir - createDirRecursive newDirPerms ghcdir + createDirRecursive newDirPerms baseDir -- logger interpreter - logfile <- initGHCupFileLogging [rel|ghcup.log|] + logfile <- flip runReaderT settings $ initGHCupFileLogging [rel|ghcup.log|] let loggerConfig = LoggerConfig { lcPrintDebug = optVerbose , colorOutter = B.hPut stderr @@ -956,12 +955,13 @@ Report bugs at |] let runSetCabal = runLogger + . flip runReaderT settings . runE @'[ NotInstalled , TagNotFound ] - let runListGHC = runLogger + let runListGHC = runLogger . flip runReaderT settings let runRm = runLogger . flip runReaderT settings . runE @'[NotInstalled] @@ -1054,7 +1054,7 @@ Report bugs at |] case optCommand of Upgrade _ _ -> pure () - _ -> runLogger $ checkForUpdates dls pfreq + _ -> runLogger $ flip runReaderT settings $ checkForUpdates dls pfreq @@ -1277,9 +1277,7 @@ Make sure to clean up #{tmpdir} afterwards.|]) p <- parseAbs . E.encodeUtf8 . T.pack $ efp pure $ Just p (UpgradeAt p) -> pure $ Just p - UpgradeGHCupDir -> do - bdir <- liftIO $ ghcupBinDir - pure (Just (bdir [rel|ghcup|])) + UpgradeGHCupDir -> pure (Just (binDir [rel|ghcup|])) (runUpgrade $ (liftE $ upgradeGHCup dls target force pfreq)) >>= \case VRight v' -> do @@ -1431,7 +1429,7 @@ printListResult raw lr = do True -> flip const False -> color -checkForUpdates :: (MonadCatch m, MonadLogger m, MonadThrow m, MonadIO m, MonadFail m, MonadLogger m) +checkForUpdates :: (MonadReader Settings m, MonadCatch m, MonadLogger m, MonadThrow m, MonadIO m, MonadFail m, MonadLogger m) => GHCupDownloads -> PlatformRequest -> m () diff --git a/lib/GHCup.hs b/lib/GHCup.hs index bdfacd5..84b993e 100644 --- a/lib/GHCup.hs +++ b/lib/GHCup.hs @@ -121,7 +121,7 @@ installGHCBindist :: ( MonadFail m installGHCBindist dlinfo ver (PlatformRequest {..}) = do let tver = (mkTVer ver) lift $ $(logDebug) [i|Requested to install GHC with #{ver}|] - whenM (liftIO $ ghcInstalled tver) + whenM (lift $ ghcInstalled tver) $ (throwE $ AlreadyInstalled GHC ver) -- download (or use cached version) @@ -133,7 +133,7 @@ installGHCBindist dlinfo ver (PlatformRequest {..}) = do void $ liftIO $ darwinNotarization _rPlatform tmpUnpack -- prepare paths - ghcdir <- liftIO $ ghcupGHCDir tver + ghcdir <- lift $ ghcupGHCDir tver -- the subdir of the archive where we do the work let workdir = maybe tmpUnpack (tmpUnpack ) (view dlSubdir dlinfo) @@ -230,14 +230,14 @@ installCabalBindist :: ( MonadMask m installCabalBindist dlinfo ver (PlatformRequest {..}) = do lift $ $(logDebug) [i|Requested to install cabal version #{ver}|] - bindir <- liftIO ghcupBinDir + Settings {dirs = Dirs {..}} <- lift ask whenM - (liftIO $ cabalInstalled ver >>= \a -> + (lift (cabalInstalled ver) >>= \a -> liftIO $ handleIO (\_ -> pure False) $ fmap (\x -> a && isSymbolicLink x) -- ignore when the installation is a legacy cabal (binary, not symlink) - $ getSymbolicLinkStatus (toFilePath (bindir [rel|cabal|])) + $ getSymbolicLinkStatus (toFilePath (binDir [rel|cabal|])) ) $ (throwE $ AlreadyInstalled Cabal ver) @@ -252,10 +252,10 @@ installCabalBindist dlinfo ver (PlatformRequest {..}) = do -- the subdir of the archive where we do the work let workdir = maybe tmpUnpack (tmpUnpack ) (view dlSubdir dlinfo) - liftE $ installCabal' workdir bindir + liftE $ installCabal' workdir binDir -- create symlink if this is the latest version - cVers <- liftIO $ fmap rights $ getInstalledCabals + cVers <- lift $ fmap rights $ getInstalledCabals let lInstCabal = headMay . reverse . sort $ cVers when (maybe True (ver >=) lInstCabal) $ liftE $ setCabal ver @@ -328,17 +328,17 @@ installCabalBin bDls ver pfreq = do -- -- Additionally creates a @~\/.ghcup\/share -> ~\/.ghcup\/ghc\/\\/share symlink@ -- for 'SetGHCOnly' constructor. -setGHC :: (MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) +setGHC :: (MonadReader Settings m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) => GHCTargetVersion -> SetGHC -> Excepts '[NotInstalled] m GHCTargetVersion setGHC ver sghc = do let verBS = verToBS (_tvVersion ver) - ghcdir <- liftIO $ ghcupGHCDir ver + ghcdir <- lift $ ghcupGHCDir ver -- symlink destination - bindir <- liftIO $ ghcupBinDir - liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms bindir + Settings {dirs = Dirs {..}} <- lift ask + liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms binDir -- first delete the old symlinks (this fixes compatibility issues -- with old ghcup) @@ -359,8 +359,8 @@ setGHC ver sghc = do SetGHC_XYZ -> parseRel (toFilePath file <> B.singleton _hyphen <> verBS) -- create symlink - let fullF = bindir targetFile - destL <- ghcLinkDestination (toFilePath file) ver + let fullF = binDir targetFile + destL <- lift $ ghcLinkDestination (toFilePath file) ver lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|] liftIO $ createSymlink fullF destL @@ -371,12 +371,13 @@ setGHC ver sghc = do where - symlinkShareDir :: (MonadIO m, MonadLogger m) + symlinkShareDir :: (MonadReader Settings m, MonadIO m, MonadLogger m) => Path Abs -> ByteString -> m () symlinkShareDir ghcdir verBS = do - destdir <- liftIO $ ghcupBaseDir + Settings {dirs = Dirs {..}} <- ask + let destdir = baseDir case sghc of SetGHCOnly -> do let sharedir = [rel|share|] @@ -393,7 +394,7 @@ setGHC ver sghc = do -- | Set the @~\/.ghcup\/bin\/cabal@ symlink. -setCabal :: (MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) +setCabal :: (MonadReader Settings m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) => Version -> Excepts '[NotInstalled] m () setCabal ver = do @@ -401,14 +402,14 @@ setCabal ver = do targetFile <- parseRel ("cabal-" <> verBS) -- symlink destination - bindir <- liftIO $ ghcupBinDir - liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms bindir + Settings {dirs = Dirs {..}} <- lift ask + liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms binDir - whenM (liftIO $ fmap not $ doesFileExist (bindir targetFile)) + whenM (liftIO $ fmap not $ doesFileExist (binDir targetFile)) $ throwE $ NotInstalled Cabal (prettyVer ver) - let cabalbin = bindir [rel|cabal|] + let cabalbin = binDir [rel|cabal|] -- delete old file (may be binary or symlink) lift $ $(logDebug) [i|rm -f #{toFilePath cabalbin}|] @@ -467,6 +468,7 @@ listVersions :: ( MonadCatch m , MonadThrow m , MonadLogger m , MonadIO m + , MonadReader Settings m ) => GHCupDownloads -> Maybe Tool @@ -478,7 +480,7 @@ listVersions av lt criteria pfreq = do Just t -> do -- get versions from GHCupDownloads let avTools = availableToolVersions av t - lr <- filter' <$> forM (Map.toList avTools) (liftIO . toListResult t) + lr <- filter' <$> forM (Map.toList avTools) (toListResult t) case t of -- append stray GHCs @@ -493,7 +495,7 @@ listVersions av lt criteria pfreq = do pure (ghcvers <> cabalvers <> ghcupvers) where - strayGHCs :: (MonadThrow m, MonadLogger m, MonadIO m) + strayGHCs :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m) => Map.Map Version [Tag] -> m [ListResult] strayGHCs avTools = do @@ -504,7 +506,7 @@ listVersions av lt criteria pfreq = do Just _ -> pure Nothing Nothing -> do lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet Nothing - fromSrc <- liftIO $ ghcSrcInstalled tver + fromSrc <- ghcSrcInstalled tver pure $ Just $ ListResult { lTool = GHC , lVer = _tvVersion @@ -517,7 +519,7 @@ listVersions av lt criteria pfreq = do } Right tver@GHCTargetVersion{ .. } -> do lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet _tvTarget - fromSrc <- liftIO $ ghcSrcInstalled tver + fromSrc <- ghcSrcInstalled tver pure $ Just $ ListResult { lTool = GHC , lVer = _tvVersion @@ -534,7 +536,7 @@ listVersions av lt criteria pfreq = do pure Nothing -- NOTE: this are not cross ones, because no bindists - toListResult :: Tool -> (Version, [Tag]) -> IO ListResult + toListResult :: (MonadReader Settings m, MonadIO m, MonadCatch m) => Tool -> (Version, [Tag]) -> m ListResult toListResult t (v, tags) = case t of GHC -> do let lNoBindist = isLeft $ getDownloadInfo GHC v pfreq av @@ -587,12 +589,12 @@ listVersions av lt criteria pfreq = do -- This may leave GHCup without a "set" version. -- Will try to fix the ghc-x.y symlink after removal (e.g. to an -- older version). -rmGHCVer :: (MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) +rmGHCVer :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) => GHCTargetVersion -> Excepts '[NotInstalled] m () rmGHCVer ver = do - isSetGHC <- fmap (maybe False (== ver)) $ ghcSet (_tvTarget ver) - dir <- liftIO $ ghcupGHCDir ver + isSetGHC <- lift $ fmap (maybe False (== ver)) $ ghcSet (_tvTarget ver) + dir <- lift $ ghcupGHCDir ver let d' = toFilePath dir exists <- liftIO $ doesDirectoryExist dir @@ -615,36 +617,38 @@ rmGHCVer ver = do lift $ rmMajorSymlinks ver -- then fix them (e.g. with an earlier version) (mj, mi) <- getMajorMinorV (_tvVersion ver) - getGHCForMajor mj mi (_tvTarget ver) >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) + lift (getGHCForMajor mj mi (_tvTarget ver)) >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) + + Settings {dirs = Dirs {..}} <- lift ask liftIO - $ ghcupBaseDir - >>= hideError doesNotExistErrorType - . deleteFile - . ( [rel|share|]) + $ hideError doesNotExistErrorType + $ deleteFile + $ (baseDir [rel|share|]) else throwE (NotInstalled GHC (ver ^. tvVersion % to prettyVer)) -- | Delete a cabal version. Will try to fix the @cabal@ symlink -- after removal (e.g. setting it to an older version). -rmCabalVer :: (MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) +rmCabalVer :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m, MonadCatch m) => Version -> Excepts '[NotInstalled] m () rmCabalVer ver = do - whenM (fmap not $ liftIO $ cabalInstalled ver) $ throwE (NotInstalled Cabal (prettyVer ver)) + whenM (lift $ fmap not $ cabalInstalled ver) $ throwE (NotInstalled Cabal (prettyVer ver)) - cSet <- liftIO cabalSet + cSet <- lift $ cabalSet + + Settings {dirs = Dirs {..}} <- lift ask - bindir <- liftIO ghcupBinDir cabalFile <- lift $ parseRel ("cabal-" <> verToBS ver) - liftIO $ hideError doesNotExistErrorType $ deleteFile (bindir cabalFile) + liftIO $ hideError doesNotExistErrorType $ deleteFile (binDir cabalFile) when (maybe False (== ver) cSet) $ do - cVers <- liftIO $ fmap rights $ getInstalledCabals + cVers <- lift $ fmap rights $ getInstalledCabals case headMay . reverse . sort $ cVers of Just latestver -> setCabal latestver Nothing -> liftIO $ hideError doesNotExistErrorType $ deleteFile - (bindir [rel|cabal|]) + (binDir [rel|cabal|]) @@ -653,18 +657,19 @@ rmCabalVer ver = do ------------------ -getDebugInfo :: (MonadLogger m, MonadCatch m, MonadIO m) +getDebugInfo :: (MonadReader Settings m, MonadLogger m, MonadCatch m, MonadIO m) => Excepts '[NoCompatiblePlatform , NoCompatibleArch , DistroNotFound] m DebugInfo getDebugInfo = do - diBaseDir <- liftIO $ ghcupBaseDir - diBinDir <- liftIO $ ghcupBinDir - diGHCDir <- liftIO $ ghcupGHCBaseDir - diCacheDir <- liftIO $ ghcupCacheDir - diArch <- lE getArchitecture - diPlatform <- liftE $ getPlatform + Settings {dirs = Dirs {..}} <- lift ask + let diBaseDir = baseDir + let diBinDir = binDir + diGHCDir <- lift ghcupGHCBaseDir + let diCacheDir = cacheDir + diArch <- lE getArchitecture + diPlatform <- liftE $ getPlatform pure $ DebugInfo { .. } @@ -711,7 +716,7 @@ compileGHC :: ( MonadMask m () compileGHC dls tver bstrap jobs mbuildConfig patchdir aargs PlatformRequest {..} = do lift $ $(logDebug) [i|Requested to compile: #{tver} with #{bstrap}|] - whenM (liftIO $ ghcInstalled tver) + whenM (lift $ ghcInstalled tver) (throwE $ AlreadyInstalled GHC (tver ^. tvVersion)) -- download source tarball @@ -729,7 +734,7 @@ compileGHC dls tver bstrap jobs mbuildConfig patchdir aargs PlatformRequest {..} Right g -> pure $ Right g Left bver -> Left <$> parseRel ("ghc-" <> verToBS bver) let workdir = maybe id (flip ()) (view dlSubdir dlInfo) $ tmpUnpack - ghcdir <- liftIO $ ghcupGHCDir tver + ghcdir <- lift $ ghcupGHCDir tver liftE $ runBuildAction tmpUnpack @@ -892,14 +897,14 @@ compileCabal :: ( MonadReader Settings m compileCabal dls tver bghc jobs patchdir PlatformRequest{..} = do lift $ $(logDebug) [i|Requested to compile: #{tver} with ghc-#{bghc}|] - bindir <- liftIO ghcupBinDir + Settings {dirs = Dirs {..}} <- lift ask whenM - (liftIO $ cabalInstalled tver >>= \a -> + (lift (cabalInstalled tver) >>= \a -> liftIO $ handleIO (\_ -> pure False) $ fmap (\x -> a && isSymbolicLink x) -- ignore when the installation is a legacy cabal (binary, not symlink) - $ getSymbolicLinkStatus (toFilePath (bindir [rel|cabal|])) + $ getSymbolicLinkStatus (toFilePath (binDir [rel|cabal|])) ) $ (throwE $ AlreadyInstalled Cabal tver) @@ -919,11 +924,11 @@ compileCabal dls tver bghc jobs patchdir PlatformRequest{..} = do destFileName <- lift $ parseRel ("cabal-" <> verToBS tver) handleIO (throwE . CopyError . show) $ liftIO $ copyFile cbin - (bindir destFileName) + (binDir destFileName) Overwrite -- create symlink if this is the latest version - cVers <- liftIO $ fmap rights $ getInstalledCabals + cVers <- lift $ fmap rights $ getInstalledCabals let lInstCabal = headMay . reverse . sort $ cVers when (maybe True (tver >=) lInstCabal) $ liftE $ setCabal tver @@ -1004,6 +1009,7 @@ upgradeGHCup :: ( MonadMask m m Version upgradeGHCup dls mtarget force pfreq = do + Settings {dirs = Dirs {..}} <- lift ask lift $ $(logInfo) [i|Upgrading GHCup...|] let latestVer = fromJust $ getLatest dls GHCup when (not force && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate @@ -1016,7 +1022,6 @@ upgradeGHCup dls mtarget force pfreq = do `unionFileModes` ownerExecuteMode `unionFileModes` groupExecuteMode `unionFileModes` otherExecuteMode - binDir <- liftIO $ ghcupBinDir let fullDest = fromMaybe (binDir fn) mtarget liftIO $ hideError NoSuchThing $ deleteFile fullDest handleIO (throwE . CopyError . show) $ liftIO $ copyFile p @@ -1034,7 +1039,7 @@ upgradeGHCup dls mtarget force pfreq = do -- | Creates @ghc-x.y.z@ and @ghc-x.y@ symlinks. This is used for -- both installing from source and bindist. -postGHCInstall :: (MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) +postGHCInstall :: (MonadReader Settings m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) => GHCTargetVersion -> Excepts '[NotInstalled] m () postGHCInstall ver@GHCTargetVersion{..} = do @@ -1043,4 +1048,4 @@ postGHCInstall ver@GHCTargetVersion{..} = do -- Create ghc-x.y symlinks. This may not be the current -- version, create it regardless. (mj, mi) <- getMajorMinorV _tvVersion - getGHCForMajor mj mi _tvTarget >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) + lift (getGHCForMajor mj mi _tvTarget) >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) diff --git a/lib/GHCup/Download.hs b/lib/GHCup/Download.hs index 45ff181..ea59551 100644 --- a/lib/GHCup/Download.hs +++ b/lib/GHCup/Download.hs @@ -133,10 +133,10 @@ getDownloadsF urlSource = do (OwnSpec _) -> liftE $ getDownloads urlSource where readFromCache = do + Settings {dirs = Dirs {..}} <- lift ask lift $ $(logWarn) [i|Could not get download info, trying cached version (this may not be recent!)|] let path = view pathL' ghcupURL - cacheDir <- liftIO $ ghcupCacheDir yaml_file <- (cacheDir ) <$> urlBaseName path bs <- handleIO' NoSuchThing @@ -200,8 +200,8 @@ getDownloads urlSource = do m1 L.ByteString smartDl uri' = do + Settings {dirs = Dirs {..}} <- lift ask let path = view pathL' uri' - cacheDir <- liftIO $ ghcupCacheDir json_file <- (cacheDir ) <$> urlBaseName path e <- liftIO $ doesFileExist json_file if e @@ -392,15 +392,15 @@ downloadCached dli mfn = do cache <- lift getCache case cache of True -> do - cachedir <- liftIO $ ghcupCacheDir + Settings {dirs = Dirs {..}} <- lift ask fn <- maybe (urlBaseName $ view (dlUri % pathL') dli) pure mfn - let cachfile = cachedir fn + let cachfile = cacheDir fn fileExists <- liftIO $ doesFileExist cachfile if | fileExists -> do liftE $ checkDigest dli cachfile pure $ cachfile - | otherwise -> liftE $ download dli cachedir mfn + | otherwise -> liftE $ download dli cacheDir mfn False -> do tmp <- lift withGHCupTmpDir liftE $ download dli tmp mfn diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs index 0a49cfc..2e8374a 100644 --- a/lib/GHCup/Types.hs +++ b/lib/GHCup/Types.hs @@ -158,14 +158,14 @@ data URLSource = GHCupURL data Settings = Settings - { -- * set by user + { -- set by user cache :: Bool , noVerify :: Bool , keepDirs :: KeepDirs , downloader :: Downloader , verbose :: Bool - -- * set on app start + -- set on app start , dirs :: Dirs } deriving Show diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index 8c02886..445b187 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -97,24 +97,24 @@ import qualified Text.Megaparsec as MP -- | The symlink destination of a ghc tool. -ghcLinkDestination :: (MonadThrow m, MonadIO m) +ghcLinkDestination :: (MonadReader Settings m, MonadThrow m, MonadIO m) => ByteString -- ^ the tool, such as 'ghc', 'haddock' etc. -> GHCTargetVersion -> m ByteString ghcLinkDestination tool ver = do + Settings {dirs = Dirs {..}} <- ask t <- parseRel tool - bin <- liftIO ghcupBinDir - ghcd <- liftIO $ ghcupGHCDir ver - pure (relativeSymlink bin (ghcd [rel|bin|] t)) + ghcd <- ghcupGHCDir ver + pure (relativeSymlink binDir (ghcd [rel|bin|] t)) -- | Removes the minor GHC symlinks, e.g. ghc-8.6.5. -rmMinorSymlinks :: (MonadIO m, MonadLogger m) => GHCTargetVersion -> m () +rmMinorSymlinks :: (MonadReader Settings m, MonadIO m, MonadLogger m) => GHCTargetVersion -> m () rmMinorSymlinks GHCTargetVersion {..} = do - bindir <- liftIO $ ghcupBinDir + Settings {dirs = Dirs {..}} <- ask files <- liftIO $ findFiles' - bindir + binDir ( maybe mempty (\x -> MP.chunk (x <> "-")) _tvTarget *> parseUntil1 (MP.chunk $ prettyVer _tvVersion) *> (MP.chunk $ prettyVer _tvVersion) @@ -122,42 +122,41 @@ rmMinorSymlinks GHCTargetVersion {..} = do ) forM_ files $ \f -> do - let fullF = (bindir f) + let fullF = (binDir f) $(logDebug) [i|rm -f #{toFilePath fullF}|] liftIO $ hideError doesNotExistErrorType $ deleteFile fullF -- | Removes the set ghc version for the given target, if any. -rmPlain :: (MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) +rmPlain :: (MonadReader Settings m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) => Maybe Text -- ^ target -> Excepts '[NotInstalled] m () rmPlain target = do - mtv <- ghcSet target + Settings {dirs = Dirs {..}} <- lift ask + mtv <- lift $ ghcSet target forM_ mtv $ \tv -> do files <- liftE $ ghcToolFiles tv - bindir <- liftIO $ ghcupBinDir forM_ files $ \f -> do - let fullF = (bindir f) + let fullF = (binDir f) lift $ $(logDebug) [i|rm -f #{toFilePath fullF}|] liftIO $ hideError doesNotExistErrorType $ deleteFile fullF -- old ghcup - let hdc_file = (bindir [rel|haddock-ghc|]) + let hdc_file = (binDir [rel|haddock-ghc|]) lift $ $(logDebug) [i|rm -f #{toFilePath hdc_file}|] liftIO $ hideError doesNotExistErrorType $ deleteFile hdc_file -- | Remove the major GHC symlink, e.g. ghc-8.6. -rmMajorSymlinks :: (MonadThrow m, MonadLogger m, MonadIO m) +rmMajorSymlinks :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m) => GHCTargetVersion -> m () rmMajorSymlinks GHCTargetVersion {..} = do + Settings {dirs = Dirs {..}} <- ask (mj, mi) <- getMajorMinorV _tvVersion let v' = intToText mj <> "." <> intToText mi - bindir <- liftIO ghcupBinDir - files <- liftIO $ findFiles' - bindir + binDir ( maybe mempty (\x -> MP.chunk (x <> "-")) _tvTarget *> parseUntil1 (MP.chunk v') *> MP.chunk v' @@ -165,7 +164,7 @@ rmMajorSymlinks GHCTargetVersion {..} = do ) forM_ files $ \f -> do - let fullF = (bindir f) + let fullF = (binDir f) $(logDebug) [i|rm -f #{toFilePath fullF}|] liftIO $ hideError doesNotExistErrorType $ deleteFile fullF @@ -178,27 +177,28 @@ rmMajorSymlinks GHCTargetVersion {..} = do -- | Whethe the given GHC versin is installed. -ghcInstalled :: GHCTargetVersion -> IO Bool +ghcInstalled :: (MonadIO m, MonadReader Settings m, MonadThrow m) => GHCTargetVersion -> m Bool ghcInstalled ver = do ghcdir <- ghcupGHCDir ver - doesDirectoryExist ghcdir + liftIO $ doesDirectoryExist ghcdir -- | Whether the given GHC version is installed from source. -ghcSrcInstalled :: GHCTargetVersion -> IO Bool +ghcSrcInstalled :: (MonadIO m, MonadReader Settings m, MonadThrow m) => GHCTargetVersion -> m Bool ghcSrcInstalled ver = do ghcdir <- ghcupGHCDir ver - doesFileExist (ghcdir ghcUpSrcBuiltFile) + liftIO $ doesFileExist (ghcdir ghcUpSrcBuiltFile) -- | Whether the given GHC version is set as the current. -ghcSet :: (MonadThrow m, MonadIO m) +ghcSet :: (MonadReader Settings m, MonadThrow m, MonadIO m) => Maybe Text -- ^ the target of the GHC version, if any -- (e.g. armv7-unknown-linux-gnueabihf) -> m (Maybe GHCTargetVersion) ghcSet mtarget = do + Settings {dirs = Dirs {..}} <- ask ghc <- parseRel $ E.encodeUtf8 (maybe "ghc" (<> "-ghc") mtarget) - ghcBin <- ( ghc) <$> liftIO ghcupBinDir + let ghcBin = binDir ghc -- link destination is of the form ../ghc//bin/ghc -- for old ghcup, it is ../ghc//bin/ghc- @@ -229,9 +229,9 @@ ghcLinkVersion bs = do -- | Get all installed GHCs by reading ~/.ghcup/ghc/. -- If a dir cannot be parsed, returns left. -getInstalledGHCs :: MonadIO m => m [Either (Path Rel) GHCTargetVersion] +getInstalledGHCs :: (MonadReader Settings m, MonadIO m) => m [Either (Path Rel) GHCTargetVersion] getInstalledGHCs = do - ghcdir <- liftIO $ ghcupGHCBaseDir + ghcdir <- ghcupGHCBaseDir fs <- liftIO $ hideErrorDef [NoSuchThing] [] $ getDirsFiles' ghcdir forM fs $ \f -> case parseGHCupGHCDir f of Right r -> pure $ Right r @@ -239,11 +239,12 @@ getInstalledGHCs = do -- | Get all installed cabals, by matching on @~\/.ghcup\/bin/cabal-*@. -getInstalledCabals :: IO [Either (Path Rel) Version] +getInstalledCabals :: (MonadReader Settings m, MonadIO m, MonadCatch m) + => m [Either (Path Rel) Version] getInstalledCabals = do - bindir <- liftIO $ ghcupBinDir + Settings {dirs = Dirs {..}} <- ask bins <- liftIO $ handleIO (\_ -> pure []) $ findFiles - bindir + binDir (makeRegexOpts compExtended execBlank ([s|^cabal-.*$|] :: ByteString)) vs <- forM bins $ \f -> case fmap version (fmap decUTF8Safe . B.stripPrefix "cabal-" . toFilePath $ f) of Just (Right r) -> pure $ Right r @@ -254,16 +255,17 @@ getInstalledCabals = do -- | Whether the given cabal version is installed. -cabalInstalled :: Version -> IO Bool +cabalInstalled :: (MonadIO m, MonadReader Settings m, MonadCatch m) => Version -> m Bool cabalInstalled ver = do vers <- fmap rights $ getInstalledCabals pure $ elem ver $ vers -- Return the currently set cabal version, if any. -cabalSet :: (MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version) +cabalSet :: (MonadReader Settings m, MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version) cabalSet = do - cabalbin <- ( [rel|cabal|]) <$> liftIO ghcupBinDir + Settings {dirs = Dirs {..}} <- ask + let cabalbin = binDir [rel|cabal|] b <- handleIO (\_ -> pure False) $ fmap (== SymbolicLink) $ liftIO $ getFileType cabalbin if | b -> do @@ -319,7 +321,7 @@ matchMajor v' major' minor' = case getMajorMinorV v' of -- | Get the latest installed full GHC version that satisfies X.Y. -- This reads `ghcupGHCBaseDir`. -getGHCForMajor :: (MonadIO m, MonadThrow m) +getGHCForMajor :: (MonadReader Settings m, MonadIO m, MonadThrow m) => Int -- ^ major version component -> Int -- ^ minor version component -> Maybe Text -- ^ the target triple @@ -463,11 +465,11 @@ urlBaseName = parseRel . snd . B.breakEnd (== _slash) . urlDecode False -- Returns unversioned relative files, e.g.: -- -- - @["hsc2hs","haddock","hpc","runhaskell","ghc","ghc-pkg","ghci","runghc","hp2ps"]@ -ghcToolFiles :: (MonadThrow m, MonadFail m, MonadIO m) +ghcToolFiles :: (MonadReader Settings m, MonadThrow m, MonadFail m, MonadIO m) => GHCTargetVersion -> Excepts '[NotInstalled] m [Path Rel] ghcToolFiles ver = do - ghcdir <- liftIO $ ghcupGHCDir ver + ghcdir <- lift $ ghcupGHCDir ver let bindir = ghcdir [rel|bin|] -- fail if ghc is not installed diff --git a/lib/GHCup/Utils/File.hs b/lib/GHCup/Utils/File.hs index be16bd5..8ddae18 100644 --- a/lib/GHCup/Utils/File.hs +++ b/lib/GHCup/Utils/File.hs @@ -17,7 +17,6 @@ Some of these functions use sophisticated logging. -} module GHCup.Utils.File where -import GHCup.Utils.Dirs import GHCup.Utils.Prelude import GHCup.Types @@ -123,9 +122,8 @@ execLogged :: (MonadReader Settings m, MonadIO m, MonadThrow m) -> Maybe [(ByteString, ByteString)] -- ^ optional environment -> m (Either ProcessError ()) execLogged exe spath args lfile chdir env = do - Settings {..} <- ask - ldir <- liftIO ghcupLogsDir - logfile <- (ldir ) <$> parseRel (toFilePath lfile <> ".log") + Settings {dirs = Dirs {..}, ..} <- ask + logfile <- (logsDir ) <$> parseRel (toFilePath lfile <> ".log") liftIO $ bracket (createFile (toFilePath logfile) newFilePerms) closeFd (action verbose) diff --git a/lib/GHCup/Utils/Logger.hs b/lib/GHCup/Utils/Logger.hs index 2e58cc1..0ff0004 100644 --- a/lib/GHCup/Utils/Logger.hs +++ b/lib/GHCup/Utils/Logger.hs @@ -1,4 +1,5 @@ {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE FlexibleContexts #-} {-| Module : GHCup.Utils.Logger @@ -13,9 +14,11 @@ Here we define our main logger. -} module GHCup.Utils.Logger where -import GHCup.Utils +import GHCup.Types import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Reader import Control.Monad.Logger import HPath import HPath.IO @@ -61,11 +64,12 @@ myLoggerT LoggerConfig {..} loggingt = runLoggingT loggingt mylogger rawOutter outr -initGHCupFileLogging :: Path Rel -> IO (Path Abs) +initGHCupFileLogging :: (MonadIO m, MonadReader Settings m) => Path Rel -> m (Path Abs) initGHCupFileLogging context = do - logs <- ghcupLogsDir - let logfile = logs context - createDirRecursive newDirPerms logs - hideError doesNotExistErrorType $ deleteFile logfile - createRegularFile newFilePerms logfile - pure logfile + Settings {dirs = Dirs {..}} <- ask + let logfile = logsDir context + liftIO $ do + createDirRecursive newDirPerms logsDir + hideError doesNotExistErrorType $ deleteFile logfile + createRegularFile newFilePerms logfile + pure logfile From 5c45884f5ff7d1a4f2f100e96ef0fad6e7ad2a72 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Thu, 6 Aug 2020 13:28:20 +0200 Subject: [PATCH 11/19] Allow to specify regex for subdir --- .gitlab-ci.yml | 2 +- app/ghcup/BrickMain.hs | 4 +- app/ghcup/Main.hs | 11 ++++- ghcup.cabal | 4 ++ lib/GHCup.hs | 95 +++++++++++++++++++++++++++++------------ lib/GHCup/Errors.hs | 3 ++ lib/GHCup/Types.hs | 8 +++- lib/GHCup/Types/JSON.hs | 4 ++ lib/GHCup/Utils.hs | 24 +++++++++++ lib/GHCup/Utils/Dirs.hs | 1 - 10 files changed, 123 insertions(+), 33 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5495d6d..dcb7c51 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,7 +57,7 @@ variables: script: - ./.gitlab/script/ghcup_version.sh variables: - JSON_VERSION: "0.0.2" + JSON_VERSION: "0.0.3" .test_ghcup_version:linux: extends: diff --git a/app/ghcup/BrickMain.hs b/app/ghcup/BrickMain.hs index f563ced..b843976 100644 --- a/app/ghcup/BrickMain.hs +++ b/app/ghcup/BrickMain.hs @@ -214,7 +214,9 @@ install' AppState {..} (_, ListResult {..}) = do , TagNotFound , DigestError , DownloadFailed - , NoUpdate] + , NoUpdate + , TarDirDoesNotExist + ] (run $ do case lTool of diff --git a/app/ghcup/Main.hs b/app/ghcup/Main.hs index 7464ae5..9116ae8 100644 --- a/app/ghcup/Main.hs +++ b/app/ghcup/Main.hs @@ -404,7 +404,11 @@ installParser = installGHCFooter = [s|Discussion: Installs the specified GHC version (or a recommended default one) into a self-contained "~/.ghcup/ghc/" directory - and symlinks the ghc binaries to "~/.ghcup/bin/-".|] + and symlinks the ghc binaries to "~/.ghcup/bin/-". + +Examples: + # install GHC head + ghcup -n install ghc -u '{"dlHash": "", "dlSubdir": { "RegexDir": "ghc-.*"}, "dlUri": "https://gitlab.haskell.org/api/v4/projects/1/jobs/artifacts/master/raw/ghc-x86_64-fedora27-linux.tar.xz?job=validate-x86_64-linux-fedora27" }' head|] installOpts :: Parser InstallOptions @@ -428,7 +432,7 @@ installOpts = <> long "url" <> metavar "BINDIST_URL" <> help - "Provide DownloadInfo as json string, e.g.: '{ \"dlHash\": \"\", \"dlSubdir\": \"ghc-\", \"dlUri\": \"\" }'" + "Provide DownloadInfo as json string, e.g.: '{ \"dlHash\": \"\", \"dlSubdir\": { \"RegexDir\": \"ghc-.*\"}, \"dlUri\": \"\" }'" ) ) ) @@ -940,6 +944,7 @@ Report bugs at |] , TagNotFound , DigestError , DownloadFailed + , TarDirDoesNotExist ] let @@ -986,6 +991,7 @@ Report bugs at |] , NotFoundInPATH , PatchFailed , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -1005,6 +1011,7 @@ Report bugs at |] , NotInstalled , PatchFailed , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif diff --git a/ghcup.cabal b/ghcup.cabal index cca484f..4f9c0cd 100644 --- a/ghcup.cabal +++ b/ghcup.cabal @@ -153,6 +153,9 @@ common safe common safe-exceptions build-depends: safe-exceptions >=0.1 +common split + build-depends: split >=0.2.3.4 + common streamly build-depends: streamly >=0.7.1 @@ -276,6 +279,7 @@ library , resourcet , safe , safe-exceptions + , split , streamly , streamly-posix , streamly-bytestring diff --git a/lib/GHCup.hs b/lib/GHCup.hs index 84b993e..0271bc2 100644 --- a/lib/GHCup.hs +++ b/lib/GHCup.hs @@ -112,6 +112,7 @@ installGHCBindist :: ( MonadFail m , NoDownload , NotInstalled , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -136,7 +137,7 @@ installGHCBindist dlinfo ver (PlatformRequest {..}) = do ghcdir <- lift $ ghcupGHCDir tver -- the subdir of the archive where we do the work - let workdir = maybe tmpUnpack (tmpUnpack ) (view dlSubdir dlinfo) + workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo) liftE $ runBuildAction tmpUnpack (Just ghcdir) (installGHC' workdir ghcdir) @@ -189,6 +190,7 @@ installGHCBin :: ( MonadFail m , NoDownload , NotInstalled , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -221,6 +223,7 @@ installCabalBindist :: ( MonadMask m , NoDownload , NotInstalled , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -250,7 +253,7 @@ installCabalBindist dlinfo ver (PlatformRequest {..}) = do void $ liftIO $ darwinNotarization _rPlatform tmpUnpack -- the subdir of the archive where we do the work - let workdir = maybe tmpUnpack (tmpUnpack ) (view dlSubdir dlinfo) + workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlinfo) liftE $ installCabal' workdir binDir @@ -300,6 +303,7 @@ installCabalBin :: ( MonadMask m , NoDownload , NotInstalled , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -328,16 +332,22 @@ installCabalBin bDls ver pfreq = do -- -- Additionally creates a @~\/.ghcup\/share -> ~\/.ghcup\/ghc\/\\/share symlink@ -- for 'SetGHCOnly' constructor. -setGHC :: (MonadReader Settings m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) +setGHC :: ( MonadReader Settings m + , MonadLogger m + , MonadThrow m + , MonadFail m + , MonadIO m + , MonadCatch m + ) => GHCTargetVersion -> SetGHC -> Excepts '[NotInstalled] m GHCTargetVersion setGHC ver sghc = do let verBS = verToBS (_tvVersion ver) - ghcdir <- lift $ ghcupGHCDir ver + ghcdir <- lift $ ghcupGHCDir ver -- symlink destination - Settings {dirs = Dirs {..}} <- lift ask + Settings { dirs = Dirs {..} } <- lift ask liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms binDir -- first delete the old symlinks (this fixes compatibility issues @@ -350,19 +360,26 @@ setGHC ver sghc = do -- for ghc tools (ghc, ghci, haddock, ...) verfiles <- ghcToolFiles ver forM_ verfiles $ \file -> do - targetFile <- case sghc of - SetGHCOnly -> pure file + mTargetFile <- case sghc of + SetGHCOnly -> pure $ Just file SetGHC_XY -> do - major' <- (\(mj, mi) -> E.encodeUtf8 $ intToText mj <> "." <> intToText mi) - <$> getMajorMinorV (_tvVersion ver) - parseRel (toFilePath file <> B.singleton _hyphen <> major') - SetGHC_XYZ -> parseRel (toFilePath file <> B.singleton _hyphen <> verBS) + v' <- + handle + (\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing) + $ fmap Just + $ getMajorMinorV (_tvVersion ver) + forM v' $ \(mj, mi) -> + let major' = E.encodeUtf8 $ intToText mj <> "." <> intToText mi + in parseRel (toFilePath file <> B.singleton _hyphen <> major') + SetGHC_XYZ -> + fmap Just $ parseRel (toFilePath file <> B.singleton _hyphen <> verBS) -- create symlink - let fullF = binDir targetFile - destL <- lift $ ghcLinkDestination (toFilePath file) ver - lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|] - liftIO $ createSymlink fullF destL + forM mTargetFile $ \targetFile -> do + let fullF = binDir targetFile + destL <- lift $ ghcLinkDestination (toFilePath file) ver + lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|] + liftIO $ createSymlink fullF destL -- create symlink for share dir when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verBS @@ -376,7 +393,7 @@ setGHC ver sghc = do -> ByteString -> m () symlinkShareDir ghcdir verBS = do - Settings {dirs = Dirs {..}} <- ask + Settings { dirs = Dirs {..} } <- ask let destdir = baseDir case sghc of SetGHCOnly -> do @@ -589,7 +606,13 @@ listVersions av lt criteria pfreq = do -- This may leave GHCup without a "set" version. -- Will try to fix the ghc-x.y symlink after removal (e.g. to an -- older version). -rmGHCVer :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) +rmGHCVer :: ( MonadReader Settings m + , MonadThrow m + , MonadLogger m + , MonadIO m + , MonadFail m + , MonadCatch m + ) => GHCTargetVersion -> Excepts '[NotInstalled] m () rmGHCVer ver = do @@ -614,12 +637,17 @@ rmGHCVer ver = do lift $ $(logInfo) [i|Removing/rewiring ghc-x.y symlinks|] -- first remove - lift $ rmMajorSymlinks ver + handle (\(_ :: ParseError) -> pure ()) $ lift $ rmMajorSymlinks ver -- then fix them (e.g. with an earlier version) - (mj, mi) <- getMajorMinorV (_tvVersion ver) - lift (getGHCForMajor mj mi (_tvTarget ver)) >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) + v' <- + handle + (\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing) + $ fmap Just + $ getMajorMinorV (_tvVersion ver) + forM_ v' $ \(mj, mi) -> lift (getGHCForMajor mj mi (_tvTarget ver)) + >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) - Settings {dirs = Dirs {..}} <- lift ask + Settings { dirs = Dirs {..} } <- lift ask liftIO $ hideError doesNotExistErrorType @@ -708,6 +736,7 @@ compileGHC :: ( MonadMask m , NotFoundInPATH , PatchFailed , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -733,7 +762,7 @@ compileGHC dls tver bstrap jobs mbuildConfig patchdir aargs PlatformRequest {..} bghc <- case bstrap of Right g -> pure $ Right g Left bver -> Left <$> parseRel ("ghc-" <> verToBS bver) - let workdir = maybe id (flip ()) (view dlSubdir dlInfo) $ tmpUnpack + workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlInfo) ghcdir <- lift $ ghcupGHCDir tver liftE $ runBuildAction @@ -888,6 +917,7 @@ compileCabal :: ( MonadReader Settings m , NotInstalled , PatchFailed , UnknownArchive + , TarDirDoesNotExist #if !defined(TAR) , ArchiveResult #endif @@ -917,7 +947,7 @@ compileCabal dls tver bghc jobs patchdir PlatformRequest{..} = do liftE $ unpackToDir tmpUnpack dl void $ liftIO $ darwinNotarization _rPlatform tmpUnpack - let workdir = maybe id (flip ()) (view dlSubdir dlInfo) $ tmpUnpack + workdir <- maybe (pure tmpUnpack) (liftE . intoSubdir tmpUnpack) (view dlSubdir dlInfo) cbin <- liftE $ runBuildAction tmpUnpack Nothing (compile workdir) @@ -1039,13 +1069,24 @@ upgradeGHCup dls mtarget force pfreq = do -- | Creates @ghc-x.y.z@ and @ghc-x.y@ symlinks. This is used for -- both installing from source and bindist. -postGHCInstall :: (MonadReader Settings m, MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) +postGHCInstall :: ( MonadReader Settings m + , MonadLogger m + , MonadThrow m + , MonadFail m + , MonadIO m + , MonadCatch m + ) => GHCTargetVersion -> Excepts '[NotInstalled] m () -postGHCInstall ver@GHCTargetVersion{..} = do +postGHCInstall ver@GHCTargetVersion {..} = do void $ liftE $ setGHC ver SetGHC_XYZ -- Create ghc-x.y symlinks. This may not be the current -- version, create it regardless. - (mj, mi) <- getMajorMinorV _tvVersion - lift (getGHCForMajor mj mi _tvTarget) >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) + v' <- + handle (\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing) + $ fmap Just + $ getMajorMinorV _tvVersion + forM_ v' $ \(mj, mi) -> lift (getGHCForMajor mj mi _tvTarget) + >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) + diff --git a/lib/GHCup/Errors.hs b/lib/GHCup/Errors.hs index 5f09bfb..fdf54b0 100644 --- a/lib/GHCup/Errors.hs +++ b/lib/GHCup/Errors.hs @@ -89,6 +89,9 @@ data JSONError = JSONDecodeError String data FileDoesNotExistError = FileDoesNotExistError ByteString deriving Show +data TarDirDoesNotExist = TarDirDoesNotExist TarDir + deriving Show + -- | File digest verification failed. data DigestError = DigestError Text Text deriving Show diff --git a/lib/GHCup/Types.hs b/lib/GHCup/Types.hs index 2e8374a..acdd482 100644 --- a/lib/GHCup/Types.hs +++ b/lib/GHCup/Types.hs @@ -137,7 +137,7 @@ data LinuxDistro = Debian -- to download, extract and install a tool. data DownloadInfo = DownloadInfo { _dlUri :: URI - , _dlSubdir :: Maybe (Path Rel) + , _dlSubdir :: Maybe TarDir , _dlHash :: Text } deriving (Eq, Show) @@ -150,6 +150,12 @@ data DownloadInfo = DownloadInfo -------------- +-- | How to descend into a tar archive. +data TarDir = RealDir (Path Rel) + | RegexDir String -- ^ will be compiled to regex, the first match will "win" + deriving (Eq, Show) + + -- | Where to fetch GHCupDownloads from. data URLSource = GHCupURL | OwnSource URI diff --git a/lib/GHCup/Types/JSON.hs b/lib/GHCup/Types/JSON.hs index 1f27bee..1320c5e 100644 --- a/lib/GHCup/Types/JSON.hs +++ b/lib/GHCup/Types/JSON.hs @@ -193,3 +193,7 @@ instance FromJSON (Path Rel) where case parseRel d of Right x -> pure x Left e -> fail $ "Failure in HPath Rel (FromJSON)" <> show e + + +deriveJSON defaultOptions{ sumEncoding = ObjectWithSingleField } ''TarDir + diff --git a/lib/GHCup/Utils.hs b/lib/GHCup/Utils.hs index 445b187..e06e876 100644 --- a/lib/GHCup/Utils.hs +++ b/lib/GHCup/Utils.hs @@ -48,7 +48,9 @@ import Control.Monad.Logger import Control.Monad.Reader import Data.ByteString ( ByteString ) import Data.Either +import Data.Foldable import Data.List +import Data.List.Split import Data.Maybe import Data.String.Interpolate import Data.Text ( Text ) @@ -403,6 +405,28 @@ unpackToDir dest av = do | otherwise -> throwE $ UnknownArchive fn +intoSubdir :: (MonadLogger m, MonadIO m, MonadThrow m, MonadCatch m) + => Path Abs -- ^ unpacked tar dir + -> TarDir -- ^ how to descend + -> Excepts '[TarDirDoesNotExist] m (Path Abs) +intoSubdir bdir tardir = case tardir of + RealDir pr -> do + whenM (fmap not . liftIO . doesDirectoryExist $ (bdir pr)) + (throwE $ TarDirDoesNotExist tardir) + pure (bdir pr) + RegexDir r -> do + let rs = splitOn "/" r + foldlM + (\y x -> + (fmap sort . handleIO (\_ -> pure []) . liftIO . findFiles y . regex $ x) >>= \case + [] -> throwE $ TarDirDoesNotExist tardir + (p : _) -> pure (y p) + ) + bdir + rs + where regex = makeRegexOpts compIgnoreCase execBlank + + ------------ diff --git a/lib/GHCup/Utils/Dirs.hs b/lib/GHCup/Utils/Dirs.hs index 0e72cf8..2704e42 100644 --- a/lib/GHCup/Utils/Dirs.hs +++ b/lib/GHCup/Utils/Dirs.hs @@ -166,7 +166,6 @@ ghcupGHCDir :: (MonadReader Settings m, MonadThrow m) => GHCTargetVersion -> m (Path Abs) ghcupGHCDir ver = do - Settings {..} <- ask ghcbasedir <- ghcupGHCBaseDir verdir <- parseRel $ E.encodeUtf8 (prettyTVer ver) pure (ghcbasedir verdir) From d071a7e51ba35c612de67eaf10456210e9fab096 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Sun, 9 Aug 2020 10:33:32 +0200 Subject: [PATCH 12/19] Avoid duplicate edits to .bashrc etc --- bootstrap-haskell | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bootstrap-haskell b/bootstrap-haskell index 467a119..9afac86 100755 --- a/bootstrap-haskell +++ b/bootstrap-haskell @@ -232,11 +232,16 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then case $MY_SHELL in "") break ;; fish) - echo "set -q GHCUP_INSTALL_BASE_PREFIX[1]; or set GHCUP_INSTALL_BASE_PREFIX \$HOME" >> "${GHCUP_PROFILE_FILE}" - echo "test -f $GHCUP_DIR/env ; and set -gx PATH \$HOME/.cabal/bin $GHCUP_BIN/bin \$PATH" >> "${GHCUP_PROFILE_FILE}" + if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then + echo "# ghcup-env" >> "${GHCUP_PROFILE_FILE}" + echo "set -q GHCUP_INSTALL_BASE_PREFIX[1]; or set GHCUP_INSTALL_BASE_PREFIX \$HOME" >> "${GHCUP_PROFILE_FILE}" + echo "test -f $GHCUP_DIR/env ; and set -gx PATH \$HOME/.cabal/bin $GHCUP_BIN/bin \$PATH" >> "${GHCUP_PROFILE_FILE}" + fi break ;; *) - echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\"" >> "${GHCUP_PROFILE_FILE}" + if ! grep -q "ghcup-env" "${GHCUP_PROFILE_FILE}" ; then + echo "[ -f \"${GHCUP_DIR}/env\" ] && source \"${GHCUP_DIR}/env\" # ghcup-env" >> "${GHCUP_PROFILE_FILE}" + fi break ;; esac printf "\\033[0;35m%s\\033[0m\\n" "OK! ${GHCUP_PROFILE_FILE} has been modified. Restart your terminal for the changes to take effect," From 47142dd376338bbac997caf315a3446e4193d879 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Sun, 9 Aug 2020 11:21:49 +0200 Subject: [PATCH 13/19] Test on 32bit --- .gitlab-ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dcb7c51..9294a41 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -66,6 +66,13 @@ variables: before_script: - ./.gitlab/before_script/linux/install_deps.sh +.test_ghcup_version:linux32: + extends: + - .test_ghcup_version + - .alpine:32bit + before_script: + - ./.gitlab/before_script/linux/alpine/install_deps.sh + .test_ghcup_version:darwin: extends: - .test_ghcup_version @@ -107,6 +114,13 @@ test:linux:latest: CABAL_VERSION: "3.2.0.0" allow_failure: true +######## linux 32bit test ######## + +test:linux:recommended:32bit: + extends: .test_ghcup_version:linux32 + variables: + GHC_VERSION: "8.8.4" + CABAL_VERSION: "3.2.0.0" ######## darwin test ######## From 27b2d2ac7dcc6bf2ee6d4e422f0df8c86601eaa7 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Sun, 9 Aug 2020 11:22:12 +0200 Subject: [PATCH 14/19] Fix cabal.project for cabal-3.4 --- cabal.project | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 4cd11a6..8412ed7 100644 --- a/cabal.project +++ b/cabal.project @@ -19,6 +19,6 @@ package ghcup constraints: http-io-streams -brotli package libarchive - flags: static + flags: +static -allow-newer: base ghc-prim template-haskell +allow-newer: base, ghc-prim, template-haskell From dac64f5718561daf4f298f631097a7a0a02492b2 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Mon, 10 Aug 2020 22:22:48 +0200 Subject: [PATCH 15/19] Make TarDir backwardscompat --- ghcup-0.0.2.yaml | 1 + lib/GHCup/Types/JSON.hs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ghcup-0.0.2.yaml b/ghcup-0.0.2.yaml index cbf278a..873de2f 100644 --- a/ghcup-0.0.2.yaml +++ b/ghcup-0.0.2.yaml @@ -1,3 +1,4 @@ +# !!! if you use RegexDir, then the version must be bumped !!! --- toolRequirements: GHC: diff --git a/lib/GHCup/Types/JSON.hs b/lib/GHCup/Types/JSON.hs index 1320c5e..c271a08 100644 --- a/lib/GHCup/Types/JSON.hs +++ b/lib/GHCup/Types/JSON.hs @@ -24,6 +24,7 @@ module GHCup.Types.JSON where import GHCup.Types import GHCup.Utils.Prelude +import Control.Applicative ( (<|>) ) import Data.Aeson import Data.Aeson.TH import Data.Aeson.Types @@ -195,5 +196,16 @@ instance FromJSON (Path Rel) where Left e -> fail $ "Failure in HPath Rel (FromJSON)" <> show e -deriveJSON defaultOptions{ sumEncoding = ObjectWithSingleField } ''TarDir +instance ToJSON TarDir where + toJSON (RealDir p) = toJSON p + toJSON (RegexDir r) = object ["RegexDir" .= r] +instance FromJSON TarDir where + parseJSON v = realDir v <|> regexDir v + where + realDir = withText "TarDir" $ \t -> do + fp <- parseJSON (String t) + pure (RealDir fp) + regexDir = withObject "TarDir" $ \o -> do + r <- o .: "RegexDir" + pure $ RegexDir r From 71cb75c1702bf7c6a7178950c92a118d448370f0 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Mon, 10 Aug 2020 22:25:23 +0200 Subject: [PATCH 16/19] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 505a0dc..7e8c03c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ tags TAGS /tmp/ .entangled +release/ From f63b2bf7441dd6077b49b8d609d7f060a184f9c3 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 11 Aug 2020 11:48:42 +0200 Subject: [PATCH 17/19] Fix CI --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9294a41..990daf2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,7 +57,7 @@ variables: script: - ./.gitlab/script/ghcup_version.sh variables: - JSON_VERSION: "0.0.3" + JSON_VERSION: "0.0.2" .test_ghcup_version:linux: extends: From 6f1b8b4041f578429c26bd7c1f9086675c9f67c7 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 11 Aug 2020 12:12:15 +0200 Subject: [PATCH 18/19] Fix build on 32bit --- .gitlab-ci.yml | 3 +++ .gitlab/script/ghcup_version.sh | 12 +++++++++--- ghcup-0.0.2.yaml | 8 ++++++-- ghcup.cabal | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 990daf2..d5c46ac 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,7 @@ variables: - x86_64-linux variables: OS: "LINUX" + BIT: "64" .alpine:64bit: image: "alpine:edge" @@ -36,12 +37,14 @@ variables: - x86_64-darwin variables: OS: "DARWIN" + BIT: "64" .freebsd: tags: - x86_64-freebsd variables: OS: "FREEBSD" + BIT: "64" .root_cleanup: after_script: diff --git a/.gitlab/script/ghcup_version.sh b/.gitlab/script/ghcup_version.sh index 1c410a8..baf3c55 100755 --- a/.gitlab/script/ghcup_version.sh +++ b/.gitlab/script/ghcup_version.sh @@ -22,14 +22,20 @@ ecabal update if [ "${OS}" = "DARWIN" ] ; then ecabal build -w ghc-${GHC_VERSION} -ftui +elif [ "${OS}" = "LINUX" ] ; then + if [ "${BIT}" = "32" ] ; then + ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui -ftar + else + ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui + fi else ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui fi -ecabal haddock +ecabal haddock -w ghc-${GHC_VERSION} -ftar -cp "$(ecabal new-exec --enable-tests --verbose=0 --offline sh -- -c 'command -v ghcup')" . -cp "$(ecabal new-exec --enable-tests --verbose=0 --offline sh -- -c 'command -v ghcup-gen')" . +cp "$(ecabal new-exec -w ghc-${GHC_VERSION} --enable-tests --verbose=0 --offline sh -- -c 'command -v ghcup')" . +cp "$(ecabal new-exec -w ghc-${GHC_VERSION} --enable-tests --verbose=0 --offline sh -- -c 'command -v ghcup-gen')" . cp ./ghcup "$CI_PROJECT_DIR"/.local/bin/ghcup cp ./ghcup-gen "$CI_PROJECT_DIR"/.local/bin/ghcup-gen diff --git a/ghcup-0.0.2.yaml b/ghcup-0.0.2.yaml index 873de2f..82eb2ca 100644 --- a/ghcup-0.0.2.yaml +++ b/ghcup-0.0.2.yaml @@ -1305,7 +1305,7 @@ ghcupDownloads: viArch: A_64: Linux_UnknownLinux: - unknown_versioning: + unknown_versioning: &ghcup-64 dlUri: https://downloads.haskell.org/~ghcup/0.1.8/x86_64-linux-ghcup-0.1.8 dlHash: 7ffcd4c3de156e895b648c75a36c762be2a4932883f3cd598f7a483c97d4a8a9 Darwin: @@ -1316,8 +1316,12 @@ ghcupDownloads: unknown_versioning: dlUri: https://downloads.haskell.org/~ghcup/0.1.8/x86_64-portbld-freebsd-ghcup-0.1.8 dlHash: 442cdfe1b4525a327d9566e6270f909f7deba21c16dd4c7912537cf67e6cd521 + Linux_Alpine: + unknown_versioning: *ghcup-64 A_32: Linux_UnknownLinux: - unknown_versioning: + unknown_versioning: &ghcup-32 dlUri: https://downloads.haskell.org/~ghcup/0.1.8/i386-linux-ghcup-0.1.8 dlHash: 18ab162920cea662feae4b08f39d3879e9e416fde7b734afd8072c39d3c43cde + Linux_Alpine: + unknown_versioning: *ghcup-32 diff --git a/ghcup.cabal b/ghcup.cabal index 4f9c0cd..5464209 100644 --- a/ghcup.cabal +++ b/ghcup.cabal @@ -112,7 +112,7 @@ common io-streams build-depends: io-streams >=1.5 common libarchive - build-depends: libarchive >= 2.2.5.2 + build-depends: libarchive >= 2.2.5.0 common lzma build-depends: lzma >=0.0.0.3 From d2c5d4dfd941e66a2d0d518c8b2229a9c0a56cdd Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 11 Aug 2020 20:21:45 +0200 Subject: [PATCH 19/19] Test that we're not missing GHCup alpine --- app/ghcup-gen/Validate.hs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/ghcup-gen/Validate.hs b/app/ghcup-gen/Validate.hs index cec2130..9c4b7d0 100644 --- a/app/ghcup-gen/Validate.hs +++ b/app/ghcup-gen/Validate.hs @@ -9,6 +9,7 @@ import GHCup.Download import GHCup.Types import GHCup.Utils.Dirs import GHCup.Utils.Logger +import GHCup.Utils.Version.QQ import Control.Exception.Safe import Control.Monad @@ -89,6 +90,15 @@ validate dls = do when ((not $ any (== FreeBSD) pspecs) && arch == A_64) $ lift $ $(logWarn) [i|FreeBSD missing for #{t} #{v'} #{arch}|] + -- alpine needs to be set explicitly, because + -- we cannot assume that "Linux UnknownLinux" runs on Alpine + -- (although it could be static) + when (not $ any (== Linux Alpine) pspecs) $ + case t of + GHCup -> (lift $ $(logError) [i|Linux Alpine missing for #{t} #{v'} #{arch}|]) >> addError + Cabal | v > [vver|2.4.1.0|] -> (lift $ $(logError) [i|Linux Alpine missing for #{t} #{v'} #{arch}|]) >> addError + _ -> lift $ $(logWarn) [i|Linux Alpine missing for #{t} #{v'} #{arch}|] + checkUniqueTags tool = do let allTags = join $ M.elems $ availableToolVersions dls tool let nonUnique =