stack-2.3.1: src/Stack/Upgrade.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
module Stack.Upgrade
( upgrade
, UpgradeOpts
, upgradeOpts
) where
import Stack.Prelude hiding (force, Display (..))
import qualified Data.Text as T
import Distribution.Version (mkVersion')
import Options.Applicative
import Path
import qualified Paths_stack as Paths
import Stack.Build
import Stack.Build.Target (NeedTargets(..))
import Stack.Constants
import Stack.Runners
import Stack.Setup
import Stack.Types.Config
import System.Console.ANSI (hSupportsANSIWithoutEmulation)
import System.Process (rawSystem, readProcess)
import RIO.PrettyPrint
import RIO.Process
upgradeOpts :: Parser UpgradeOpts
upgradeOpts = UpgradeOpts
<$> (sourceOnly <|> optional binaryOpts)
<*> (binaryOnly <|> optional sourceOpts)
where
binaryOnly = flag' Nothing (long "binary-only" <> help "Do not use a source upgrade path")
sourceOnly = flag' Nothing (long "source-only" <> help "Do not use a binary upgrade path")
binaryOpts = BinaryOpts
<$> optional (strOption
( long "binary-platform"
<> help "Platform type for archive to download"
<> showDefault))
<*> switch
(long "force-download" <>
help "Download the latest available stack executable")
<*> optional (strOption
(long "binary-version" <>
help "Download a specific stack version"))
<*> optional (strOption
(long "github-org" <>
help "Github organization name"))
<*> optional (strOption
(long "github-repo" <>
help "Github repository name"))
sourceOpts = SourceOpts
<$> ((\fromGit repo branch -> if fromGit then Just (repo, branch) else Nothing)
<$> switch
( long "git"
<> help "Clone from Git instead of downloading from Hackage (more dangerous)" )
<*> strOption
( long "git-repo"
<> help "Clone from specified git repository"
<> value "https://github.com/commercialhaskell/stack"
<> showDefault )
<*> strOption
( long "git-branch"
<> help "Clone from this git branch"
<> value "master"
<> showDefault ))
data BinaryOpts = BinaryOpts
{ _boPlatform :: !(Maybe String)
, _boForce :: !Bool
-- ^ force a download, even if the downloaded version is older
-- than what we are
, _boVersion :: !(Maybe String)
-- ^ specific version to download
, _boGithubOrg :: !(Maybe String)
, _boGithubRepo :: !(Maybe String)
}
deriving Show
newtype SourceOpts = SourceOpts (Maybe (String, String)) -- repo and branch
deriving Show
data UpgradeOpts = UpgradeOpts
{ _uoBinary :: !(Maybe BinaryOpts)
, _uoSource :: !(Maybe SourceOpts)
}
deriving Show
upgrade :: Maybe String -- ^ git hash at time of building, if known
-> UpgradeOpts
-> RIO Runner ()
upgrade builtHash (UpgradeOpts mbo mso) =
case (mbo, mso) of
-- FIXME It would be far nicer to capture this case in the
-- options parser itself so we get better error messages, but
-- I can't think of a way to make it happen.
(Nothing, Nothing) -> throwString "You must allow either binary or source upgrade paths"
(Just bo, Nothing) -> binary bo
(Nothing, Just so) -> source so
-- See #2977 - if --git or --git-repo is specified, do source upgrade.
(_, Just so@(SourceOpts (Just _))) -> source so
(Just bo, Just so) -> binary bo `catchAny` \e -> do
prettyWarnL
[ flow "Exception occured when trying to perform binary upgrade:"
, fromString . show $ e
, line <> flow "Falling back to source upgrade"
]
source so
where
binary bo = binaryUpgrade bo
source so = sourceUpgrade builtHash so
binaryUpgrade :: BinaryOpts -> RIO Runner ()
binaryUpgrade (BinaryOpts mplatform force' mver morg mrepo) = withConfig NoReexec $ do
platforms0 <-
case mplatform of
Nothing -> preferredPlatforms
Just p -> return [("windows" `T.isInfixOf` T.pack p, p)]
archiveInfo <- downloadStackReleaseInfo morg mrepo mver
let mdownloadVersion = getDownloadVersion archiveInfo
force =
case mver of
Nothing -> force'
Just _ -> True -- specifying a version implies we're forcing things
isNewer <-
case mdownloadVersion of
Nothing -> do
prettyErrorL $
flow "Unable to determine upstream version from Github metadata"
:
[ line <> flow "Rerun with --force-download to force an upgrade"
| not force]
return False
Just downloadVersion -> do
prettyInfoL
[ flow "Current Stack version:"
, fromString (versionString stackVersion) <> ","
, flow "available download version:"
, fromString (versionString downloadVersion)
]
return $ downloadVersion > stackVersion
toUpgrade <- case (force, isNewer) of
(False, False) -> do
prettyInfoS "Skipping binary upgrade, you are already running the most recent version"
return False
(True, False) -> do
prettyInfoS "Forcing binary upgrade"
return True
(_, True) -> do
prettyInfoS "Newer version detected, downloading"
return True
when toUpgrade $ do
config <- view configL
downloadStackExe platforms0 archiveInfo (configLocalBin config) True $ \tmpFile -> do
-- Sanity check!
ec <- rawSystem (toFilePath tmpFile) ["--version"]
unless (ec == ExitSuccess)
$ throwString "Non-success exit code from running newly downloaded executable"
sourceUpgrade
:: Maybe String
-> SourceOpts
-> RIO Runner ()
sourceUpgrade builtHash (SourceOpts gitRepo) =
withSystemTempDir "stack-upgrade" $ \tmp -> do
mdir <- case gitRepo of
Just (repo, branch) -> do
remote <- liftIO $ System.Process.readProcess "git" ["ls-remote", repo, branch] []
latestCommit <-
case words remote of
[] -> throwString $ "No commits found for branch " ++ branch ++ " on repo " ++ repo
x:_ -> return x
when (isNothing builtHash) $
prettyWarnS $
"Information about the commit this version of stack was "
<> "built from is not available due to how it was built. "
<> "Will continue by assuming an upgrade is needed "
<> "because we have no information to the contrary."
if builtHash == Just latestCommit
then do
prettyInfoS "Already up-to-date, no upgrade required"
return Nothing
else do
prettyInfoS "Cloning stack"
-- NOTE: "--recursive" was added after v1.0.0 (and before the
-- next release). This means that we can't use submodules in
-- the stack repo until we're comfortable with "stack upgrade
-- --git" not working for earlier versions.
let args = [ "clone", repo , "stack", "--depth", "1", "--recursive", "--branch", branch]
withWorkingDir (toFilePath tmp) $ proc "git" args runProcess_
-- On Windows 10, an upstream issue with the `git clone` command
-- means that command clears, but does not then restore, the
-- ENABLE_VIRTUAL_TERMINAL_PROCESSING flag for native terminals.
-- The following hack re-enables the lost ANSI-capability.
when osIsWindows $
void $ liftIO $ hSupportsANSIWithoutEmulation stdout
return $ Just $ tmp </> relDirStackProgName
-- We need to access the Pantry database to find out about the
-- latest Stack available on Hackage. We first use a standard
-- Config to do this, and once we have the source load up the
-- stack.yaml from inside that source.
Nothing -> withConfig NoReexec $ do
void $ updateHackageIndex
$ Just "Updating index to make sure we find the latest Stack version"
mversion <- getLatestHackageVersion YesRequireHackageIndex "stack" UsePreferredVersions
(PackageIdentifierRevision _ version _) <-
case mversion of
Nothing -> throwString "No stack found in package indices"
Just version -> pure version
if version <= mkVersion' Paths.version
then do
prettyInfoS "Already at latest version, no upgrade required"
return Nothing
else do
suffix <- parseRelDir $ "stack-" ++ versionString version
let dir = tmp </> suffix
mrev <- getLatestHackageRevision YesRequireHackageIndex "stack" version
case mrev of
Nothing -> throwString "Latest version with no revision"
Just (_rev, cfKey, treeKey) -> do
let ident = PackageIdentifier "stack" version
unpackPackageLocation dir $ PLIHackage ident cfKey treeKey
pure $ Just dir
let modifyGO dir go = go
{ globalResolver = Nothing -- always use the resolver settings in the stack.yaml file
, globalStackYaml = SYLOverride $ dir </> stackDotYaml
}
boptsCLI = defaultBuildOptsCLI
{ boptsCLITargets = ["stack"]
}
forM_ mdir $ \dir ->
local (over globalOptsL (modifyGO dir)) $
withConfig NoReexec $ withEnvConfig AllowNoTargets boptsCLI $
local (set (buildOptsL.buildOptsInstallExesL) True) $
build Nothing