Compare commits

..

8 Commits

12 changed files with 19149 additions and 232 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -116,7 +116,20 @@ else
if [ "${OS}" = "LINUX" ] ; then if [ "${OS}" = "LINUX" ] ; then
eghcup --downloader=wget prefetch ghc 8.10.3 eghcup --downloader=wget prefetch ghc 8.10.3
eghcup --offline install ghc 8.10.3 eghcup --offline install ghc 8.10.3
else # test wget a bit if [ "${ARCH}" = "64" ] ; then
expected=$(cat "$( cd "$(dirname "$0")" ; pwd -P )/../ghc-8.10.3-linux.files" | sort)
actual=$(cd "${GHCUP_DIR}/ghc/8.10.3/" && find | sort)
[ "${actual}" = "${expected}" ]
unset actual expected
fi
elif [ "${OS}" = "WINDOWS" ] ; then
eghcup prefetch ghc 8.10.3
eghcup --offline install ghc 8.10.3
expected=$(cat "$( cd "$(dirname "$0")" ; pwd -P )/../ghc-8.10.3-windows.files" | sort)
actual=$(cd "${GHCUP_DIR}/ghc/8.10.3/" && find | sort)
[ "${actual}" = "${expected}" ]
unset actual expected
else
eghcup prefetch ghc 8.10.3 eghcup prefetch ghc 8.10.3
eghcup --offline install ghc 8.10.3 eghcup --offline install ghc 8.10.3
fi fi

View File

@@ -19,8 +19,6 @@ Similar in scope to [rustup](https://github.com/rust-lang-nursery/rustup.rs), [p
* [Env variables](#env-variables) * [Env variables](#env-variables)
* [Installing custom bindists](#installing-custom-bindists) * [Installing custom bindists](#installing-custom-bindists)
* [Tips and tricks](#tips-and-tricks) * [Tips and tricks](#tips-and-tricks)
* [Stack hooks](#stack-hooks)
* [Sharing MSys2 between stack and ghcup](#sharing-msys2-between-stack-and-ghcup)
* [Design goals](#design-goals) * [Design goals](#design-goals)
* [How](#how) * [How](#how)
* [Known users](#known-users) * [Known users](#known-users)
@@ -141,10 +139,6 @@ This is the complete list of env variables that change GHCup behavior:
* `GHCUP_SKIP_UPDATE_CHECK`: Skip the (possibly annoying) update check when you run a command * `GHCUP_SKIP_UPDATE_CHECK`: Skip the (possibly annoying) update check when you run a command
* `CC`/`LD` etc.: full environment is passed to the build system when compiling GHC via GHCup * `CC`/`LD` etc.: full environment is passed to the build system when compiling GHC via GHCup
On windows, there are additional variables:
* `GHCUP_MSYS2`: where to find msys2, so we can invoke shells and other cool stuff
### Installing custom bindists ### Installing custom bindists
There are a couple of good use cases to install custom bindists: There are a couple of good use cases to install custom bindists:
@@ -208,36 +202,6 @@ with_ghc 8.10.5 code path/to/haskell/source
Cabal and HLS will now see `8.10.5` as the primary GHC, without the need to Cabal and HLS will now see `8.10.5` as the primary GHC, without the need to
run `ghcup set` all the time when switching between projects. run `ghcup set` all the time when switching between projects.
### Stack hooks
GHCup distributes a patched Stack, which has support for custom installation hooks, see:
* https://github.com/commercialhaskell/stack/pull/5585
Usually, the bootstrap script will already install a hook for you. If not,
download it [here](https://gitlab.haskell.org/haskell/ghcup-hs/-/tree/master/hooks/stack/ghc-install.sh),
place it in `~/.stack/hooks/ghc-install.sh` and make sure it's executable.
Hooks aren't run when `system-ghc: true` is set in `stack.yaml`. If you want stack
to never fall back to its own installation logic if ghcup fails, run the following command:
```sh
stack config set install-ghc false --global
```
### Sharing MSys2 between stack and ghcup
You can tell stack to use GHCup's MSys2 installation. Add the following lines to `~/.stack/config.yaml`:
```yml
skip-msys: true
extra-path:
- "C:\\ghcup\\msys64\\usr\\bin"
- "C:\\ghcup\\msys64\\mingw64\\bin"
extra-include-dirs: "C:\\ghcup\\msys64\\mingw64\\include"
extra-lib-dirs: "C:\\ghcup\\msys64\\mingw64\\lib"
```
## Design goals ## Design goals
1. simplicity 1. simplicity
@@ -314,6 +278,18 @@ to figure out whether you have the correct toolchain and
the correct dependencies. Refer to [the official docs](https://ghc.haskell.org/trac/ghc/wiki/Building/Preparation/Linux) the correct dependencies. Refer to [the official docs](https://ghc.haskell.org/trac/ghc/wiki/Building/Preparation/Linux)
on how to prepare your environment for building GHC. on how to prepare your environment for building GHC.
### Stack support
There may be a number of bugs when trying to make ghcup installed GHC versions work with stack,
such as:
- https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/188
Further, stack's upgrade procedure may break/confuse ghcup. There are a number of integration
issues discussed here:
- https://gitlab.haskell.org/haskell/ghcup-hs/-/issues/153
### Windows support ### Windows support
Windows support is in early stages. Since windows doesn't support symbolic links properly, Windows support is in early stages. Since windows doesn't support symbolic links properly,

View File

@@ -237,7 +237,7 @@ validateTarballs (TarballFilter etool versionRegex) dls gt = do
($(logError) $ T.pack $ prettyShow e) ($(logError) $ T.pack $ prettyShow e)
liftIO $ exitWith (ExitFailure 2) liftIO $ exitWith (ExitFailure 2)
let appstate = AppState (Settings True False Never Curl False GHCupURL False) dirs defaultKeyBindings (GHCupInfo mempty mempty mempty) pfreq let appstate = AppState (Settings True False Never Curl True GHCupURL False) dirs defaultKeyBindings (GHCupInfo mempty mempty mempty) pfreq
r <- r <-
runLogger runLogger

View File

@@ -8,7 +8,6 @@
# * BOOTSTRAP_HASKELL_GHC_VERSION - the ghc version to install # * BOOTSTRAP_HASKELL_GHC_VERSION - the ghc version to install
# * BOOTSTRAP_HASKELL_CABAL_VERSION - the cabal version to install # * BOOTSTRAP_HASKELL_CABAL_VERSION - the cabal version to install
# * BOOTSTRAP_HASKELL_INSTALL_STACK - whether to install latest stack # * BOOTSTRAP_HASKELL_INSTALL_STACK - whether to install latest stack
# * BOOTSTRAP_HASKELL_INSTALL_STACK_HOOK - whether to install stack hook as well
# * BOOTSTRAP_HASKELL_INSTALL_HLS - whether to install latest hls # * BOOTSTRAP_HASKELL_INSTALL_HLS - whether to install latest hls
# * BOOTSTRAP_HASKELL_ADJUST_BASHRC - whether to adjust PATH in bashrc (prepend) # * BOOTSTRAP_HASKELL_ADJUST_BASHRC - whether to adjust PATH in bashrc (prepend)
# * BOOTSTRAP_HASKELL_ADJUST_CABAL_CONFIG - whether to adjust mingw paths in cabal.config on windows # * BOOTSTRAP_HASKELL_ADJUST_CABAL_CONFIG - whether to adjust mingw paths in cabal.config on windows
@@ -509,25 +508,17 @@ ask_hls() {
ask_stack() { ask_stack() {
if [ -n "${BOOTSTRAP_HASKELL_INSTALL_STACK}" ] ; then if [ -n "${BOOTSTRAP_HASKELL_INSTALL_STACK}" ] ; then
if [ -n "${BOOTSTRAP_HASKELL_INSTALL_STACK_HOOK}" ] ; then return 1
return 2
else
return 1
fi
fi fi
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
echo "-------------------------------------------------------------------------------" echo "-------------------------------------------------------------------------------"
warn "Do you want to install stack and stack hooks?" warn "Do you want to install stack?"
warn ""
warn "Stack is a haskell build tool similar to cabal that is used by some projects." warn "Stack is a haskell build tool similar to cabal that is used by some projects."
warn "Also see https://docs.haskellstack.org/" warn "Also see https://docs.haskellstack.org/"
warn "" warn ""
warn "Stack hooks allow stack to use ghcup for GHC installation (usually stack handles" warn "[Y] Yes [N] No [?] Help (default is \"N\")."
warn "installation itself, leading to possibly duplicated GHC versions)."
warn ""
warn "[Y] Yes, with hooks [L] Yes, no hooks [N] No [?] Help (default is \"N\")."
warn "" warn ""
while true; do while true; do
@@ -535,16 +526,13 @@ ask_stack() {
case $stack_answer in case $stack_answer in
[Yy]*) [Yy]*)
return 2 ;;
[Ll]*)
return 1 ;; return 1 ;;
[Nn]* | "") [Nn]* | "")
return 0 ;; return 0 ;;
*) *)
echo "Possible choices are:" echo "Possible choices are:"
echo echo
echo "Y - Yes, install stack with hooks" echo "Y - Yes, install stack"
echo "L - Yes, install stack without hooks"
echo "N - No, don't install anything more (default)" echo "N - No, don't install anything more (default)"
echo echo
echo "Please make your choice and press ENTER." echo "Please make your choice and press ENTER."
@@ -648,27 +636,7 @@ esac
case $ask_stack_answer in case $ask_stack_answer in
1) 1)
{ _eghcup --cache install stack || warn "Stack installation failed, continuing anyway"
_eghcup --cache install stack
edo stack update
} || warn "Stack installation failed, continuing anyway"
;;
2)
{
_eghcup --cache install stack
edo stack update
if [ -n "${STACK_ROOT}" ] ; then
stack_root=$STACK_ROOT
else
stack_root=${HOME}/.stack
fi
edo mkdir -p "${stack_root}/hooks/"
edo curl -Lf "https://www.haskell.org/ghcup/sh/hooks/stack/ghc-install.sh" > "${stack_root}/hooks/ghc-install.sh"
edo chmod +x "${stack_root}/hooks/ghc-install.sh"
edo stack config set system-ghc false --global
edo stack config set install-ghc false --global
} || warn "Stack installation failed, continuing anyway"
;; ;;
*) ;; *) ;;
esac esac

View File

@@ -29,8 +29,6 @@ param (
[switch]$InBash, [switch]$InBash,
# Whether to install stack as well # Whether to install stack as well
[switch]$InstallStack, [switch]$InstallStack,
# Whether to install stack hooks as well
[switch]$InstallStackHook,
# Whether to install hls as well # Whether to install hls as well
[switch]$InstallHLS, [switch]$InstallHLS,
# Skip adjusting cabal.config with mingw paths # Skip adjusting cabal.config with mingw paths
@@ -225,7 +223,6 @@ $null = [Environment]::SetEnvironmentVariable("GHCUP_INSTALL_BASE_PREFIX", $Ghcu
$GhcupDir = ('{0}\ghcup' -f $GhcupBasePrefix) $GhcupDir = ('{0}\ghcup' -f $GhcupBasePrefix)
$StackDir = ('{0}\sr' -f $GhcupBasePrefix)
$MsysDir = ('{0}\msys64' -f $GhcupDir) $MsysDir = ('{0}\msys64' -f $GhcupDir)
$Bash = ('{0}\usr\bin\bash' -f $MsysDir) $Bash = ('{0}\usr\bin\bash' -f $MsysDir)
if (!($BootstrapUrl)) { if (!($BootstrapUrl)) {
@@ -313,28 +310,15 @@ if (!($InstallHLS)) {
} }
# ask whether to install stack # ask whether to install stack
$InstallStackDoc = @'
Do you want to install stack and stack hooks?
Stack is a haskell build tool similar to cabal that is used by some projects.
Also see https://docs.haskellstack.org/
Stack hooks allow stack to use ghcup for GHC installation (usually stack handles
installation itself, leading to possibly duplicated GHC versions).
'@
if (!($InstallStack)) { if (!($InstallStack)) {
if (!($Silent)) { if (!($Silent)) {
$StackDecision = $Host.UI.PromptForChoice('Install stack' $StackDecision = $Host.UI.PromptForChoice('Install stack'
, $InstallStackDoc , 'Do you want to install stack as well?'
, [System.Management.Automation.Host.ChoiceDescription[]] @('&Yes, with hooks' , [System.Management.Automation.Host.ChoiceDescription[]] @('&Yes'
'&Light install, without hooks'
'&No' '&No'
'&Abort'), 2) '&Abort'), 1)
if ($StackDecision -eq 0) { if ($StackDecision -eq 0) {
$InstallStack = $true
$InstallStackHook = $true
} elseif ($StackDecision -eq 1) {
$InstallStack = $true $InstallStack = $true
} elseif ($StackDecision -eq 2) { } elseif ($StackDecision -eq 2) {
Exit 0 Exit 0
@@ -515,11 +499,6 @@ $SilentExport = 'export BOOTSTRAP_HASKELL_NONINTERACTIVE=1 ;'
if ($InstallStack) { if ($InstallStack) {
$StackInstallExport = 'export BOOTSTRAP_HASKELL_INSTALL_STACK=1 ;' $StackInstallExport = 'export BOOTSTRAP_HASKELL_INSTALL_STACK=1 ;'
$null = [Environment]::SetEnvironmentVariable("STACK_ROOT", $StackDir, [System.EnvironmentVariableTarget]::User)
}
if ($InstallStackHook) {
$StackInstallHookExport = 'export BOOTSTRAP_HASKELL_INSTALL_STACK_HOOK=1 ;'
} }
if ($InstallHLS) { if ($InstallHLS) {
@@ -531,9 +510,9 @@ if (!($NoAdjustCabalConfig)) {
} }
if ((Get-Process -ID $PID).ProcessName.StartsWith("bootstrap-haskell") -Or $InBash) { if ((Get-Process -ID $PID).ProcessName.StartsWith("bootstrap-haskell") -Or $InBash) {
Exec "$Bash" '-lc' ('{4} {6} {7} {8} {9} [ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; export CABAL_DIR=''{5}'' ; export STACK_ROOT=$(cygpath -m ''{10}/'') ; [[ ''{0}'' = https* ]] && curl --proto ''=https'' --tlsv1.2 -sSf {0} | bash || cat $(cygpath -m ''{0}'') | bash' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir, $SilentExport, $CabalDirFull, $StackInstallExport, $HLSInstallExport, $AdjustCabalConfigExport, $StackInstallHookExport, $StackDir) Exec "$Bash" '-lc' ('{4} {6} {7} {8} [ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; export CABAL_DIR=''{5}'' ; [[ ''{0}'' = https* ]] && curl --proto ''=https'' --tlsv1.2 -sSf {0} | bash || cat $(cygpath -m ''{0}'') | bash' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir, $SilentExport, $CabalDirFull, $StackInstallExport, $HLSInstallExport, $AdjustCabalConfigExport)
} else { } else {
Exec "$Msys2Shell" '-mingw64' '-mintty' '-c' ('{4} {6} {7} {8} {9} [ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; export CABAL_DIR=''{5}'' ; export STACK_ROOT=$(cygpath -m ''{10}/'') ; trap ''echo Press any key to exit && read -n 1 && exit'' 2 ; [[ ''{0}'' = https* ]] && curl --proto ''=https'' --tlsv1.2 -sSf {0} | bash || cat $(cygpath -m ''{0}'') | bash ; echo ''Press any key to exit'' && read -n 1' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir, $SilentExport, $CabalDirFull, $StackInstallExport, $HLSInstallExport, $AdjustCabalConfigExport, $StackInstallHookExport, $StackDir) Exec "$Msys2Shell" '-mingw64' '-mintty' '-c' ('{4} {6} {7} {8} [ -n ''{1}'' ] && export GHCUP_MSYS2=$(cygpath -m ''{1}'') ; [ -n ''{2}'' ] && export GHCUP_INSTALL_BASE_PREFIX=$(cygpath -m ''{2}/'') ; export PATH=$(cygpath -u ''{3}/bin''):$PATH ; export CABAL_DIR=''{5}'' ; trap ''echo Press any key to exit && read -n 1 && exit'' 2 ; [[ ''{0}'' = https* ]] && curl --proto ''=https'' --tlsv1.2 -sSf {0} | bash || cat $(cygpath -m ''{0}'') | bash ; echo ''Press any key to exit'' && read -n 1' -f $BootstrapUrl, $MsysDir, $GhcupBasePrefix, $GhcupDir, $SilentExport, $CabalDirFull, $StackInstallExport, $HLSInstallExport, $AdjustCabalConfigExport)
} }

View File

@@ -2206,7 +2206,8 @@ ghcupDownloads:
unknown_versioning: *stack-271-64 unknown_versioning: *stack-271-64
2.7.3: 2.7.3:
viTags: viTags:
- old - Latest
- Recommended
viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v273 viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v273
viPostInstall: *stack-post viPostInstall: *stack-post
viArch: viArch:
@@ -2231,65 +2232,4 @@ ghcupDownloads:
RegexDir: "stack-.*" RegexDir: "stack-.*"
Linux_Alpine: Linux_Alpine:
unknown_versioning: *stack-273-64 unknown_versioning: *stack-273-64
2.7.3.1:
viTags:
- Latest
- Recommended
viChangeLog: https://github.com/commercialhaskell/stack/blob/master/ChangeLog.md#v273
viPostInstall: &stack-post-new |
Stack manages GHC versions internally by default. It can also use hooks to automatically
install and locate GHC versions utilizing GHCup:
https://gitlab.haskell.org/haskell/ghcup-hs#stack-hooks
Alternatively, you can also tell stack to use the system GHC version (whatever is in PATH):
stack config set system-ghc true --global
If you want stack to use GHCup's provided MSys2, follow the instructions here (not recommended):
https://gitlab.haskell.org/haskell/ghcup-hs/-/tree/stack-fork#sharing-msys2-between-stack-and-ghcup
Also check out: https://docs.haskellstack.org/en/stable/yaml_configuration
!!! GHCup ships a patched version of stack. Before reporting bugs to stack developers !!!
!!! make sure it is reproducible with the original stack version: !!!
!!! https://docs.haskellstack.org/en/stable/install_and_upgrade/ !!!
viArch:
A_64:
Linux_UnknownLinux:
unknown_versioning: &stack-2731-64
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-linux-x86_64.tar.gz
dlSubdir:
RegexDir: "stack-.*"
dlHash: 7c10090d568651208b6d6c003c8d78ef79099f4b472aea5ca800f3e4a7a1c6ce
Darwin:
unknown_versioning:
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-osx-x86_64.tar.gz
dlSubdir:
RegexDir: "stack-.*"
dlHash: 23030868be377d62ed324332d239ddd915b12f7a64c887f297e1b60a3f65894f
Windows:
unknown_versioning:
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-windows-x86_64.tar.bz2
dlHash: 983ac24219370cdf220f47f9edb17d0aeb831a1253fd777d322fd1bff358c511
Linux_Alpine:
unknown_versioning: *stack-2731-64
FreeBSD:
unknown_versioning:
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-freebsd-x86_64.tar.gz
dlHash: b6b1eb59981b8c1d3a04b061f03296b1540ecbadbc34cbd4fe712a575a155ee9
A_32:
Linux_UnknownLinux:
unknown_versioning: &stack-2731-32
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-linux-i386.tar.gz
dlHash: da2081d84517d16a8216e929582dd39caa19643c819b77c6f68f138bd177a758
Linux_Alpine:
unknown_versioning: *stack-2731-32
A_ARM64:
Linux_UnknownLinux:
unknown_versioning:
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-linux-aarch64.tar.gz
dlHash: 0e1789852a476dd87f495fa0b481309398b2bafc00cf7c8d980c91977b39d9a6
A_ARM:
Linux_UnknownLinux:
unknown_versioning:
dlUri: https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.3.1/stack-2.7.3.1-linux-armv7.tar.gz
dlHash: 13d61616c169615d80381ea0c84e13540410b736a553565cd48d10b4d2bd7f23

View File

@@ -1,23 +0,0 @@
#!/bin/sh
set -eu
case $HOOK_GHC_TYPE in
bindist)
ghcup --offline whereis ghc $HOOK_GHC_VERSION || {
ghcup --cache install ghc $HOOK_GHC_VERSION && ghcup whereis ghc $HOOK_GHC_VERSION
}
;;
git)
>&2 echo "Hook doesn't support installing from source."
>&2 echo "Consider enabling stack GHC installs for this project, via:"
>&2 echo " stack config set install-ghc true"
exit 1
;;
*)
>&2 echo "Unsupported GHC installation type: ${HOOK_GHC_TYPE}."
>&2 echo "Consider enabling stack GHC installs for this project, via:"
>&2 echo " stack config set install-ghc true"
exit 2
;;
esac

View File

@@ -300,10 +300,6 @@ installUnpackedGHC path inst ver = do
setModificationTime dest mtime setModificationTime dest mtime
#else #else
PlatformRequest {..} <- lift getPlatformReq PlatformRequest {..} <- lift getPlatformReq
liftIO $ copyDirectoryRecursive path inst $ \source dest -> do
mtime <- getModificationTime source
copyFile source dest
setModificationTime dest mtime
let alpineArgs let alpineArgs
| ver >= [vver|8.2.2|], Linux Alpine <- _rPlatform | ver >= [vver|8.2.2|], Linux Alpine <- _rPlatform

View File

@@ -172,21 +172,31 @@ getBase :: ( MonadReader env m
-> Excepts '[JSONError] m GHCupInfo -> Excepts '[JSONError] m GHCupInfo
getBase uri = do getBase uri = do
Settings { noNetwork } <- lift getSettings Settings { noNetwork } <- lift getSettings
yaml <- lift $ yamlFromCache uri
unless noNetwork $ -- try to download yaml... usually this writes it into cache dir,
handleIO (\e -> warnCache (displayException e)) -- but in some cases not (e.g. when using file://), so we honour
. catchE @_ @_ @'[] (\e@(DownloadFailed _) -> warnCache (prettyShow e)) -- the return filepath, if any
. reThrowAll @_ @_ @'[DownloadFailed] DownloadFailed mYaml <- if noNetwork && view (uriSchemeL' % schemeBSL') uri /= "file" -- for file://, let it fall through
. smartDl then pure Nothing
$ uri else handleIO (\e -> warnCache (displayException e) >> pure Nothing)
. catchE @_ @_ @'[] (\e@(DownloadFailed _) -> warnCache (prettyShow e) >> pure Nothing)
. reThrowAll @_ @_ @'[DownloadFailed] DownloadFailed
. fmap Just
. smartDl
$ uri
-- if we didn't get a filepath from the download, use the cached yaml
actualYaml <- maybe (lift $ yamlFromCache uri) pure mYaml
lift $ $(logDebug) [i|Decoding yaml at: #{actualYaml}|]
liftE liftE
. onE_ (onError yaml) . onE_ (onError actualYaml)
. lEM' @_ @_ @'[JSONError] JSONDecodeError . lEM' @_ @_ @'[JSONError] JSONDecodeError
. fmap (first (\e -> [i|#{displayException e} . fmap (first (\e -> [i|#{displayException e}
Consider removing "#{yaml}" manually.|])) Consider removing "#{actualYaml}" manually.|]))
. liftIO . liftIO
. Y.decodeFileEither . Y.decodeFileEither
$ yaml $ actualYaml
where where
-- On error, remove the etags file and set access time to 0. This should ensure the next invocation -- On error, remove the etags file and set access time to 0. This should ensure the next invocation
-- may re-download and succeed. -- may re-download and succeed.
@@ -221,28 +231,32 @@ Consider removing "#{yaml}" manually.|]))
, DigestError , DigestError
] ]
m1 m1
() FilePath
smartDl uri' = do smartDl uri' = do
json_file <- lift $ yamlFromCache uri' json_file <- lift $ yamlFromCache uri'
let scheme = view (uriSchemeL' % schemeBSL') uri'
e <- liftIO $ doesFileExist json_file e <- liftIO $ doesFileExist json_file
currentTime <- liftIO getCurrentTime currentTime <- liftIO getCurrentTime
if e Dirs { cacheDir } <- lift getDirs
then do
accessTime <- liftIO $ getAccessTime json_file
-- access time won't work on most linuxes, but we can try regardless -- for local files, let's short-circuit and ignore access time
when ((utcTimeToPOSIXSeconds currentTime - utcTimeToPOSIXSeconds accessTime) > 300) $ if | scheme == "file" -> liftE $ download uri' Nothing cacheDir Nothing True
-- no access in last 5 minutes, re-check upstream mod time | e -> do
dlWithMod currentTime json_file accessTime <- liftIO $ getAccessTime json_file
else
dlWithMod currentTime json_file -- access time won't work on most linuxes, but we can try regardless
if | ((utcTimeToPOSIXSeconds currentTime - utcTimeToPOSIXSeconds accessTime) > 300) ->
-- no access in last 5 minutes, re-check upstream mod time
dlWithMod currentTime json_file
| otherwise -> pure json_file
| otherwise -> dlWithMod currentTime json_file
where where
dlWithMod modTime json_file = do dlWithMod modTime json_file = do
let (dir, fn) = splitFileName json_file let (dir, fn) = splitFileName json_file
f <- liftE $ download uri' Nothing dir (Just fn) True f <- liftE $ download uri' Nothing dir (Just fn) True
liftIO $ setModificationTime f modTime liftIO $ setModificationTime f modTime
liftIO $ setAccessTime f modTime liftIO $ setAccessTime f modTime
pure f
getDownloadInfo :: ( MonadReader env m getDownloadInfo :: ( MonadReader env m
@@ -304,27 +318,25 @@ download :: ( MonadReader env m
) )
=> URI => URI
-> Maybe T.Text -- ^ expected hash -> Maybe T.Text -- ^ expected hash
-> FilePath -- ^ destination dir -> FilePath -- ^ destination dir (ignored for file:// scheme)
-> Maybe FilePath -- ^ optional filename -> Maybe FilePath -- ^ optional filename
-> Bool -- ^ whether to read an write etags -> Bool -- ^ whether to read an write etags
-> Excepts '[DigestError , DownloadFailed] m FilePath -> Excepts '[DigestError , DownloadFailed] m FilePath
download uri eDigest dest mfn etags download uri eDigest dest mfn etags
| scheme == "https" = dl | scheme == "https" = dl
| scheme == "http" = dl | scheme == "http" = dl
| scheme == "file" = cp | scheme == "file" = do
let destFile' = T.unpack . decUTF8Safe $ path
lift $ $(logDebug) [i|using local file: #{destFile'}|]
forM_ eDigest (liftE . flip checkDigest destFile')
pure destFile'
| otherwise = throwE $ DownloadFailed (variantFromValue UnsupportedScheme) | otherwise = throwE $ DownloadFailed (variantFromValue UnsupportedScheme)
where where
scheme = view (uriSchemeL' % schemeBSL') uri scheme = view (uriSchemeL' % schemeBSL') uri
cp = do
-- destination dir must exist
liftIO $ createDirRecursive' dest
let fromFile = T.unpack . decUTF8Safe $ path
liftIO $ copyFile fromFile destFile
pure destFile
dl = do dl = do
let uri' = decUTF8Safe (serializeURIRef' uri) destFile <- liftE . reThrowAll @_ @_ @'[DownloadFailed] DownloadFailed $ getDestFile
lift $ $(logInfo) [i|downloading: #{uri'}|] lift $ $(logInfo) [i|downloading: #{uri'} as file #{destFile}|]
-- destination dir must exist -- destination dir must exist
liftIO $ createDirRecursive' dest liftIO $ createDirRecursive' dest
@@ -366,7 +378,7 @@ download uri eDigest dest mfn etags
_ -> liftE $ throwE @_ @'[DownloadFailed] (DownloadFailed (toVariantAt @0 (MalformedHeaders headers) _ -> liftE $ throwE @_ @'[DownloadFailed] (DownloadFailed (toVariantAt @0 (MalformedHeaders headers)
:: V '[MalformedHeaders])) :: V '[MalformedHeaders]))
writeEtags (parseEtags headers) writeEtags destFile (parseEtags headers)
else else
liftE $ lEM @_ @'[ProcessError] $ exec "curl" liftE $ lEM @_ @'[ProcessError] $ exec "curl"
(o' ++ ["-fL", "-o", destFile, T.unpack uri']) Nothing Nothing (o' ++ ["-fL", "-o", destFile, T.unpack uri']) Nothing Nothing
@@ -383,13 +395,13 @@ download uri eDigest dest mfn etags
case _exitCode of case _exitCode of
ExitSuccess -> do ExitSuccess -> do
liftIO $ copyFile destFileTemp destFile liftIO $ copyFile destFileTemp destFile
writeEtags (parseEtags (decUTF8Safe' _stdErr)) writeEtags destFile (parseEtags (decUTF8Safe' _stdErr))
ExitFailure i' ExitFailure i'
| i' == 8 | i' == 8
, Just _ <- find (T.pack "304 Not Modified" `T.isInfixOf`) . T.lines . decUTF8Safe' $ _stdErr , Just _ <- find (T.pack "304 Not Modified" `T.isInfixOf`) . T.lines . decUTF8Safe' $ _stdErr
-> do -> do
$logDebug "Not modified, skipping download" $logDebug "Not modified, skipping download"
writeEtags (parseEtags (decUTF8Safe' _stdErr)) writeEtags destFile (parseEtags (decUTF8Safe' _stdErr))
| otherwise -> throwE (NonZeroExit i' "wget" opts) | otherwise -> throwE (NonZeroExit i' "wget" opts)
else do else do
let opts = o' ++ ["-O", destFileTemp , T.unpack uri'] let opts = o' ++ ["-O", destFileTemp , T.unpack uri']
@@ -404,10 +416,10 @@ download uri eDigest dest mfn etags
let addHeaders = maybe mempty (\etag -> M.fromList [ (mk . E.encodeUtf8 . T.pack $ "If-None-Match" let addHeaders = maybe mempty (\etag -> M.fromList [ (mk . E.encodeUtf8 . T.pack $ "If-None-Match"
, E.encodeUtf8 etag)]) metag , E.encodeUtf8 etag)]) metag
liftE liftE
$ catchE @HTTPNotModified @'[DownloadFailed] @'[] (\(HTTPNotModified etag) -> lift $ writeEtags (pure $ Just etag)) $ catchE @HTTPNotModified @'[DownloadFailed] @'[] (\(HTTPNotModified etag) -> lift $ writeEtags destFile (pure $ Just etag))
$ do $ do
r <- downloadToFile https host fullPath port destFile addHeaders r <- downloadToFile https host fullPath port destFile addHeaders
writeEtags (pure $ decUTF8Safe <$> getHeader r "etag") writeEtags destFile (pure $ decUTF8Safe <$> getHeader r "etag")
else void $ liftE $ catchE @HTTPNotModified else void $ liftE $ catchE @HTTPNotModified
@'[DownloadFailed] @'[DownloadFailed]
(\e@(HTTPNotModified _) -> (\e@(HTTPNotModified _) ->
@@ -420,12 +432,18 @@ download uri eDigest dest mfn etags
-- Manage to find a file we can write the body into. -- Manage to find a file we can write the body into.
destFile :: FilePath getDestFile :: Monad m => Excepts '[NoUrlBase] m FilePath
destFile = maybe (dest </> T.unpack (decUTF8Safe (urlBaseName path))) getDestFile =
(dest </>) case mfn of
mfn Just fn -> pure (dest </> fn)
Nothing
| let urlBase = T.unpack (decUTF8Safe (urlBaseName path))
, not (null urlBase) -> pure (dest </> urlBase)
-- TODO: remove this once we use hpath again
| otherwise -> throwE $ NoUrlBase uri'
path = view pathL' uri path = view pathL' uri
uri' = decUTF8Safe (serializeURIRef' uri)
parseEtags :: (MonadLogger m, MonadIO m, MonadThrow m) => T.Text -> m (Maybe T.Text) parseEtags :: (MonadLogger m, MonadIO m, MonadThrow m) => T.Text -> m (Maybe T.Text)
parseEtags stderr = do parseEtags stderr = do
@@ -444,8 +462,8 @@ download uri eDigest dest mfn etags
$logDebug "No etags header found" $logDebug "No etags header found"
pure Nothing pure Nothing
writeEtags :: (MonadLogger m, MonadIO m, MonadThrow m) => m (Maybe T.Text) -> m () writeEtags :: (MonadLogger m, MonadIO m, MonadThrow m) => FilePath -> m (Maybe T.Text) -> m ()
writeEtags getTags = do writeEtags destFile getTags = do
getTags >>= \case getTags >>= \case
Just t -> do Just t -> do
$logDebug [i|Writing etagsFile #{(etagsFile destFile)}|] $logDebug [i|Writing etagsFile #{(etagsFile destFile)}|]

View File

@@ -327,6 +327,15 @@ instance Pretty UnexpectedListLength where
instance Exception UnexpectedListLength instance Exception UnexpectedListLength
data NoUrlBase = NoUrlBase Text
deriving Show
instance Pretty NoUrlBase where
pPrint (NoUrlBase url) =
text [i|Couldn't get a base filename from url #{url}|]
instance Exception NoUrlBase
------------------------ ------------------------