packages feed

ghcup-0.2.1.0: lib/GHCup/Command/Set.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}

module GHCup.Command.Set where

import GHCup.Errors
import GHCup.Legacy.Cabal
import GHCup.Legacy.HLS
import GHCup.Legacy.Stack
import GHCup.Legacy.Utils (rmPlainGHC, binarySymLinkDestination, ghcInternalBinDir, ghcToolFiles)
import GHCup.Prelude
import GHCup.Query.DB
import GHCup.Query.GHCupDirs
import GHCup.Query.Symlink
import GHCup.System.Directory
import GHCup.Types
import GHCup.Types.Optics
import GHCup.Warnings

import Control.Applicative
import Control.Monad
#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail )
#endif
import Control.Monad.Reader
import Data.Maybe
import Data.Data (Proxy(..))
import Data.Variant.Excepts
import Data.Versions        hiding ( patch )
import Prelude              hiding ( abs )
import System.FilePath
import System.IO.Error

import qualified Data.Text    as T
import qualified Data.Text.IO as T
import GHCup.Query.DB.HLS

setToolVersion ::
  ( MonadReader env m
  , HasDirs env
  , HasLog env
  , HasPlatformReq env
  , MonadIOish m
  )
  => Tool
  -> TargetVersion
  -> Excepts '[ParseError, NotInstalled] m TargetVersion
setToolVersion tool ver = setToolVersion' tool ver Nothing

setToolVersion' ::
  ( MonadReader env m
  , HasDirs env
  , HasLog env
  , HasPlatformReq env
  , MonadIOish m
  )
  => Tool
  -> TargetVersion
  -> Maybe FilePath        -- ^ if @Nothing@, then we operate in ~/.ghcup/bin,
                           --   otherwise in a tmp directory
  -> Excepts '[ParseError, NotInstalled] m TargetVersion
-- TODO: warn about hls compatibility
setToolVersion' tool tver mTmpDir = do
  hideExcept' @NoToolVersionSet Proxy $ when (isNothing mTmpDir) $ unsetTool tool (_tvTarget tver)

  let ver' = _tvVersion tver
  vspec <- lift $ runE $ getSymlinkSpec tool tver
  case vspec of
    -- legacy
    VLeft _
      | tool == ghc   -> liftE legacySetGHC
      | tool == cabal -> liftE $ setCabal ver' mTmpDir
      | tool == stack -> liftE $ setStack ver' mTmpDir
      | tool == hls   -> liftE $ setHLS ver' SetHLSOnly mTmpDir
      | otherwise -> throwE $ NotInstalled tool tver
    VRight spec -> do
      lift $ logDebug2 $ T.pack (show spec)
      toolDir <- lift $ fmap fromGHCupPath $ toolInstallDestination tool tver
      symls <- maybe
        (lift $ getUnqualifiedSymlinks spec toolDir)
        (\tmpDir -> lift $ getUnqualifiedSymlinks' tmpDir spec toolDir)
        mTmpDir
      shadows <- fmap catMaybes <$> forM symls $ \(target, bin) -> do
        lift $ createLink target bin
        liftIO (isShadowed bin) >>= \case
          Nothing -> pure Nothing
          Just pa -> pure $ Just (pa, bin)
      unless (null shadows) $ logWarn $ T.pack $ prettyHFError (ToolShadowed tool (_tvVersion tver) shadows)

  -- record in 'set' file
  when (isNothing mTmpDir) $ do
    setFile <- lift $ recordedSetVersionFile tool (_tvTarget tver)
    liftIO $ createDirRecursive' (takeDirectory setFile)
    liftIO $ T.writeFile setFile (prettyVer . _tvVersion $ tver)
    currentHLS <- liftE $ getSetVersion' hls Nothing
    currentGHC <- liftE $ getSetVersion' ghc Nothing
    supportedGHC <- lift $ maybe (pure []) (getHLSGHCs . _vrVersion . fst) currentHLS
    lift $ warnAboutHlsCompatibility (_vrVersion . fst <$> currentHLS) (_vrVersion . fst <$> currentGHC) supportedGHC

  pure tver
 where
  legacySetGHC = do
    binDir' <- maybe (binDir <$> lift getDirs) pure mTmpDir
    ghcdir <- lift $ ghcupGHCDir tver
    -- for ghc tools (ghc, ghci, haddock, ...)
    verfiles <- ghcToolFiles tver
    forM_ verfiles $ \file -> do
      -- create symlink
      internalBinDir <- ghcInternalBinDir tver
      let fullF = binDir' </> file  <> exeExt
      destL <- binarySymLinkDestination binDir' (internalBinDir </> file <> exeExt)
      lift $ createLink destL fullF

    when ((isNothing . _tvTarget $ tver) && isNothing mTmpDir)
      $ lift $ symlinkShareDir (fromGHCupPath ghcdir) (T.unpack $ prettyVer (_tvVersion tver))

  symlinkShareDir :: ( MonadReader env m
                     , HasDirs env
                     , HasLog env
                     , MonadIOish m
                     )
                  => FilePath
                  -> String
                  -> m ()
  symlinkShareDir ghcdir ver' = do
    Dirs {..} <- getDirs
    let destdir = fromGHCupPath baseDir
    let sharedir     = "share"
    let fullsharedir = ghcdir </> sharedir
    logDebug $ "Checking for sharedir existence: " <> T.pack fullsharedir
    whenM (liftIO $ doesDirectoryExist fullsharedir) $ do
      let fullF   = destdir </> sharedir
      let targetF = "." </> "ghc" </> ver' </> sharedir
      hideError doesNotExistErrorType $ rmDirectoryLink fullF

      if isWindows
      then liftIO
             -- On windows we need to be more permissive
             -- in case symlinks can't be created, be just
             -- give up here. This symlink isn't strictly necessary.
             $ hideError permissionErrorType
             $ hideError illegalOperationErrorType
             $ createDirectoryLink targetF fullF
      else liftIO
             $ createDirectoryLink targetF fullF



unsetTool ::
  ( MonadReader env m
  , HasDirs env
  , HasLog env
  , HasPlatformReq env
  , MonadIOish m
  )
  => Tool
  -> Maybe T.Text
  -> Excepts '[ParseError, NotInstalled, NoToolVersionSet] m ()
unsetTool tool target = do
  unlessM (fmap isJust $ liftE $ getSetVersion' tool target) $ throwE (NoToolVersionSet tool target)

  mset <- liftE $ getSetVersion' tool target
  setFile <- lift $ recordedSetVersionFile tool target
  case mset of
    Just (VersionRev{..}, mSetFile) -> do
      let tver = TargetVersion target _vrVersion
      vspec <- lift $ runE $ getSymlinkSpec tool tver
      case vspec of
        VLeft _
          | tool == ghc -> liftE $ rmPlainGHC target
          | tool == cabal -> lift unsetCabal
          | tool == stack -> lift unsetStack
          | tool == hls -> lift unsetHLS
          | otherwise -> pure ()
        VRight spec -> do
          toolDir <- lift $ fmap fromGHCupPath $ toolInstallDestination tool tver
          unqualSyml <- lift $ getUnqualifiedSymlinks spec toolDir
          forM_ unqualSyml (rmLink . snd)
          forM_ mSetFile recycleFile
    Nothing -> pure ()
  when (tool == ghc) $ do
    Dirs {..} <- getDirs
    hideError doesNotExistErrorType $ rmDirectoryLink (fromGHCupPath baseDir </> "share")

  logDebug2 $ "rm -f " <> T.pack setFile
  hideError doesNotExistErrorType $ recycleFile setFile