Skip to content

Commit cad6eaa

Browse files
snoybergborsboom
authored andcommitted
Download from haskellstack.org, fix archive assumptions
Implements points (2) and (3) from issue #5288.
1 parent 9d61f4d commit cad6eaa

File tree

2 files changed

+118
-29
lines changed

2 files changed

+118
-29
lines changed

ChangeLog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ Behavior changes:
1313

1414
Other enhancements:
1515

16+
* `stack upgrade` will download from `haskellstack.org` before trying `github.com`. See
17+
[#5288](https://github.com/commercialhaskell/stack/issues/5288)
18+
* `stack upgrade` makes less assumptions about archive format. See
19+
[#5288](https://github.com/commercialhaskell/stack/issues/5288)
20+
1621
Bug fixes:
1722

1823
* GHC source builds work properly for recent GHC versions again. See

src/Stack/Setup.hs

Lines changed: 113 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,13 @@ import qualified Distribution.System as Cabal
5454
import Distribution.Text (simpleParse)
5555
import Distribution.Types.PackageName (mkPackageName)
5656
import Distribution.Version (mkVersion)
57+
import Network.HTTP.Client (redirectCount)
5758
import Network.HTTP.StackClient (CheckHexDigest (..), HashCheck (..),
5859
getResponseBody, getResponseStatusCode, httpLbs, httpJSON,
5960
mkDownloadRequest, parseRequest, parseUrlThrow, setGithubHeaders,
60-
setHashChecks, setLengthCheck, verifiedDownloadWithProgress, withResponse)
61+
setHashChecks, setLengthCheck, verifiedDownloadWithProgress, withResponse,
62+
setRequestMethod)
63+
import Network.HTTP.Simple (getResponseHeader)
6164
import Path hiding (fileExtension)
6265
import Path.CheckInstall (warnInstallSearchPathIssues)
6366
import Path.Extended (fileExtension)
@@ -1780,14 +1783,92 @@ getUtf8EnvVars compilerVer =
17801783

17811784
-- Binary Stack upgrades
17821785

1783-
newtype StackReleaseInfo = StackReleaseInfo Value
1784-
1785-
downloadStackReleaseInfo :: (MonadIO m, MonadThrow m)
1786-
=> Maybe String -- Github org
1787-
-> Maybe String -- Github repo
1788-
-> Maybe String -- ^ optional version
1789-
-> m StackReleaseInfo
1790-
downloadStackReleaseInfo morg mrepo mver = liftIO $ do
1786+
-- | Information on a binary release of Stack
1787+
data StackReleaseInfo
1788+
= SRIGithub !Value
1789+
-- ^ Metadata downloaded from GitHub releases about available binaries.
1790+
| SRIHaskellStackOrg !HaskellStackOrg
1791+
-- ^ Information on the latest available binary for the current platforms.
1792+
1793+
data HaskellStackOrg = HaskellStackOrg
1794+
{ hsoUrl :: !Text
1795+
, hsoVersion :: !Version
1796+
}
1797+
deriving Show
1798+
1799+
downloadStackReleaseInfo
1800+
:: (HasPlatform env, HasLogFunc env)
1801+
=> Maybe String -- Github org
1802+
-> Maybe String -- Github repo
1803+
-> Maybe String -- ^ optional version
1804+
-> RIO env StackReleaseInfo
1805+
downloadStackReleaseInfo Nothing Nothing Nothing = do
1806+
platform <- view platformL
1807+
-- Fallback list of URLs to try for upgrading.
1808+
let urls0 =
1809+
case platform of
1810+
Platform X86_64 Cabal.Linux ->
1811+
[ "https://get.haskellstack.org/upgrade/linux-x86_64-static.tar.gz"
1812+
, "https://get.haskellstack.org/upgrade/linux-x86_64.tar.gz"
1813+
]
1814+
Platform X86_64 Cabal.OSX ->
1815+
[ "https://get.haskellstack.org/upgrade/osx-x86_64.tar.gz"
1816+
]
1817+
Platform X86_64 Cabal.Windows ->
1818+
[ "https://get.haskellstack.org/upgrade/windows-x86_64.tar.gz"
1819+
]
1820+
_ -> []
1821+
-- Helper function: extract the version from a GitHub releases URL.
1822+
let extractVersion loc = do
1823+
version0 <-
1824+
case reverse $ splitOn "/" $ T.unpack loc of
1825+
_final:version0:_ -> Right version0
1826+
_ -> Left $ "Insufficient pieces in location: " ++ show loc
1827+
version1 <- maybe (Left "no leading v on version") Right $ stripPrefix "v" version0
1828+
maybe (Left $ "Invalid version: " ++ show version1) Right $ parseVersion version1
1829+
1830+
-- Try out different URLs. If we've exhausted all of them, fall back to GitHub.
1831+
loop [] = do
1832+
logDebug "Could not get binary from haskellstack.org, trying GitHub"
1833+
downloadStackReleaseInfoGithub Nothing Nothing Nothing
1834+
1835+
-- Try the next URL
1836+
loop (url:urls) = do
1837+
-- Make a HEAD request without any redirects
1838+
req <- setRequestMethod "HEAD" <$> parseRequest (T.unpack url)
1839+
res <- httpLbs req { redirectCount = 0 }
1840+
1841+
-- Look for a redirect. We're looking for a standard GitHub releases
1842+
-- URL where we can extract version information from.
1843+
case getResponseHeader "location" res of
1844+
[] -> logDebug "No location header found, continuing" *> loop urls
1845+
-- Exactly one location header.
1846+
[locBS] ->
1847+
case decodeUtf8' locBS of
1848+
Left e -> logDebug ("Invalid UTF8: " <> displayShow (locBS, e)) *> loop urls
1849+
Right loc ->
1850+
case extractVersion loc of
1851+
Left s -> logDebug ("No version found: " <> displayShow (url, loc, s)) *> loop (loc:urls)
1852+
-- We found a valid URL, let's use it!
1853+
Right version -> do
1854+
let hso = HaskellStackOrg
1855+
{ hsoUrl = loc
1856+
, hsoVersion = version
1857+
}
1858+
logDebug $ "Downloading from haskellstack.org: " <> displayShow hso
1859+
pure $ SRIHaskellStackOrg hso
1860+
locs -> logDebug ("Multiple location headers found: " <> displayShow locs) *> loop urls
1861+
loop urls0
1862+
downloadStackReleaseInfo morg mrepo mver = downloadStackReleaseInfoGithub morg mrepo mver
1863+
1864+
-- | Same as above, but always uses Github
1865+
downloadStackReleaseInfoGithub
1866+
:: (MonadIO m, MonadThrow m)
1867+
=> Maybe String -- Github org
1868+
-> Maybe String -- Github repo
1869+
-> Maybe String -- ^ optional version
1870+
-> m StackReleaseInfo
1871+
downloadStackReleaseInfoGithub morg mrepo mver = liftIO $ do
17911872
let org = fromMaybe "commercialhaskell" morg
17921873
repo = fromMaybe "stack" mrepo
17931874
let url = concat
@@ -1804,7 +1885,7 @@ downloadStackReleaseInfo morg mrepo mver = liftIO $ do
18041885
res <- httpJSON $ setGithubHeaders req
18051886
let code = getResponseStatusCode res
18061887
if code >= 200 && code < 300
1807-
then return $ StackReleaseInfo $ getResponseBody res
1888+
then return $ SRIGithub $ getResponseBody res
18081889
else throwString $ "Could not get release information for Stack from: " ++ url
18091890

18101891
preferredPlatforms :: (MonadReader env m, HasPlatform env, MonadThrow m)
@@ -1899,7 +1980,7 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
18991980
`catchAny` (logError . displayShow)
19001981
where
19011982

1902-
findArchive (StackReleaseInfo val) pattern = do
1983+
findArchive (SRIGithub val) pattern = do
19031984
Object top <- return val
19041985
Array assets <- HashMap.lookup "assets" top
19051986
getFirst $ fold $ fmap (First . findMatch pattern') assets
@@ -1913,6 +1994,7 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
19131994
String url <- HashMap.lookup "browser_download_url" o
19141995
Just url
19151996
findMatch _ _ = Nothing
1997+
findArchive (SRIHaskellStackOrg hso) _ = pure $ hsoUrl hso
19161998

19171999
handleTarball :: Path Abs File -> Bool -> T.Text -> IO ()
19182000
handleTarball tmpFile isWindows url = do
@@ -1928,25 +2010,26 @@ downloadStackExe platforms0 archiveInfo destDir checkPath testExe = do
19282010
, T.unpack url
19292011
]
19302012
loop (Tar.Fail e) = throwM e
1931-
loop (Tar.Next e es)
1932-
| Tar.entryPath e == exeName =
1933-
case Tar.entryContent e of
1934-
Tar.NormalFile lbs _ -> do
1935-
ensureDir destDir
1936-
LBS.writeFile (toFilePath tmpFile) lbs
1937-
_ -> error $ concat
1938-
[ "Invalid file type for tar entry named "
1939-
, exeName
1940-
, " downloaded from "
1941-
, T.unpack url
1942-
]
1943-
| otherwise = loop es
2013+
loop (Tar.Next e es) =
2014+
case FP.splitPath (Tar.entryPath e) of
2015+
-- Ignore the first component, see: https://github.com/commercialhaskell/stack/issues/5288
2016+
[_ignored, name] | name == exeName -> do
2017+
case Tar.entryContent e of
2018+
Tar.NormalFile lbs _ -> do
2019+
ensureDir destDir
2020+
LBS.writeFile (toFilePath tmpFile) lbs
2021+
_ -> error $ concat
2022+
[ "Invalid file type for tar entry named "
2023+
, Tar.entryPath e
2024+
, " downloaded from "
2025+
, T.unpack url
2026+
]
2027+
_ -> loop es
19442028
loop entries
19452029
where
1946-
-- The takeBaseName drops the .gz, dropExtension drops the .tar
1947-
exeName =
1948-
let base = FP.dropExtension (FP.takeBaseName (T.unpack url)) FP.</> "stack"
1949-
in if isWindows then base FP.<.> "exe" else base
2030+
exeName
2031+
| isWindows = "stack.exe"
2032+
| otherwise = "stack"
19502033

19512034
-- | Ensure that the Stack executable download is in the same location
19522035
-- as the currently running executable. See:
@@ -2004,8 +2087,9 @@ performPathChecking newFile executablePath = do
20042087
| otherwise -> throwM e
20052088

20062089
getDownloadVersion :: StackReleaseInfo -> Maybe Version
2007-
getDownloadVersion (StackReleaseInfo val) = do
2090+
getDownloadVersion (SRIGithub val) = do
20082091
Object o <- Just val
20092092
String rawName <- HashMap.lookup "name" o
20102093
-- drop the "v" at the beginning of the name
20112094
parseVersion $ T.unpack (T.drop 1 rawName)
2095+
getDownloadVersion (SRIHaskellStackOrg hso) = Just $ hsoVersion hso

0 commit comments

Comments
 (0)