Compare commits

..

126 Commits

Author SHA1 Message Date
d2c5d4dfd9 Test that we're not missing GHCup alpine 2020-08-11 20:23:15 +02:00
6f1b8b4041 Fix build on 32bit 2020-08-11 19:47:29 +02:00
f63b2bf744 Fix CI 2020-08-11 11:48:42 +02:00
71cb75c170 Update .gitignore 2020-08-10 22:28:02 +02:00
dac64f5718 Make TarDir backwardscompat 2020-08-10 22:28:02 +02:00
27b2d2ac7d Fix cabal.project for cabal-3.4 2020-08-10 22:28:01 +02:00
47142dd376 Test on 32bit 2020-08-10 22:28:01 +02:00
d071a7e51b Avoid duplicate edits to .bashrc etc 2020-08-10 22:28:01 +02:00
5c45884f5f Allow to specify regex for subdir 2020-08-10 22:27:50 +02:00
cafedd73a2 Use Settings to avoid querying dirs every time 2020-08-10 21:52:30 +02:00
7163b77837 Only query directories once 2020-08-10 21:51:31 +02:00
122c54b51e Refactor 2020-08-10 21:51:31 +02:00
b9d7d7d007 Fix licences in module haddock 2020-08-10 21:51:29 +02:00
9050c9792a Improve bootstrap-haskell 2020-08-10 21:51:20 +02:00
aac8f760ad Add xdg support wrt #39 2020-08-10 21:51:19 +02:00
7d334c18f5 Don't stop TUI on subcommand failure 2020-08-10 21:51:19 +02:00
86b0e4b31b Fix cabalSet for pre-release versions 2020-08-10 21:51:19 +02:00
af811f3dbc nub result in getInstalledCabals 2020-08-10 21:51:19 +02:00
d30d2ac8a5 Add cabal-install-3.4.0.0-rc1 2020-08-10 21:51:00 +02:00
86a4b10de6 Merge branch 'yaml' 2020-08-10 08:58:55 +02:00
cfa7049ab9 Merge branch 'CI' 2020-08-09 22:11:48 +02:00
391676e90a Use yaml instead of pesky json 2020-08-09 21:56:11 +02:00
34e4ece8b5 Merge branch 'ghc-8.10.2' 2020-08-09 16:14:04 +02:00
cf6443d83f Add GHC-8.10.2 2020-08-09 15:53:56 +02:00
846cf92fa4 Add GHC-8.10.2 2020-08-09 15:49:08 +02:00
ab568901f8 Try to build for El Capitan 2020-07-22 22:50:13 +02:00
bfda95c0d6 Fix haskus-utils-types build with GHC-8.10.1 2020-07-22 02:35:22 +02:00
fb1875ee5b Doc fixes 2020-07-22 02:34:17 +02:00
185d4f869b Remove unnecessary bundles 2020-07-22 01:37:15 +02:00
2ac8b61aa8 Fix bootstrap-haskell when ghcup is a broken script
Fixes #28
2020-07-22 01:25:12 +02:00
8739cb4656 Enable haddock building in CI 2020-07-22 01:11:37 +02:00
826900cc41 Improve documentation 2020-07-22 01:08:58 +02:00
ec6bbdbf06 Update ghcup binaries 2020-07-22 00:18:31 +02:00
15a188c501 Update FreeBSD 8.10.1 2020-07-22 00:17:14 +02:00
b5440fc7d2 Fix bug in installCabalBin 2020-07-21 23:10:47 +02:00
4b21adadf1 Release 0.1.8 2020-07-21 22:47:21 +02:00
78ae77780b Fix bug in logging thread
It would die on newlines due to empty String blindness.
Also make sure takeMVar does not block.
2020-07-21 22:43:09 +02:00
ccb95bcbee f custom 2020-07-21 22:42:39 +02:00
21ac670bbe Update FreeBSD bindist 2020-07-21 21:08:41 +02:00
8b54dee66c Merge branch 'CI-m' 2020-07-21 20:21:53 +02:00
dad926f3ba Allow to specify custom bindist, fixes #14 2020-07-21 20:19:33 +02:00
a298d949b5 Remove FreeBSD workaround in CI 2020-07-21 19:00:10 +02:00
e1cf11f9d4 Add Alpine GHC 8.4.4 for 64bit 2020-07-21 01:18:03 +02:00
a046f16308 Remove libtinfo compat symlinks 2020-07-21 00:36:31 +02:00
97cd43792d Set 8.8.4 as recommended GHC version 2020-07-21 00:31:02 +02:00
08693e6d3a Add more alpine bindists 2020-07-21 00:29:46 +02:00
e2227da8d2 Update ghcup binaries 2020-07-21 00:02:14 +02:00
5f20e4c583 Bump versions in CI 2020-07-20 23:57:26 +02:00
f82f1a12dd Update version in bootstrap-script 2020-07-20 23:04:11 +02:00
53148fd1c9 Release 0.1.7 2020-07-20 22:25:28 +02:00
8985101b2a Use 8.8.4 for test build 2020-07-20 22:25:09 +02:00
d1949c8490 Use available bindists for release build 2020-07-20 22:24:21 +02:00
b7faae1203 Add more alpine bindists 2020-07-20 22:20:24 +02:00
b6a9d35c3e Merge branch 'alpine-bindists' 2020-07-20 20:59:08 +02:00
6cb6c7a448 Install alpine bindists with --disable-ld-override 2020-07-20 20:48:22 +02:00
22a5ad739e Don't try non-musl bindists for Alpine Linux 2020-07-20 20:47:45 +02:00
14c91bdd78 Merge branch 'libarchive-fix' 2020-07-20 20:34:54 +02:00
2c638cd2e2 Fix unpacking of bindists with hardinks pointing to themselves
https://github.com/vmchale/libarchive/issues/14
https://github.com/libarchive/libarchive/issues/1381
2020-07-20 20:34:36 +02:00
9e59f484e3 Fix alpine bindists 2020-07-20 20:30:47 +02:00
ac8419ecb2 Fix platform detection for i386 docker images wrt #37 2020-07-20 20:30:36 +02:00
3ecdb63063 Update tarballs 2020-07-19 00:50:57 +02:00
cfe24428fa Only check for upgrades when not upgrading 2020-07-19 00:47:20 +02:00
4c4266dd8c Add GHC-8.8.4 for FreeBSD 2020-07-16 14:38:19 +02:00
e8336bbc8a Add GHC-8.8.4 2020-07-16 10:57:44 +02:00
0f69c73e0e Rework printing/tee
This should be faster to draw, use less syscalls
and generally use EOF and pipes correctly.
2020-07-16 00:10:27 +02:00
e348de8dc4 Drop unused error variants 2020-07-14 19:16:01 +02:00
55a3ba9be2 Improve fish support in bootstrap-haskell 2020-07-14 19:08:20 +02:00
51b29b81b0 Use new-style ghcup commands in bootstrap-haskell 2020-07-14 19:07:57 +02:00
3c2e0334b7 Update ghcup binary urls 2020-07-14 19:07:19 +02:00
0679626514 Self host ghcup binaries 2020-07-14 14:29:23 +02:00
5035051135 Update 0.1.6 2020-07-13 23:50:11 +02:00
63c70ee74b Fix changelog subcommand on darwin 2020-07-13 23:10:17 +02:00
2e0bbca2e0 Fix freebsd tui 2020-07-13 22:45:38 +02:00
b52fa23ca2 Update alpine install-deps 2020-07-13 22:34:38 +02:00
ba03b78f23 Update ghcup binaries 2020-07-13 22:15:39 +02:00
04ef472c15 Fix freebsd gitlab-ci 2020-07-13 21:29:13 +02:00
75cd8f2341 Fix stripping in travis 2020-07-13 21:17:01 +02:00
f2e26c1800 Fix release build 2020-07-13 20:48:37 +02:00
0f7dd597d2 Update .gitignore 2020-07-13 20:37:34 +02:00
fb0eba9201 Release 0.1.6 2020-07-13 20:31:14 +02:00
3c80929c38 Merge branch 'ghc-8.10.1' 2020-07-13 20:06:17 +02:00
b184ee835f Add freebsd 8.6.5 bindist 2020-07-13 20:05:02 +02:00
ef8e3bd940 Reduce number of os/dl lookups 2020-07-13 18:27:21 +02:00
1a64527e14 Improve verbosity 2020-07-13 16:27:01 +02:00
30b4d399b9 Add -ftui to travis build 2020-07-13 16:26:54 +02:00
50424c2801 Allow to build with tar-bytestring on e.g. 32bit 2020-07-13 15:41:31 +02:00
7e7c357e47 Fix freebsd CI 2020-07-13 15:41:30 +02:00
531b82a406 Add ghc-8.8.3 freebsd bindist 2020-07-13 15:41:30 +02:00
146ac38549 Add 8.10.1 freebsd bindist 2020-07-12 16:48:25 +02:00
c481956b07 Bump GHC versions in CI 2020-07-11 23:03:04 +02:00
8ef19f0825 Allow to build with ghc-8.10.1 and 8.6.5 2020-07-11 22:53:38 +02:00
c1e29a8f16 Rm bundled os-release 2020-07-11 21:25:45 +02:00
c3611eec6a Grey out versions without bindists in tui 2020-07-11 18:53:11 +02:00
74b58db7d1 Merge branch 'tui' 2020-07-11 13:32:34 +02:00
9e4763c640 Merge branch 'www-wsl' 2020-07-11 00:00:53 +02:00
abc4278fc8 Improve style 2020-07-10 23:55:01 +02:00
8c4cde3d14 Recommend the curl command for potential WSL users on website
Fixes #32
2020-07-10 23:53:55 +02:00
3824f6417a Update libarchive 2020-07-10 22:44:16 +02:00
Ben Gamari
2be1aa2707 Simplify upgrade copying logic 2020-07-10 22:03:04 +02:00
da94fa5f92 Create brick tui wrt #24 2020-07-10 21:55:12 +02:00
35bf9b5ff2 Merge branch 'libarchive' 2020-07-05 23:33:47 +02:00
bed2cca8d2 Use libarchive instead of tar-bytestring 2020-07-05 23:03:24 +02:00
9717a1c00f Use os-release package 2020-07-04 23:28:30 +02:00
3ddc719d8a Fix quasi quotes in Main 2020-07-04 21:49:59 +02:00
4b89810892 Rm unused functions 2020-07-04 21:20:08 +02:00
b82367838d Fix memory leak 2020-07-04 21:18:51 +02:00
dd7556ba21 Merge branch 'less-bash' 2020-06-27 21:38:12 +02:00
f4b6bfc594 Merge branch 'better-platform' 2020-06-27 21:37:45 +02:00
f9251589cd Add some architectures 2020-06-27 19:00:13 +02:00
2de549862a Get rid of language-bash
And clean up detection logic a bit. We also don't
read /etc/lsb-release manually more, since it's format is
not specified.
2020-06-27 18:54:20 +02:00
c502f70f68 Update DOCKER_REV 2020-06-27 01:38:38 +02:00
cbf076740a Merge branch 'hadrian' 2020-06-20 18:59:01 +02:00
86c144b285 Merge remote-tracking branch 'remotes/origin/merge-requests/12' into hadrian 2020-06-20 14:40:47 +02:00
7ec6e8604c Slight style changes 2020-06-20 14:37:38 +02:00
de70f4820f Merge remote-tracking branch 'origin/merge-requests/13' into hadrian 2020-06-20 12:39:21 +02:00
Brian McKenna
febe6fcb35 Fix behaviour of non-Hadrian builds
getFileStatus will resolve symbolic links. getSymbolicLinkStatus doesn't.
2020-06-20 03:38:41 +00:00
Brian McKenna
3055529d4c Update GHCupDownloads with ghcup-0.0.2.json content 2020-06-19 23:17:34 +00:00
Brian McKenna
d276bfb3ec Extract Hadrian logic to isHadrian function with comment 2020-06-19 23:06:46 +00:00
9db0664465 Add hie.yaml 2020-06-19 19:44:30 +02:00
e9c727647a Update .gitigore 2020-06-19 19:42:55 +02:00
55eef8a3d3 Merge branch 'redhat' 2020-06-19 19:22:56 +02:00
d07ad3eb26 Update ghcup-0.0.2.json with redhat wrt #29 2020-06-19 10:49:31 +02:00
Sigmund Vestergaard
ad53b141c7 Removed reference to specific version of RHEL in GHCupDownloads.hs 2020-06-19 10:49:31 +02:00
Sigmund Vestergaard
23c13a07a9 Added support for RedHat in lib/GHCup/Data/GHCupDownloads.hs 2020-06-19 10:49:31 +02:00
Brian McKenna
a186b07763 Support Hadrian provided bindists
Fixes #31
2020-06-18 14:03:51 +00:00
puffnfresh
1ca628aba1 Fix dlSubdir of ghc-8.10.1-x86_64-unknown-linux for Linux_Alpine 2020-06-18 07:52:34 -04:00
46 changed files with 3287 additions and 5333 deletions

11
.gitignore vendored
View File

@@ -1,4 +1,15 @@
.ghci
.vim
codex.tags
dist-newstyle/ dist-newstyle/
cabal.project.local cabal.project.local
.stack-work/ .stack-work/
bin/ bin/
/*.prof
/*.ps
/*.hp
tags
TAGS
/tmp/
.entangled
release/

View File

@@ -2,7 +2,7 @@ variables:
GIT_SSL_NO_VERIFY: "1" GIT_SSL_NO_VERIFY: "1"
# Commit of ghc/ci-images repository from which to pull Docker images # Commit of ghc/ci-images repository from which to pull Docker images
DOCKER_REV: cefaee3c742af193e0f7783f87edb0d35374515c DOCKER_REV: 1ac7f435c9312f10422a82d304194778378e2a1a
############################################################ ############################################################
# CI Step # CI Step
@@ -14,6 +14,7 @@ variables:
- x86_64-linux - x86_64-linux
variables: variables:
OS: "LINUX" OS: "LINUX"
BIT: "64"
.alpine:64bit: .alpine:64bit:
image: "alpine:edge" image: "alpine:edge"
@@ -36,12 +37,14 @@ variables:
- x86_64-darwin - x86_64-darwin
variables: variables:
OS: "DARWIN" OS: "DARWIN"
BIT: "64"
.freebsd: .freebsd:
tags: tags:
- x86_64-freebsd - x86_64-freebsd
variables: variables:
OS: "FREEBSD" OS: "FREEBSD"
BIT: "64"
.root_cleanup: .root_cleanup:
after_script: after_script:
@@ -66,6 +69,13 @@ variables:
before_script: before_script:
- ./.gitlab/before_script/linux/install_deps.sh - ./.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: .test_ghcup_version:darwin:
extends: extends:
- .test_ghcup_version - .test_ghcup_version
@@ -97,7 +107,7 @@ variables:
test:linux:recommended: test:linux:recommended:
extends: .test_ghcup_version:linux extends: .test_ghcup_version:linux
variables: variables:
GHC_VERSION: "8.8.3" GHC_VERSION: "8.8.4"
CABAL_VERSION: "3.2.0.0" CABAL_VERSION: "3.2.0.0"
test:linux:latest: test:linux:latest:
@@ -107,13 +117,20 @@ test:linux:latest:
CABAL_VERSION: "3.2.0.0" CABAL_VERSION: "3.2.0.0"
allow_failure: true 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 ######## ######## darwin test ########
test:mac:recommended: test:mac:recommended:
extends: .test_ghcup_version:darwin extends: .test_ghcup_version:darwin
variables: variables:
GHC_VERSION: "8.8.3" GHC_VERSION: "8.8.4"
CABAL_VERSION: "3.2.0.0" CABAL_VERSION: "3.2.0.0"
test:mac:latest: test:mac:latest:
@@ -129,7 +146,7 @@ test:mac:latest:
test:freebsd:recommended: test:freebsd:recommended:
extends: .test_ghcup_version:freebsd extends: .test_ghcup_version:freebsd
variables: variables:
GHC_VERSION: "8.8.3" GHC_VERSION: "8.8.4"
CABAL_VERSION: "3.2.0.0" CABAL_VERSION: "3.2.0.0"
test:freebsd:latest: test:freebsd:latest:
@@ -150,7 +167,7 @@ release:linux:64bit:
- ./.gitlab/before_script/linux/alpine/install_deps.sh - ./.gitlab/before_script/linux/alpine/install_deps.sh
variables: variables:
ARTIFACT: "x86_64-linux-ghcup" ARTIFACT: "x86_64-linux-ghcup"
GHC_VERSION: "8.8.3" GHC_VERSION: "8.8.4"
CABAL_VERSION: "3.2.0.0" CABAL_VERSION: "3.2.0.0"
@@ -162,7 +179,7 @@ release:linux:32bit:
- ./.gitlab/before_script/linux/alpine/install_deps.sh - ./.gitlab/before_script/linux/alpine/install_deps.sh
variables: variables:
ARTIFACT: "i386-linux-ghcup" ARTIFACT: "i386-linux-ghcup"
GHC_VERSION: "8.8.3" GHC_VERSION: "8.8.4"
CABAL_VERSION: "3.2.0.0" CABAL_VERSION: "3.2.0.0"
@@ -194,4 +211,5 @@ release:freebsd:
variables: variables:
ARTIFACT: "x86_64-portbld-freebsd-ghcup" ARTIFACT: "x86_64-portbld-freebsd-ghcup"
GHC_VERSION: "8.8.3" GHC_VERSION: "8.8.3"
CABAL_VERSION: "3.2.0.0"

View File

@@ -12,7 +12,7 @@ curl -sSfL https://downloads.haskell.org/~ghcup/x86_64-portbld-freebsd-ghcup > .
chmod +x ghcup-bin chmod +x ghcup-bin
./ghcup-bin install ${GHC_VERSION} ./ghcup-bin install ${GHC_VERSION}
./ghcup-bin set ${GHC_VERSION}
./ghcup-bin install-cabal ${CABAL_VERSION} ./ghcup-bin install-cabal ${CABAL_VERSION}
./ghcup-bin set ${GHC_VERSION}
exit 0 exit 0

View File

@@ -18,29 +18,14 @@ apk add --no-cache \
tar \ tar \
perl perl
ln -s libncurses.so /usr/lib/libtinfo.so
ln -s libncursesw.so.6 /usr/lib/libtinfow.so.6
if [ "${BIT}" = "32" ] ; then if [ "${BIT}" = "32" ] ; then
curl -sSfL https://downloads.haskell.org/~ghcup/0.1.4/i386-linux-ghcup-0.1.4 > ./ghcup-bin curl -sSfL https://downloads.haskell.org/ghcup/i386-linux-ghcup > ./ghcup-bin
else else
curl -sSfL https://downloads.haskell.org/~ghcup/0.1.4/x86_64-linux-ghcup-0.1.4 > ./ghcup-bin curl -sSfL https://downloads.haskell.org/ghcup/x86_64-linux-ghcup > ./ghcup-bin
fi fi
chmod +x ghcup-bin chmod +x ghcup-bin
./ghcup-bin upgrade
./ghcup-bin install ${GHC_VERSION} ./ghcup-bin install ${GHC_VERSION}
# ./ghcup-bin install-cabal ${CABAL_VERSION} ./ghcup-bin install-cabal ${CABAL_VERSION}
# install cabal-3.2.0.0
if [ "${BIT}" = "32" ] ; then
curl -sSfL -o cabal-install-3.2.0.0-i386-alpine-linux-musl.tar.xz 'https://hasufell.de/d/d3e215db133e4fcaa61e/files/?p=/cabal-install-3.2.0.0-i386-alpine-linux-musl.tar.xz&dl=1'
tar xf cabal-install-3.2.0.0-i386-alpine-linux-musl.tar.xz
cp cabal-install-3.2.0.0-i386-alpine-linux-musl "${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin/cabal"
else
curl -sSfL -o cabal-install-3.2.0.0-x86_64-alpine-linux-musl.tar.xz 'https://hasufell.de/d/d3e215db133e4fcaa61e/files/?p=/cabal-install-3.2.0.0-x86_64-alpine-linux-musl.tar.xz&dl=1'
tar xf cabal-install-3.2.0.0-x86_64-alpine-linux-musl.tar.xz
cp cabal-install-3.2.0.0-x86_64-alpine-linux-musl "${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin/cabal"
fi
chmod +x "${GHCUP_INSTALL_BASE_PREFIX}/.ghcup/bin/cabal"
# utils # utils
apk add --no-cache \ apk add --no-cache \
@@ -57,7 +42,6 @@ apk add --no-cache \
openssl-dev \ openssl-dev \
openssl-libs-static \ openssl-libs-static \
xz \ xz \
xz-dev xz-dev \
ncurses-static

View File

@@ -16,16 +16,24 @@ git describe
ecabal update ecabal update
if [ "${OS}" = "LINUX" ] ; then if [ "${OS}" = "LINUX" ] ; then
ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections -optl-static' if [ "${BIT}" = "32" ] ; then
ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections -optl-static' -ftui -ftar
else
ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections -optl-static' -ftui
fi
elif [ "${OS}" = "FREEBSD" ] ; then elif [ "${OS}" = "FREEBSD" ] ; then
ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections' --constraint="zlib static" ecabal build -w ghc-${GHC_VERSION} --ghc-options='-split-sections' --constraint="zlib static" -ftui
else else
ecabal build -w ghc-${GHC_VERSION} --constraint="zlib static" --constraint="lzma static" ecabal build -w ghc-${GHC_VERSION} --constraint="zlib static" --constraint="lzma static" -ftui
fi fi
mkdir out mkdir out
cp "$(ecabal new-exec -w ghc-${GHC_VERSION} --verbose=0 --offline sh -- -c 'command -v ghcup')" . cp "$(ecabal new-exec -w ghc-${GHC_VERSION} --verbose=0 --offline sh -- -c 'command -v ghcup')" .
ver=$(./ghcup --numeric-version) ver=$(./ghcup --numeric-version)
strip -s ./ghcup if [ "${OS}" = "DARWIN" ] ; then
strip ./ghcup
else
strip -s ./ghcup
fi
cp ghcup out/${ARTIFACT}-${ver} cp ghcup out/${ARTIFACT}-${ver}

View File

@@ -11,7 +11,7 @@ ecabal() {
} }
eghcup() { eghcup() {
ghcup -v -c -s file://$(pwd)/ghcup-${JSON_VERSION}.json "$@" ghcup -v -c -s file://$(pwd)/ghcup-${JSON_VERSION}.yaml "$@"
} }
git describe --always git describe --always
@@ -21,13 +21,21 @@ git describe --always
ecabal update ecabal update
if [ "${OS}" = "DARWIN" ] ; then if [ "${OS}" = "DARWIN" ] ; then
ecabal build -w ghc-${GHC_VERSION} 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 else
ecabal build -w ghc-${GHC_VERSION} -finternal-downloader ecabal build -w ghc-${GHC_VERSION} -finternal-downloader -ftui
fi fi
cp "$(ecabal new-exec --enable-tests --verbose=0 --offline sh -- -c 'command -v ghcup')" . ecabal haddock -w ghc-${GHC_VERSION} -ftar
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 "$CI_PROJECT_DIR"/.local/bin/ghcup
cp ./ghcup-gen "$CI_PROJECT_DIR"/.local/bin/ghcup-gen cp ./ghcup-gen "$CI_PROJECT_DIR"/.local/bin/ghcup-gen
@@ -40,16 +48,10 @@ rm -rf "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup
### manual cli based testing ### manual cli based testing
ghcup-gen check -f ghcup-${JSON_VERSION}.json ghcup-gen check -f ghcup-${JSON_VERSION}.yaml
eghcup --numeric-version eghcup --numeric-version
# TODO: rm once we have tarballs
if [ "${OS}" = "FREEBSD" ] ; then
GHC_VERSION=8.6.3
CABAL_VERSION=2.4.1.0
fi
eghcup install ${GHC_VERSION} eghcup install ${GHC_VERSION}
eghcup set ${GHC_VERSION} eghcup set ${GHC_VERSION}
eghcup install-cabal ${CABAL_VERSION} eghcup install-cabal ${CABAL_VERSION}

View File

@@ -1,5 +1,10 @@
jobs: jobs:
include: include:
- os: osx
osx_image: xcode8
language: generic
env: ARTIFACT=x86_64-apple-darwin-10.11-ghcup
- os: osx - os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: generic language: generic
@@ -10,6 +15,13 @@ jobs:
language: generic language: generic
env: ARTIFACT=x86_64-apple-darwin-10.14-ghcup env: ARTIFACT=x86_64-apple-darwin-10.14-ghcup
allow_failures:
- os: osx
osx_image: xcode8
language: generic
env: ARTIFACT=x86_64-apple-darwin-10.11-ghcup
script: ".travis/build.sh" script: ".travis/build.sh"
deploy: deploy:

View File

@@ -18,7 +18,7 @@ ghcup set 8.8.3
## install ghcup ## install ghcup
cabal update cabal update
cabal build --constraint="zlib static" --constraint="lzma static" cabal build --constraint="zlib static" --constraint="lzma static" -ftui
cp "$(cabal new-exec --verbose=0 --offline sh -- -c 'command -v ghcup')" . cp "$(cabal new-exec --verbose=0 --offline sh -- -c 'command -v ghcup')" .
strip -s ghcup strip ./ghcup
cp ghcup "./${ARTIFACT}" cp ghcup "./${ARTIFACT}"

View File

@@ -86,7 +86,7 @@ test-suite lzma-tests
-- additional dependencies that require version bounds -- additional dependencies that require version bounds
build-depends: HUnit >= 1.2 && <1.7 build-depends: HUnit >= 1.2 && <1.7
, QuickCheck >= 2.8 && <2.14 , QuickCheck >= 2.8 && <2.14
, tasty >= 0.10 && <1.3 , tasty >= 0.10 && <1.4
, tasty-hunit >= 0.9 && <0.11 , tasty-hunit >= 0.9 && <0.11
, tasty-quickcheck >= 0.8.3.2 && <0.11 , tasty-quickcheck >= 0.8.3.2 && <0.11

View File

@@ -110,7 +110,7 @@ test-suite tests
default-language: Haskell2010 default-language: Haskell2010
build-depends: base, bytestring, zlib, build-depends: base, bytestring, zlib,
QuickCheck == 2.*, QuickCheck == 2.*,
tasty >= 0.8 && < 1.3, tasty >= 0.8 && < 1.4,
tasty-quickcheck >= 0.8 && < 0.11, tasty-quickcheck >= 0.8 && < 0.11,
tasty-hunit >= 0.8 && < 0.11 tasty-hunit >= 0.8 && < 0.11
ghc-options: -Wall ghc-options: -Wall

View File

@@ -1,5 +1,31 @@
# Revision history for ghcup # Revision history for ghcup
## 0.1.8 -- 2020-07-21
* Fix bug in logging thread dying on newlines
* Allow to install from arbitrary bindists: `ghcup -n install ghc -u '{"dlHash": "", "dlSubdir": "ghc-8.10.1", "dlUri": "https://github.com/commercialhaskell/ghc/releases/download/ghc-8.10.1-release/ghc-8.10.1-x86_64-deb9-linux.tar.xz"}' 8.10.1`
## 0.1.7 -- 2020-07-20
* Fix a bug in libarchive not unpacking some uncleanly packed bindists
* Improved fish support in bootstrap-haskell
* Only check for upgrades when not upgrading
* Fix platform detection for i386 docker images
* Improve alpine support
- more/proper bindists
- don't fall back to glibc based bindists
- install bindists with `--disable-ld-override` to avoid ld.gold bugs
## 0.1.6 -- 2020-07-13
* Create a new curses (brick) based TUI, accessible via `ghcup tui` #24
* Support multiple installed versions of cabal #23
* Improvements to `ghcup list` (show unavailable bindists for platform)
* Fix redhat downloads #29
* Support for hadrian bindists (fixes alpine-8.10.1) #31
* Add FreeBSD bindists 8.6.5 and 8.8.3
* Fix memory leak during unpack
## 0.1.5 -- 2020-04-30 ## 0.1.5 -- 2020-04-30
* Fix errors when PATH variable contains path components that are actually files * Fix errors when PATH variable contains path components that are actually files

View File

@@ -13,6 +13,7 @@ Similar in scope to [rustup](https://github.com/rust-lang-nursery/rustup.rs), [p
* [Manpages](#manpages) * [Manpages](#manpages)
* [Shell-completion](#shell-completion) * [Shell-completion](#shell-completion)
* [Cross support](#cross-support) * [Cross support](#cross-support)
* [XDG support](#xdg-support)
* [Design goals](#design-goals) * [Design goals](#design-goals)
* [How](#how) * [How](#how)
* [Known users](#known-users) * [Known users](#known-users)
@@ -40,7 +41,13 @@ export PATH="$HOME/.cabal/bin:$HOME/.ghcup/bin:$PATH"
See `ghcup --help`. See `ghcup --help`.
Common use cases are: For the simple interactive TUI, run:
```sh
ghcup tui
```
For the full functionality via cli:
```sh ```sh
# list available ghc/cabal versions # list available ghc/cabal versions
@@ -90,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. libraries, this may need some tweaking of `build.mk` or configure args.
See `ghcup compile ghc --help` for further information. 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 ## Design goals
1. simplicity 1. simplicity

View File

@@ -1,19 +1,19 @@
# RELEASING # RELEASING
1. update `GHCup.Version` module. `ghcupURL` must only be updated if we change the `_toolRequirements` type or the JSON representation of it. The version of the json represents the change increments. `ghcUpVer` is the current application version. 1. update `GHCup.Version` module. `ghcupURL` must only be updated if we change the `_toolRequirements` type or the YAML representation of it. The version of the YAML represents the change increments. `ghcUpVer` is the current application version.
2. Update version in ghcup.cabal 2. Update version in ghcup.cabal
3. Add ChangeLog entry 3. Add ChangeLog entry
4. Add/fix downloads to `GHCupDownloads` module, then run `ghcup-gen gen` to generate the new json and validate it via `ghcup-gen check`. 4. Add/fix downloads in `ghcup-<ver>.yaml`, then verify with `ghcup-gen check -f ghcup-<ver>.yaml`
5. Commit and git push with tag. Wait for tests to succeed and release artifacts to build. 5. Commit and git push with tag. Wait for tests to succeed and release artifacts to build.
6. Download release artifacts and upload them `downloads.haskell.org/ghcup` 6. Download release artifacts and upload them `downloads.haskell.org/ghcup`
7. Add release artifacts to GHCupDownloads (see point 4.) 7. Add release artifacts to yaml file (see point 4.)
8. Upload the final `ghcup-<ver>.json` to `webhost.haskell.org/ghcup/data/`. 8. Upload the final `ghcup-<ver>.yaml` to `webhost.haskell.org/ghcup/data/`.
9. Update bootstrap-haskell and symlinks on `downloads.haskell.org/ghcup` 9. Update bootstrap-haskell and symlinks on `downloads.haskell.org/ghcup`

View File

@@ -10,13 +10,10 @@
module Main where module Main where
import GHCup.Data.GHCupInfo
import GHCup.Types import GHCup.Types
import GHCup.Types.JSON ( ) import GHCup.Types.JSON ( )
import GHCup.Utils.Logger import GHCup.Utils.Logger
import Data.Aeson ( eitherDecode, encode )
import Data.Aeson.Encode.Pretty
#if !MIN_VERSION_base(4,13,0) #if !MIN_VERSION_base(4,13,0)
import Data.Semigroup ( (<>) ) import Data.Semigroup ( (<>) )
#endif #endif
@@ -27,48 +24,15 @@ import System.IO ( stdout )
import Validate import Validate
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L import qualified Data.Yaml as Y
data Options = Options data Options = Options
{ optCommand :: Command { optCommand :: Command
} }
data Command = GenJSON GenJSONOpts data Command = ValidateYAML ValidateYAMLOpts
| ValidateJSON ValidateJSONOpts | ValidateTarballs ValidateYAMLOpts
| ValidateTarballs ValidateJSONOpts
data Output
= FileOutput FilePath -- optsparse-applicative doesn't handle ByteString correctly anyway
| StdOutput
fileOutput :: Parser Output
fileOutput =
FileOutput
<$> (strOption
(long "file" <> short 'f' <> metavar "FILENAME" <> help
"Output to a file"
)
)
stdOutput :: Parser Output
stdOutput = flag'
StdOutput
(short 'o' <> long "stdout" <> help "Print to stdout (default)")
outputP :: Parser Output
outputP = fileOutput <|> stdOutput
data GenJSONOpts = GenJSONOpts
{ output :: Maybe Output
, pretty :: Bool
}
genJSONOpts :: Parser GenJSONOpts
genJSONOpts = GenJSONOpts <$> optional outputP <*> switch
(short 'p' <> long "pretty" <> help "Make JSON output pretty (human readable)"
)
data Input data Input
@@ -92,12 +56,12 @@ stdInput = flag'
inputP :: Parser Input inputP :: Parser Input
inputP = fileInput <|> stdInput inputP = fileInput <|> stdInput
data ValidateJSONOpts = ValidateJSONOpts data ValidateYAMLOpts = ValidateYAMLOpts
{ input :: Maybe Input { vInput :: Maybe Input
} }
validateJSONOpts :: Parser ValidateJSONOpts validateYAMLOpts :: Parser ValidateYAMLOpts
validateJSONOpts = ValidateJSONOpts <$> optional inputP validateYAMLOpts = ValidateYAMLOpts <$> optional inputP
opts :: Parser Options opts :: Parser Options
opts = Options <$> com opts = Options <$> com
@@ -105,18 +69,10 @@ opts = Options <$> com
com :: Parser Command com :: Parser Command
com = subparser com = subparser
( (command ( (command
"gen"
( GenJSON
<$> (info (genJSONOpts <**> helper)
(progDesc "Generate the json downloads file")
)
)
)
<> (command
"check" "check"
( ValidateJSON ( ValidateYAML
<$> (info (validateJSONOpts <**> helper) <$> (info (validateYAMLOpts <**> helper)
(progDesc "Validate the JSON") (progDesc "Validate the YAML")
) )
) )
) )
@@ -124,7 +80,7 @@ com = subparser
"check-tarballs" "check-tarballs"
( ValidateTarballs ( ValidateTarballs
<$> (info <$> (info
(validateJSONOpts <**> helper) (validateYAMLOpts <**> helper)
(progDesc "Validate all tarballs (download and checksum)") (progDesc "Validate all tarballs (download and checksum)")
) )
) )
@@ -135,38 +91,27 @@ com = subparser
main :: IO () main :: IO ()
main = do main = do
customExecParser (prefs showHelpOnError) (info (opts <**> helper) idm) _ <- customExecParser (prefs showHelpOnError) (info (opts <**> helper) idm)
>>= \Options {..} -> case optCommand of >>= \Options {..} -> case optCommand of
GenJSON gopts -> do ValidateYAML vopts -> case vopts of
let bs True = ValidateYAMLOpts { vInput = Nothing } ->
encodePretty' (defConfig { confIndent = Spaces 2 }) ghcupInfo B.getContents >>= valAndExit validate
bs False = encode ghcupInfo ValidateYAMLOpts { vInput = Just StdInput } ->
case gopts of B.getContents >>= valAndExit validate
GenJSONOpts { output = Nothing, pretty } -> ValidateYAMLOpts { vInput = Just (FileInput file) } ->
L.hPutStr stdout (bs pretty) B.readFile file >>= valAndExit validate
GenJSONOpts { output = Just StdOutput, pretty } ->
L.hPutStr stdout (bs pretty)
GenJSONOpts { output = Just (FileOutput file), pretty } ->
L.writeFile file (bs pretty)
ValidateJSON vopts -> case vopts of
ValidateJSONOpts { input = Nothing } ->
L.getContents >>= valAndExit validate
ValidateJSONOpts { input = Just StdInput } ->
L.getContents >>= valAndExit validate
ValidateJSONOpts { input = Just (FileInput file) } ->
L.readFile file >>= valAndExit validate
ValidateTarballs vopts -> case vopts of ValidateTarballs vopts -> case vopts of
ValidateJSONOpts { input = Nothing } -> ValidateYAMLOpts { vInput = Nothing } ->
L.getContents >>= valAndExit validateTarballs B.getContents >>= valAndExit validateTarballs
ValidateJSONOpts { input = Just StdInput } -> ValidateYAMLOpts { vInput = Just StdInput } ->
L.getContents >>= valAndExit validateTarballs B.getContents >>= valAndExit validateTarballs
ValidateJSONOpts { input = Just (FileInput file) } -> ValidateYAMLOpts { vInput = Just (FileInput file) } ->
L.readFile file >>= valAndExit validateTarballs B.readFile file >>= valAndExit validateTarballs
pure () pure ()
where where
valAndExit f contents = do valAndExit f contents = do
(GHCupInfo _ av) <- case eitherDecode contents of (GHCupInfo _ av) <- case Y.decodeEither' contents of
Right r -> pure r Right r -> pure r
Left e -> die (color Red $ show e) Left e -> die (color Red $ show e)
myLoggerT (LoggerConfig True (B.hPut stdout) (\_ -> pure ())) (f av) myLoggerT (LoggerConfig True (B.hPut stdout) (\_ -> pure ())) (f av)

View File

@@ -7,7 +7,9 @@ module Validate where
import GHCup import GHCup
import GHCup.Download import GHCup.Download
import GHCup.Types import GHCup.Types
import GHCup.Utils.Dirs
import GHCup.Utils.Logger import GHCup.Utils.Logger
import GHCup.Utils.Version.QQ
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad import Control.Monad
@@ -88,6 +90,15 @@ validate dls = do
when ((not $ any (== FreeBSD) pspecs) && arch == A_64) $ lift $ $(logWarn) when ((not $ any (== FreeBSD) pspecs) && arch == A_64) $ lift $ $(logWarn)
[i|FreeBSD missing for #{t} #{v'} #{arch}|] [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 checkUniqueTags tool = do
let allTags = join $ M.elems $ availableToolVersions dls tool let allTags = join $ M.elems $ availableToolVersions dls tool
let nonUnique = let nonUnique =
@@ -111,6 +122,7 @@ validate dls = do
where where
isUniqueTag Latest = True isUniqueTag Latest = True
isUniqueTag Recommended = True isUniqueTag Recommended = True
isUniqueTag Prerelease = False
isUniqueTag (Base _) = False isUniqueTag (Base _) = False
isUniqueTag (UnknownTag _) = False isUniqueTag (UnknownTag _) = False
@@ -179,7 +191,8 @@ validateTarballs dls = do
where where
downloadAll dli = do downloadAll dli = do
let settings = Settings True False Never Curl dirs <- liftIO getDirs
let settings = Settings True False Never Curl False dirs
let runLogger = myLoggerT LoggerConfig { lcPrintDebug = True let runLogger = myLoggerT LoggerConfig { lcPrintDebug = True
, colorOutter = B.hPut stderr , colorOutter = B.hPut stderr
, rawOutter = (\_ -> pure ()) , rawOutter = (\_ -> pure ())

366
app/ghcup/BrickMain.hs Normal file
View File

@@ -0,0 +1,366 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
module BrickMain where
import GHCup
import GHCup.Download
import GHCup.Errors
import GHCup.Types
import GHCup.Utils
import GHCup.Utils.File
import GHCup.Utils.Logger
import Brick
import Brick.Widgets.Border
import Brick.Widgets.Border.Style
import Brick.Widgets.Center
import Brick.Widgets.List
#if !defined(TAR)
import Codec.Archive
#endif
import Control.Exception.Safe
import Control.Monad.Logger
import Control.Monad.Reader
import Control.Monad.Trans.Resource
import Data.Bool
import Data.Functor
import Data.List
import Data.Maybe
import Data.Char
import Data.IORef
import Data.String.Interpolate
import Data.Vector ( Vector )
import Data.Versions hiding ( str )
import Haskus.Utils.Variant.Excepts
import Prelude hiding ( appendFile )
import System.Exit
import System.IO.Unsafe
import URI.ByteString
import qualified Data.Text as T
import qualified Graphics.Vty as Vty
import qualified Data.Vector as V
data AppState = AppState {
lr :: LR
, dls :: GHCupDownloads
, pfreq :: PlatformRequest
}
type LR = GenericList String Vector ListResult
keyHandlers :: [(Char, String, AppState -> EventM n (Next AppState))]
keyHandlers =
[ ('q', "Quit" , halt)
, ('i', "Install" , withIOAction install')
, ('u', "Uninstall", withIOAction del')
, ('s', "Set" , withIOAction set')
, ('c', "ChangeLog", withIOAction changelog')
]
ui :: AppState -> Widget String
ui AppState {..} =
( padBottom Max
$ ( withBorderStyle unicode
$ borderWithLabel (str "GHCup")
$ (center $ renderList renderItem True lr)
)
)
<=> ( withAttr "help"
. txtWrap
. T.pack
. foldr1 (\x y -> x <> " " <> y)
. (++ ["↑↓:Navigation"])
$ (fmap (\(c, s, _) -> (c : ':' : s)) keyHandlers)
)
where
renderItem b ListResult {..} =
let marks = if
| lSet -> (withAttr "set" $ str "✔✔")
| lInstalled -> (withAttr "installed" $ str "")
| otherwise -> (withAttr "not-installed" $ str "")
ver = case lCross of
Nothing -> T.unpack . prettyVer $ lVer
Just c -> T.unpack (c <> "-" <> prettyVer lVer)
dim = if lNoBindist
then updateAttrMap (const dimAttributes) . withAttr "no-bindist"
else id
in dim
( marks
<+> ( padLeft (Pad 2)
$ minHSize 20
$ ((if b then withAttr "active" else id)
(str $ (fmap toLower . show $ lTool) <> " " <> ver)
)
)
<+> (padLeft (Pad 1) $ if null lTag
then emptyWidget
else
foldr1 (\x y -> x <+> str "," <+> y)
$ (fmap printTag $ sort lTag)
)
)
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
minHSize :: Int -> Widget n -> Widget n
minHSize s' = hLimit s' . vLimit 1 . (<+> fill ' ')
app :: App AppState e String
app = App { appDraw = \st -> [ui st]
, appHandleEvent = eventHandler
, appStartEvent = return
, appAttrMap = const defaultAttributes
, appChooseCursor = neverShowCursor
}
defaultAttributes :: AttrMap
defaultAttributes = attrMap
Vty.defAttr
[ ("active" , Vty.defAttr `Vty.withBackColor` Vty.blue)
, ("not-installed", Vty.defAttr `Vty.withForeColor` Vty.red)
, ("set" , Vty.defAttr `Vty.withForeColor` Vty.green)
, ("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)
]
dimAttributes :: AttrMap
dimAttributes = attrMap
(Vty.defAttr `Vty.withStyle` Vty.dim)
[ ("active" , Vty.defAttr `Vty.withBackColor` Vty.blue)
, ("no-bindist", Vty.defAttr `Vty.withStyle` Vty.dim)
]
eventHandler :: AppState -> BrickEvent n e -> EventM n (Next AppState)
eventHandler st (VtyEvent (Vty.EvResize _ _)) = continue st
eventHandler st (VtyEvent (Vty.EvKey (Vty.KChar 'q') _)) = halt st
eventHandler st (VtyEvent (Vty.EvKey Vty.KEsc _)) = halt st
eventHandler AppState {..} (VtyEvent (Vty.EvKey (Vty.KUp) _)) =
continue (AppState (listMoveUp lr) dls pfreq)
eventHandler AppState {..} (VtyEvent (Vty.EvKey (Vty.KDown) _)) =
continue (AppState (listMoveDown lr) dls pfreq)
eventHandler as (VtyEvent (Vty.EvKey (Vty.KChar c) _)) =
case find (\(c', _, _) -> c' == c) keyHandlers of
Nothing -> continue as
Just (_, _, handler) -> handler as
eventHandler st _ = continue st
-- | Suspend the current UI and run an IO action in terminal. If the
-- IO action returns a Left value, then it's thrown as userError.
withIOAction :: (AppState -> (Int, ListResult) -> IO (Either String a))
-> AppState
-> EventM n (Next AppState)
withIOAction action as = case listSelectedElement (lr as) of
Nothing -> continue as
Just (ix, e) -> suspendAndResume $ do
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 ())
install' AppState {..} (_, ListResult {..}) = do
settings <- readIORef settings'
l <- readIORef logger'
let runLogger = myLoggerT l
let
run =
runLogger
. flip runReaderT settings
. runResourceT
. runE
@'[AlreadyInstalled
, UnknownArchive
#if !defined(TAR)
, ArchiveResult
#endif
, FileDoesNotExistError
, CopyError
, NoDownload
, NotInstalled
, BuildFailed
, TagNotFound
, DigestError
, DownloadFailed
, NoUpdate
, TarDirDoesNotExist
]
(run $ do
case lTool of
GHC -> liftE $ installGHCBin dls lVer pfreq
Cabal -> liftE $ installCabalBin dls lVer pfreq
GHCup -> liftE $ upgradeGHCup dls Nothing False pfreq $> ()
)
>>= \case
VRight _ -> pure $ Right ()
VLeft (V (AlreadyInstalled _ _)) -> pure $ Right ()
VLeft (V (BuildFailed _ e)) ->
pure $ Left [i|Build failed with #{e}|]
VLeft (V NoDownload) ->
pure $ Left [i|No available version for #{prettyVer lVer}|]
VLeft (V NoUpdate) -> pure $ Right ()
VLeft e -> pure $ Left [i|#{e}
Also check the logs in ~/.ghcup/logs|]
set' :: AppState -> (Int, ListResult) -> IO (Either String ())
set' _ (_, ListResult {..}) = do
settings <- readIORef settings'
l <- readIORef logger'
let runLogger = myLoggerT l
let run =
runLogger
. flip runReaderT settings
. runE @'[FileDoesNotExistError, NotInstalled, TagNotFound]
(run $ do
case lTool of
GHC -> liftE $ setGHC (GHCTargetVersion lCross lVer) SetGHCOnly $> ()
Cabal -> liftE $ setCabal lVer $> ()
GHCup -> pure ()
)
>>= \case
VRight _ -> pure $ Right ()
VLeft e -> pure $ Left [i|#{e}|]
del' :: AppState -> (Int, ListResult) -> IO (Either String ())
del' _ (_, ListResult {..}) = do
settings <- readIORef settings'
l <- readIORef logger'
let runLogger = myLoggerT l
let run = runLogger . flip runReaderT settings . runE @'[NotInstalled]
(run $ do
case lTool of
GHC -> liftE $ rmGHCVer (GHCTargetVersion lCross lVer) $> ()
Cabal -> liftE $ rmCabalVer lVer $> ()
GHCup -> pure ()
)
>>= \case
VRight _ -> pure $ Right ()
VLeft e -> pure $ Left [i|#{e}|]
changelog' :: AppState -> (Int, ListResult) -> IO (Either String ())
changelog' AppState {..} (_, ListResult {..}) = do
case getChangeLog dls lTool (Left lVer) of
Nothing -> pure $ Left
[i|Could not find ChangeLog for #{lTool}, version #{prettyVer lVer}|]
Just uri -> do
let cmd = case _rPlatform pfreq of
Darwin -> "open"
Linux _ -> "xdg-open"
FreeBSD -> "xdg-open"
exec cmd True [serializeURIRef' uri] Nothing Nothing >>= \case
Right _ -> pure $ Right ()
Left e -> pure $ Left [i|#{e}|]
uri' :: IORef (Maybe URI)
{-# NOINLINE uri' #-}
uri' = unsafePerformIO (newIORef Nothing)
settings' :: IORef Settings
{-# NOINLINE settings' #-}
settings' = unsafePerformIO $ do
dirs <- getDirs
newIORef Settings { cache = True
, noVerify = False
, keepDirs = Never
, downloader = Curl
, verbose = False
, ..
}
logger' :: IORef LoggerConfig
{-# NOINLINE logger' #-}
logger' = unsafePerformIO
(newIORef $ LoggerConfig { lcPrintDebug = False
, colorOutter = \_ -> pure ()
, rawOutter = \_ -> pure ()
}
)
brickMain :: Settings -> Maybe URI -> LoggerConfig -> GHCupDownloads -> PlatformRequest -> IO ()
brickMain s muri l av pfreq' = do
writeIORef uri' muri
writeIORef settings' s
-- logger interpreter
writeIORef logger' l
let runLogger = myLoggerT l
eApps <- getAppState (Just av) pfreq'
case eApps of
Right as -> defaultMain app (selectLatest as) $> ()
Left e -> do
runLogger ($(logError) [i|Error building app state: #{show e}|])
exitWith $ ExitFailure 2
where
selectLatest :: AppState -> AppState
selectLatest AppState {..} =
(\ix -> AppState { lr = listMoveTo ix lr, .. })
. fromJust
. V.findIndex (\ListResult {..} -> lTool == GHC && Latest `elem` lTag)
$ (listElements lr)
getAppState :: Maybe GHCupDownloads -> PlatformRequest -> IO (Either String AppState)
getAppState mg pfreq' = do
muri <- readIORef uri'
settings <- readIORef settings'
l <- readIORef logger'
let runLogger = myLoggerT l
r <-
runLogger
. flip runReaderT settings
. runE
@'[JSONError, DownloadFailed, FileDoesNotExistError]
$ do
dls <- maybe (fmap _ghcupDownloads $ liftE $ getDownloadsF (maybe GHCupURL OwnSource muri)) pure mg
lV <- lift $ listVersions dls Nothing Nothing pfreq'
pure $ (AppState (list "Tool versions" (V.fromList lV) 1) dls pfreq')
case r of
VRight a -> pure $ Right a
VLeft e -> pure $ Left [i|#{e}|]

View File

@@ -1,6 +1,7 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
@@ -10,6 +11,10 @@
module Main where module Main where
#if defined(BRICK)
import BrickMain ( brickMain )
#endif
import GHCup import GHCup
import GHCup.Download import GHCup.Download
import GHCup.Errors import GHCup.Errors
@@ -21,8 +26,12 @@ import GHCup.Utils.File
import GHCup.Utils.Logger import GHCup.Utils.Logger
import GHCup.Utils.MegaParsec import GHCup.Utils.MegaParsec
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import GHCup.Utils.String.QQ
import GHCup.Version import GHCup.Version
#if !defined(TAR)
import Codec.Archive
#endif
import Control.Exception.Safe import Control.Exception.Safe
#if !MIN_VERSION_base(4,13,0) #if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail ) import Control.Monad.Fail ( MonadFail )
@@ -30,6 +39,7 @@ import Control.Monad.Fail ( MonadFail )
import Control.Monad.Logger import Control.Monad.Logger
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.Trans.Resource import Control.Monad.Trans.Resource
import Data.Aeson ( eitherDecode )
import Data.Bifunctor import Data.Bifunctor
import Data.Char import Data.Char
import Data.Either import Data.Either
@@ -60,6 +70,7 @@ import URI.ByteString
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.UTF8 as UTF8 import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.ByteString.Lazy.UTF8 as BLU
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.IO as T import qualified Data.Text.IO as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
@@ -93,6 +104,9 @@ data Command
| Upgrade UpgradeOpts Bool | Upgrade UpgradeOpts Bool
| ToolRequirements | ToolRequirements
| ChangeLog ChangeLogOptions | ChangeLog ChangeLogOptions
#if defined(BRICK)
| Interactive
#endif
data ToolVersion = ToolVersion GHCTargetVersion -- target is ignored for cabal data ToolVersion = ToolVersion GHCTargetVersion -- target is ignored for cabal
| ToolTag Tag | ToolTag Tag
@@ -108,6 +122,7 @@ data InstallCommand = InstallGHC InstallOptions
data InstallOptions = InstallOptions data InstallOptions = InstallOptions
{ instVer :: Maybe ToolVersion { instVer :: Maybe ToolVersion
, instPlatform :: Maybe PlatformRequest , instPlatform :: Maybe PlatformRequest
, instBindist :: Maybe DownloadInfo
} }
data SetCommand = SetGHC SetOptions data SetCommand = SetGHC SetOptions
@@ -221,7 +236,20 @@ opts =
com :: Parser Command com :: Parser Command
com = com =
subparser subparser
#if defined(BRICK)
( command ( command
"tui"
( (\_ -> Interactive)
<$> (info
helper
( progDesc "Start the interactive GHCup UI"
)
)
)
<> command
#else
( command
#endif
"install" "install"
( Install ( Install
<$> (info <$> (info
@@ -312,32 +340,32 @@ com =
) )
where where
installToolFooter :: String installToolFooter :: String
installToolFooter = [i|Discussion: installToolFooter = [s|Discussion:
Installs GHC or cabal. When no command is given, installs GHC Installs GHC or cabal. When no command is given, installs GHC
with the specified version/tag. with the specified version/tag.
It is recommended to always specify a subcommand ('ghc' or 'cabal').|] It is recommended to always specify a subcommand ('ghc' or 'cabal').|]
setFooter :: String setFooter :: String
setFooter = [i|Discussion: setFooter = [s|Discussion:
Sets the currently active GHC or cabal version. When no command is given, Sets the currently active GHC or cabal version. When no command is given,
defaults to setting GHC with the specified version/tag (if no tag defaults to setting GHC with the specified version/tag (if no tag
is given, sets GHC to 'recommended' version). is given, sets GHC to 'recommended' version).
It is recommended to always specify a subcommand ('ghc' or 'cabal').|] It is recommended to always specify a subcommand ('ghc' or 'cabal').|]
rmFooter :: String rmFooter :: String
rmFooter = [i|Discussion: rmFooter = [s|Discussion:
Remove the given GHC or cabal version. When no command is given, Remove the given GHC or cabal version. When no command is given,
defaults to removing GHC with the specified version. defaults to removing GHC with the specified version.
It is recommended to always specify a subcommand ('ghc' or 'cabal').|] It is recommended to always specify a subcommand ('ghc' or 'cabal').|]
changeLogFooter :: String changeLogFooter :: String
changeLogFooter = [i|Discussion: changeLogFooter = [s|Discussion:
By default returns the URI of the ChangeLog of the latest GHC release. By default returns the URI of the ChangeLog of the latest GHC release.
Pass '-o' to automatically open via xdg-open.|] Pass '-o' to automatically open via xdg-open.|]
installCabalFooter :: String installCabalFooter :: String
installCabalFooter = [i|Discussion: installCabalFooter = [s|Discussion:
Installs the specified cabal-install version (or a recommended default one) Installs the specified cabal-install version (or a recommended default one)
into "~/.ghcup/bin", so it can be overwritten by later into "~/.ghcup/bin", so it can be overwritten by later
"cabal install cabal-install", which installs into "~/.cabal/bin" by "cabal install cabal-install", which installs into "~/.cabal/bin" by
@@ -373,15 +401,19 @@ installParser =
<|> (Right <$> installOpts) <|> (Right <$> installOpts)
where where
installGHCFooter :: String installGHCFooter :: String
installGHCFooter = [i|Discussion: installGHCFooter = [s|Discussion:
Installs the specified GHC version (or a recommended default one) into Installs the specified GHC version (or a recommended default one) into
a self-contained "~/.ghcup/ghc/<ghcver>" directory a self-contained "~/.ghcup/ghc/<ghcver>" directory
and symlinks the ghc binaries to "~/.ghcup/bin/<binary>-<ghcver>".|] and symlinks the ghc binaries to "~/.ghcup/bin/<binary>-<ghcver>".
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 installOpts :: Parser InstallOptions
installOpts = installOpts =
(flip InstallOptions) (\p u v -> InstallOptions v p u)
<$> (optional <$> (optional
(option (option
(eitherReader platformParser) (eitherReader platformParser)
@@ -393,6 +425,17 @@ installOpts =
) )
) )
) )
<*> (optional
(option
(eitherReader bindistParser)
( short 'u'
<> long "url"
<> metavar "BINDIST_URL"
<> help
"Provide DownloadInfo as json string, e.g.: '{ \"dlHash\": \"<sha256 hash>\", \"dlSubdir\": { \"RegexDir\": \"ghc-.*\"}, \"dlUri\": \"<uri>\" }'"
)
)
)
<*> optional toolVersionArgument <*> optional toolVersionArgument
@@ -424,13 +467,13 @@ setParser =
<|> (Right <$> setOpts) <|> (Right <$> setOpts)
where where
setGHCFooter :: String setGHCFooter :: String
setGHCFooter = [i|Discussion: setGHCFooter = [s|Discussion:
Sets the the current GHC version by creating non-versioned Sets the the current GHC version by creating non-versioned
symlinks for all ghc binaries of the specified version in symlinks for all ghc binaries of the specified version in
"~/.ghcup/bin/<binary>".|] "~/.ghcup/bin/<binary>".|]
setCabalFooter :: String setCabalFooter :: String
setCabalFooter = [i|Discussion: setCabalFooter = [s|Discussion:
Sets the the current Cabal version.|] Sets the the current Cabal version.|]
@@ -530,7 +573,7 @@ compileP = subparser
) )
) )
where where
compileFooter = [i|Discussion: compileFooter = [s|Discussion:
Compiles and installs the specified GHC version into Compiles and installs the specified GHC version into
a self-contained "~/.ghcup/ghc/<ghcver>" directory a self-contained "~/.ghcup/ghc/<ghcver>" directory
and symlinks the ghc binaries to "~/.ghcup/bin/<binary>-<ghcver>". and symlinks the ghc binaries to "~/.ghcup/bin/<binary>-<ghcver>".
@@ -776,15 +819,19 @@ platformParser s' = case MP.parse (platformP <* MP.eof) "" (T.pack s') of
pure v pure v
bindistParser :: String -> Either String DownloadInfo
bindistParser = eitherDecode . BLU.fromString
toSettings :: Options -> Settings toSettings :: Options -> IO Settings
toSettings Options {..} = toSettings Options {..} = do
let cache = optCache let cache = optCache
noVerify = optNoVerify noVerify = optNoVerify
keepDirs = optKeepDirs keepDirs = optKeepDirs
downloader = optsDownloader downloader = optsDownloader
in Settings { .. } verbose = optVerbose
dirs <- getDirs
pure $ Settings { .. }
upgradeOptsP :: Parser UpgradeOpts upgradeOptsP :: Parser UpgradeOpts
@@ -843,7 +890,7 @@ main = do
<> internal <> internal
) )
let main_footer = [i|Discussion: let main_footer = [s|Discussion:
ghcup installs the Glasgow Haskell Compiler from the official ghcup installs the Glasgow Haskell Compiler from the official
release channels, enabling you to easily switch between different release channels, enabling you to easily switch between different
versions. It maintains a self-contained ~/.ghcup directory. versions. It maintains a self-contained ~/.ghcup directory.
@@ -860,19 +907,19 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
(footerDoc (Just $ text main_footer)) (footerDoc (Just $ text main_footer))
) )
>>= \opt@Options {..} -> do >>= \opt@Options {..} -> do
let settings@Settings{..} = toSettings opt settings@Settings{dirs = Dirs{..}, ..} <- toSettings opt
-- create ~/.ghcup dir -- create ~/.ghcup dir
ghcdir <- ghcupBaseDir createDirRecursive newDirPerms baseDir
createDirIfMissing newDirPerms ghcdir
-- logger interpreter -- logger interpreter
logfile <- initGHCupFileLogging [rel|ghcup.log|] logfile <- flip runReaderT settings $ initGHCupFileLogging [rel|ghcup.log|]
let runLogger = myLoggerT LoggerConfig let loggerConfig = LoggerConfig
{ lcPrintDebug = optVerbose { lcPrintDebug = optVerbose
, colorOutter = B.hPut stderr , colorOutter = B.hPut stderr
, rawOutter = appendFile logfile , rawOutter = appendFile logfile
} }
let runLogger = myLoggerT loggerConfig
------------------------- -------------------------
@@ -886,17 +933,18 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
. runE . runE
@'[ AlreadyInstalled @'[ AlreadyInstalled
, UnknownArchive , UnknownArchive
, DistroNotFound #if !defined(TAR)
, ArchiveResult
#endif
, FileDoesNotExistError , FileDoesNotExistError
, CopyError , CopyError
, NoCompatibleArch
, NoDownload , NoDownload
, NotInstalled , NotInstalled
, NoCompatiblePlatform
, BuildFailed , BuildFailed
, TagNotFound , TagNotFound
, DigestError , DigestError
, DownloadFailed , DownloadFailed
, TarDirDoesNotExist
] ]
let let
@@ -907,20 +955,20 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
@'[ FileDoesNotExistError @'[ FileDoesNotExistError
, NotInstalled , NotInstalled
, TagNotFound , TagNotFound
, TagNotFound
] ]
let let
runSetCabal = runSetCabal =
runLogger runLogger
. flip runReaderT settings
. runE . runE
@'[ NotInstalled @'[ NotInstalled
, TagNotFound , TagNotFound
] ]
let runListGHC = runLogger . runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] let runListGHC = runLogger . flip runReaderT settings
let runRmGHC = let runRm =
runLogger . flip runReaderT settings . runE @'[NotInstalled] runLogger . flip runReaderT settings . runE @'[NotInstalled]
let runDebugInfo = let runDebugInfo =
@@ -937,15 +985,16 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
@'[ AlreadyInstalled @'[ AlreadyInstalled
, BuildFailed , BuildFailed
, DigestError , DigestError
, DistroNotFound
, DownloadFailed , DownloadFailed
, GHCupSetError , GHCupSetError
, NoCompatibleArch
, NoCompatiblePlatform
, NoDownload , NoDownload
, NotFoundInPATH , NotFoundInPATH
, PatchFailed , PatchFailed
, UnknownArchive , UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
] ]
let runCompileCabal = let runCompileCabal =
@@ -957,14 +1006,15 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
, BuildFailed , BuildFailed
, CopyError , CopyError
, DigestError , DigestError
, DistroNotFound
, DownloadFailed , DownloadFailed
, NoCompatibleArch
, NoCompatiblePlatform
, NoDownload , NoDownload
, NotInstalled , NotInstalled
, PatchFailed , PatchFailed
, UnknownArchive , UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
] ]
let runUpgrade = let runUpgrade =
@@ -973,9 +1023,6 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
. runResourceT . runResourceT
. runE . runE
@'[ DigestError @'[ DigestError
, DistroNotFound
, NoCompatiblePlatform
, NoCompatibleArch
, NoDownload , NoDownload
, NoUpdate , NoUpdate
, FileDoesNotExistError , FileDoesNotExistError
@@ -984,9 +1031,19 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
] ]
--------------------------- ----------------------------------------
-- Getting download info -- -- Getting download and platform info --
--------------------------- ----------------------------------------
pfreq <- (
runLogger . runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] . liftE $ platformRequest
) >>= \case
VRight r -> pure r
VLeft e -> do
runLogger
($(logError) [i|Error determining Platform: #{e}|])
exitWith (ExitFailure 2)
(GHCupInfo treq dls) <- (GHCupInfo treq dls) <-
( runLogger ( runLogger
@@ -1001,14 +1058,11 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
runLogger runLogger
($(logError) [i|Error fetching download info: #{e}|]) ($(logError) [i|Error fetching download info: #{e}|])
exitWith (ExitFailure 2) exitWith (ExitFailure 2)
(runLogger
. runE @'[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound] $ checkForUpdates dls case optCommand of
) Upgrade _ _ -> pure ()
>>= \case _ -> runLogger $ flip runReaderT settings $ checkForUpdates dls pfreq
VRight _ -> pure ()
VLeft e -> do
runLogger
($(logError) [i|Error checking for upgrades: #{e}|])
----------------------- -----------------------
@@ -1018,7 +1072,9 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
let installGHC InstallOptions{..} = let installGHC InstallOptions{..} =
(runInstTool $ do (runInstTool $ do
v <- liftE $ fromVersion dls instVer GHC v <- liftE $ fromVersion dls instVer GHC
liftE $ installGHCBin dls (_tvVersion v) instPlatform -- FIXME: ugly sharing of tool version case instBindist of
Nothing -> liftE $ installGHCBin dls (_tvVersion v) (fromMaybe pfreq instPlatform)
Just uri -> liftE $ installGHCBindist uri (_tvVersion v) (fromMaybe pfreq instPlatform)
) )
>>= \case >>= \case
VRight _ -> do VRight _ -> do
@@ -1032,7 +1088,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
case keepDirs of case keepDirs of
Never -> runLogger ($(logError) [i|Build failed with #{e}|]) Never -> runLogger ($(logError) [i|Build failed with #{e}|])
_ -> 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.|]) Make sure to clean up #{tmpdir} afterwards.|])
pure $ ExitFailure 3 pure $ ExitFailure 3
VLeft (V NoDownload) -> do VLeft (V NoDownload) -> do
@@ -1045,14 +1101,16 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
VLeft e -> do VLeft e -> do
runLogger $ do runLogger $ do
$(logError) [i|#{e}|] $(logError) [i|#{e}|]
$(logError) [i|Also check the logs in ~/.ghcup/logs|] $(logError) [i|Also check the logs in #{logsDir}|]
pure $ ExitFailure 3 pure $ ExitFailure 3
let installCabal InstallOptions{..} = let installCabal InstallOptions{..} =
(runInstTool $ do (runInstTool $ do
v <- liftE $ fromVersion dls instVer Cabal v <- liftE $ fromVersion dls instVer Cabal
liftE $ installCabalBin dls (_tvVersion v) instPlatform -- FIXME: ugly sharing of tool version case instBindist of
Nothing -> liftE $ installCabalBin dls (_tvVersion v) (fromMaybe pfreq instPlatform)
Just uri -> liftE $ installCabalBindist uri (_tvVersion v) (fromMaybe pfreq instPlatform)
) )
>>= \case >>= \case
VRight _ -> do VRight _ -> do
@@ -1072,7 +1130,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
VLeft e -> do VLeft e -> do
runLogger $ do runLogger $ do
$(logError) [i|#{e}|] $(logError) [i|#{e}|]
$(logError) [i|Also check the logs in ~/.ghcup/logs|] $(logError) [i|Also check the logs in #{logsDir}|]
pure $ ExitFailure 4 pure $ ExitFailure 4
let setGHC' SetOptions{..} = let setGHC' SetOptions{..} =
@@ -1102,7 +1160,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
pure $ ExitFailure 14 pure $ ExitFailure 14
let rmGHC' RmOptions{..} = let rmGHC' RmOptions{..} =
(runRmGHC $ do (runRm $ do
liftE $ rmGHCVer ghcVer liftE $ rmGHCVer ghcVer
) )
>>= \case >>= \case
@@ -1112,7 +1170,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
pure $ ExitFailure 7 pure $ ExitFailure 7
let rmCabal' tv = let rmCabal' tv =
(runSetCabal $ do (runRm $ do
liftE $ rmCabalVer tv liftE $ rmCabalVer tv
) )
>>= \case >>= \case
@@ -1124,6 +1182,9 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
res <- case optCommand of res <- case optCommand of
#if defined(BRICK)
Interactive -> liftIO $ brickMain settings optUrlSource loggerConfig dls pfreq >> pure ExitSuccess
#endif
Install (Right iopts) -> do Install (Right iopts) -> do
runLogger ($(logWarn) [i|This is an old-style command for installing GHC. Use 'ghcup install ghc' instead.|]) runLogger ($(logWarn) [i|This is an old-style command for installing GHC. Use 'ghcup install ghc' instead.|])
installGHC iopts installGHC iopts
@@ -1141,16 +1202,10 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
List (ListOptions {..}) -> List (ListOptions {..}) ->
(runListGHC $ do (runListGHC $ do
l <- listVersions dls lTool lCriteria l <- listVersions dls lTool lCriteria pfreq
pure l liftIO $ printListResult lRawFormat l
pure ExitSuccess
) )
>>= \case
VRight r -> do
liftIO $ printListResult lRawFormat r
pure ExitSuccess
VLeft e -> do
runLogger ($(logError) [i|#{e}|])
pure $ ExitFailure 6
Rm (Right rmopts) -> do Rm (Right rmopts) -> do
runLogger ($(logWarn) [i|This is an old-style command for removing GHC. Use 'ghcup rm ghc' instead.|]) runLogger ($(logWarn) [i|This is an old-style command for removing GHC. Use 'ghcup rm ghc' instead.|])
@@ -1177,6 +1232,7 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
buildConfig buildConfig
patchDir patchDir
addConfArgs addConfArgs
pfreq
) )
>>= \case >>= \case
VRight _ -> do VRight _ -> do
@@ -1190,9 +1246,9 @@ Report bugs at <https://gitlab.haskell.org/haskell/ghcup-hs/issues>|]
VLeft (V (BuildFailed tmpdir e)) -> do VLeft (V (BuildFailed tmpdir e)) -> do
case keepDirs of case keepDirs of
Never -> runLogger ($(logError) [i|Build failed with #{e} 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} _ -> 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.|]) Make sure to clean up #{tmpdir} afterwards.|])
pure $ ExitFailure 9 pure $ ExitFailure 9
VLeft e -> do VLeft e -> do
@@ -1201,7 +1257,7 @@ Make sure to clean up #{tmpdir} afterwards.|])
Compile (CompileCabal CabalCompileOptions {..}) -> Compile (CompileCabal CabalCompileOptions {..}) ->
(runCompileCabal $ do (runCompileCabal $ do
liftE $ compileCabal dls targetVer bootstrapGhc jobs patchDir liftE $ compileCabal dls targetVer bootstrapGhc jobs patchDir pfreq
) )
>>= \case >>= \case
VRight _ -> do VRight _ -> do
@@ -1214,7 +1270,7 @@ Make sure to clean up #{tmpdir} afterwards.|])
case keepDirs of case keepDirs of
Never -> runLogger ($(logError) [i|Build failed with #{e}|]) Never -> runLogger ($(logError) [i|Build failed with #{e}|])
_ -> 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.|]) Make sure to clean up #{tmpdir} afterwards.|])
pure $ ExitFailure 10 pure $ ExitFailure 10
VLeft e -> do VLeft e -> do
@@ -1228,11 +1284,9 @@ Make sure to clean up #{tmpdir} afterwards.|])
p <- parseAbs . E.encodeUtf8 . T.pack $ efp p <- parseAbs . E.encodeUtf8 . T.pack $ efp
pure $ Just p pure $ Just p
(UpgradeAt p) -> pure $ Just p (UpgradeAt p) -> pure $ Just p
UpgradeGHCupDir -> do UpgradeGHCupDir -> pure (Just (binDir </> [rel|ghcup|]))
bdir <- liftIO $ ghcupBinDir
pure (Just (bdir </> [rel|ghcup|]))
(runUpgrade $ (liftE $ upgradeGHCup dls target force)) >>= \case (runUpgrade $ (liftE $ upgradeGHCup dls target force pfreq)) >>= \case
VRight v' -> do VRight v' -> do
let pretty_v = prettyVer v' let pretty_v = prettyVer v'
runLogger $ $(logInfo) runLogger $ $(logInfo)
@@ -1284,9 +1338,14 @@ Make sure to clean up #{tmpdir} afterwards.|])
pure ExitSuccess pure ExitSuccess
Just uri -> do Just uri -> do
let uri' = T.unpack . decUTF8Safe . serializeURIRef' $ uri let uri' = T.unpack . decUTF8Safe . serializeURIRef' $ uri
cmd = case _rPlatform pfreq of
Darwin -> "open"
Linux _ -> "xdg-open"
FreeBSD -> "xdg-open"
if clOpen if clOpen
then then
exec "xdg-open" exec cmd
True True
[serializeURIRef' uri] [serializeURIRef' uri]
Nothing Nothing
@@ -1370,45 +1429,41 @@ printListResult raw lr = do
where where
printTag Recommended = color' Green "recommended" printTag Recommended = color' Green "recommended"
printTag Latest = color' Yellow "latest" printTag Latest = color' Yellow "latest"
printTag Prerelease = color' Red "prerelease"
printTag (Base pvp'') = "base-" ++ T.unpack (prettyPVP pvp'') printTag (Base pvp'') = "base-" ++ T.unpack (prettyPVP pvp'')
printTag (UnknownTag t ) = t printTag (UnknownTag t ) = t
color' = case raw of color' = case raw of
True -> flip const True -> flip const
False -> color 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 => GHCupDownloads
-> Excepts -> PlatformRequest
'[ NoCompatiblePlatform -> m ()
, NoCompatibleArch checkForUpdates dls pfreq = do
, DistroNotFound
]
m
()
checkForUpdates dls = do
forM_ (getLatest dls GHCup) $ \l -> do forM_ (getLatest dls GHCup) $ \l -> do
(Right ghc_ver) <- pure $ version $ prettyPVP ghcUpVer (Right ghc_ver) <- pure $ version $ prettyPVP ghcUpVer
when (l > ghc_ver) when (l > ghc_ver)
$ lift $ $(logWarn) $ $(logWarn)
[i|New GHCup version available: #{prettyVer l}. To upgrade, run 'ghcup upgrade'|] [i|New GHCup version available: #{prettyVer l}. To upgrade, run 'ghcup upgrade'|]
forM_ (getLatest dls GHC) $ \l -> do forM_ (getLatest dls GHC) $ \l -> do
mghc_ver <- latestInstalled GHC mghc_ver <- latestInstalled GHC
forM mghc_ver $ \ghc_ver -> forM mghc_ver $ \ghc_ver ->
when (l > ghc_ver) when (l > ghc_ver)
$ lift $ $(logWarn) $ $(logWarn)
[i|New GHC version available: #{prettyVer l}. To upgrade, run 'ghcup install ghc #{prettyVer l}'|] [i|New GHC version available: #{prettyVer l}. To upgrade, run 'ghcup install ghc #{prettyVer l}'|]
forM_ (getLatest dls Cabal) $ \l -> do forM_ (getLatest dls Cabal) $ \l -> do
mcabal_ver <- latestInstalled Cabal mcabal_ver <- latestInstalled Cabal
forM mcabal_ver $ \cabal_ver -> forM mcabal_ver $ \cabal_ver ->
when (l > cabal_ver) when (l > cabal_ver)
$ lift $ $(logWarn) $ $(logWarn)
[i|New Cabal version available: #{prettyVer l}. To upgrade, run 'ghcup install cabal #{prettyVer l}'|] [i|New Cabal version available: #{prettyVer l}. To upgrade, run 'ghcup install cabal #{prettyVer l}'|]
where where
latestInstalled tool = (fmap lVer . lastMay) latestInstalled tool = (fmap lVer . lastMay)
<$> (listVersions dls (Just tool) (Just ListInstalled)) <$> (listVersions dls (Just tool) (Just ListInstalled) pfreq)
prettyDebugInfo :: DebugInfo -> String prettyDebugInfo :: DebugInfo -> String
@@ -1425,6 +1480,13 @@ Version: #{describe_result}|]
prettyArch :: Architecture -> String prettyArch :: Architecture -> String
prettyArch A_64 = "amd64" prettyArch A_64 = "amd64"
prettyArch A_32 = "i386" prettyArch A_32 = "i386"
prettyArch A_PowerPC = "PowerPC"
prettyArch A_PowerPC64 = "PowerPC64"
prettyArch A_Sparc = "Sparc"
prettyArch A_Sparc64 = "Sparc64"
prettyArch A_ARM = "ARM"
prettyArch A_ARM64 = "ARM64"
prettyPlatform :: PlatformResult -> String prettyPlatform :: PlatformResult -> String
prettyPlatform PlatformResult { _platform = plat, _distroVersion = Just v' } prettyPlatform PlatformResult { _platform = plat, _distroVersion = Just v' }
= show plat <> ", " <> show v' = show plat <> ", " <> show v'

View File

@@ -4,6 +4,17 @@
( (
: "${GHCUP_INSTALL_BASE_PREFIX:=$HOME}" : "${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_GHC_VERSION:=recommended}"
: "${BOOTSTRAP_HASKELL_CABAL_VERSION:=recommended}" : "${BOOTSTRAP_HASKELL_CABAL_VERSION:=recommended}"
@@ -18,26 +29,55 @@ edo()
} }
eghcup() { eghcup() {
edo _eghcup "$@"
}
_eghcup() {
if [ -z "${BOOTSTRAP_HASKELL_VERBOSE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_VERBOSE}" ] ; then
edo ghcup "$@" ghcup "$@"
else else
edo ghcup --verbose "$@" ghcup --verbose "$@"
fi 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() { download_ghcup() {
_plat="$(uname -s)" _plat="$(uname -s)"
_arch=$(uname -m) _arch=$(uname -m)
_ghver="0.1.5" _ghver="0.1.8"
_base_url="https://downloads.haskell.org/~ghcup"
case "${_plat}" in case "${_plat}" in
"linux"|"Linux") "linux"|"Linux")
case "${_arch}" in case "${_arch}" in
x86_64|amd64) x86_64|amd64)
_url=https://downloads.haskell.org/~ghcup/${_ghver}/x86_64-linux-ghcup-${_ghver} # we could be in a 32bit docker container, in which
# case uname doesn't give us what we want
if [ "$(getconf LONG_BIT)" = "32" ] ; then
_url=${_base_url}/${_ghver}/i386-linux-ghcup-${_ghver}
elif [ "$(getconf LONG_BIT)" = "64" ] ; then
_url=${_base_url}/${_ghver}/x86_64-linux-ghcup-${_ghver}
else
die "Unknown long bit size: $(getconf LONG_BIT)"
fi
;; ;;
i*86) i*86)
_url=https://downloads.haskell.org/~ghcup/${_ghver}/i386-linux-ghcup-${_ghver} _url=${_base_url}/${_ghver}/i386-linux-ghcup-${_ghver}
;; ;;
*) die "Unknown architecture: ${_arch}" *) die "Unknown architecture: ${_arch}"
;; ;;
@@ -53,7 +93,7 @@ download_ghcup() {
*) die "Unknown architecture: ${_arch}" *) die "Unknown architecture: ${_arch}"
;; ;;
esac esac
_url=https://downloads.haskell.org/~ghcup/${_ghver}/x86_64-portbld-freebsd-ghcup-${_ghver} _url=${_base_url}/${_ghver}/x86_64-portbld-freebsd-ghcup-${_ghver}
;; ;;
"Darwin"|"darwin") "Darwin"|"darwin")
case "${_arch}" in case "${_arch}" in
@@ -65,14 +105,23 @@ download_ghcup() {
*) die "Unknown architecture: ${_arch}" *) die "Unknown architecture: ${_arch}"
;; ;;
esac esac
_url=https://downloads.haskell.org/~ghcup/0.1.5/x86_64-apple-darwin-ghcup-0.1.5-p2 ;; _url=${_base_url}/${_ghver}/x86_64-apple-darwin-ghcup-${_ghver} ;;
*) die "Unknown platform: ${_plat}" *) die "Unknown platform: ${_plat}"
;; ;;
esac esac
edo curl -Lf "${_url}" > "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/bin/ghcup edo curl -Lf "${_url}" > "${GHCUP_BIN}"/ghcup
unset _plat _arch _url _ghver edo chmod +x "${GHCUP_BIN}"/ghcup
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_DIR}"/env
eghcup upgrade
unset _plat _arch _url _ghver _base_url
} }
@@ -80,12 +129,19 @@ echo
echo "Welcome to Haskell!" echo "Welcome to Haskell!"
echo echo
echo "This script will download and install the following binaries:" 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 " * ghc - The Glasgow Haskell Compiler"
echo " * cabal - The Cabal build tool" echo " * cabal - The Cabal build tool"
echo echo
echo "ghcup installs only into the following directory, which can be removed anytime:" if [ -z "${GHCUP_USE_XDG_DIRS}" ] ; then
echo " $GHCUP_INSTALL_BASE_PREFIX/.ghcup" 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"
echo "'GHCUP_USE_XDG_DIRS' is set."
fi
echo echo
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
@@ -97,22 +153,14 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
read -r answer </dev/tty read -r answer </dev/tty
fi fi
edo mkdir -p "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/bin edo mkdir -p "${GHCUP_BIN}"
if command -V "ghcup" >/dev/null 2>&1 ; then if command -V "ghcup" >/dev/null 2>&1 ; then
if [ -z "${BOOTSTRAP_HASKELL_NO_UPGRADE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NO_UPGRADE}" ] ; then
eghcup upgrade _eghcup upgrade || download_ghcup
fi fi
else else
download_ghcup download_ghcup
edo chmod +x "${GHCUP_INSTALL_BASE_PREFIX}"/.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"
EOF
# shellcheck disable=SC1090
edo . "${GHCUP_INSTALL_BASE_PREFIX}"/.ghcup/env
eghcup upgrade
fi fi
echo echo
@@ -129,10 +177,10 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
read -r answer </dev/tty read -r answer </dev/tty
fi fi
eghcup --cache install "${BOOTSTRAP_HASKELL_GHC_VERSION}" eghcup --cache install ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}"
eghcup set "${BOOTSTRAP_HASKELL_GHC_VERSION}" eghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}"
eghcup --cache install-cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}" eghcup --cache install cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}"
edo cabal new-update edo cabal new-update
@@ -142,7 +190,7 @@ printf "\\033[0;35m%s\\033[0m\\n" ""
if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
echo "In order to run ghc and cabal, you need to adjust your PATH variable." 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)." echo "configuration to do so (e.g. ~/.bashrc)."
case $SHELL in case $SHELL in
@@ -160,10 +208,13 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
GHCUP_PROFILE_FILE="$HOME/.zshrc" GHCUP_PROFILE_FILE="$HOME/.zshrc"
MY_SHELL="zsh" MY_SHELL="zsh"
else else
exit 0 _done
fi fi
;; ;;
*) exit 0 ;; */fish) # login shell is fish
GHCUP_PROFILE_FILE="$HOME/.config/fish/config.fish"
MY_SHELL="fish" ;;
*) _done ;;
esac esac
@@ -178,12 +229,27 @@ if [ -z "${BOOTSTRAP_HASKELL_NONINTERACTIVE}" ] ; then
case $next_answer in case $next_answer in
[Yy]*) [Yy]*)
echo "[ -f \"\${GHCUP_INSTALL_BASE_PREFIX:=\$HOME}/.ghcup/env\" ] && source \"\${GHCUP_INSTALL_BASE_PREFIX:=\$HOME}/.ghcup/env\"" >> "${GHCUP_PROFILE_FILE}" case $MY_SHELL in
"") break ;;
fish)
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 ;;
*)
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," 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;; _done
;;
[Nn]*) [Nn]*)
exit 0;; _done ;;
*) *)
echo "Please type YES or NO and press enter.";; echo "Please type YES or NO and press enter.";;
esac esac

View File

@@ -2,6 +2,12 @@ packages: ./ghcup.cabal
optional-packages: ./3rdparty/*/*.cabal optional-packages: ./3rdparty/*/*.cabal
source-repository-package
type: git
location: https://github.com/haskus/packages.git
tag: 80a1c5fc07f7226c424250ec17f674cd4d618f42
subdir: haskus-utils-types
optimization: 2 optimization: 2
package streamly package streamly
@@ -10,9 +16,9 @@ package streamly
package ghcup package ghcup
ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16 ghc-options: -O2 -fspec-constr-recursive=16 -fmax-worker-args=16
package tar-bytestring
ghc-options: -O2
constraints: http-io-streams -brotli constraints: http-io-streams -brotli
allow-newer: base package libarchive
flags: +static
allow-newer: base, ghc-prim, template-haskell

File diff suppressed because it is too large Load Diff

1327
ghcup-0.0.2.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
cabal-version: 3.0 cabal-version: 3.0
name: ghcup name: ghcup
version: 0.1.5 version: 0.1.8
synopsis: ghc toolchain installer as an exe/library synopsis: ghc toolchain installer as an exe/library
description: description:
A rewrite of the shell script ghcup, for providing A rewrite of the shell script ghcup, for providing
@@ -21,11 +21,21 @@ source-repository head
type: git type: git
location: https://gitlab.haskell.org/haskell/ghcup-hs.git location: https://gitlab.haskell.org/haskell/ghcup-hs.git
flag tui
description: Build the brick powered tui (ghcup tui)
default: False
manual: True
flag internal-downloader flag internal-downloader
description: Compile the internal downloader, which links against OpenSSL description: Compile the internal downloader, which links against OpenSSL
default: False default: False
manual: True manual: True
flag tar
description: Use tar-bytestring instead of libarchive
default: False
manual: True
common HsOpenSSL common HsOpenSSL
build-depends: HsOpenSSL >=0.11.4.18 build-depends: HsOpenSSL >=0.11.4.18
@@ -50,6 +60,9 @@ common base16-bytestring
common binary common binary
build-depends: binary >=0.8.6.0 build-depends: binary >=0.8.6.0
common brick
build-depends: brick >=0.54
common bytestring common bytestring
build-depends: bytestring >=0.10 build-depends: bytestring >=0.10
@@ -81,13 +94,13 @@ common hpath
build-depends: hpath >=0.11 build-depends: hpath >=0.11
common hpath-directory common hpath-directory
build-depends: hpath-directory >=0.13.3 build-depends: hpath-directory >=0.14
common hpath-filepath common hpath-filepath
build-depends: hpath-filepath >=0.10.3 build-depends: hpath-filepath >=0.10.3
common hpath-io common hpath-io
build-depends: hpath-io >=0.13.1 build-depends: hpath-io >=0.14
common hpath-posix common hpath-posix
build-depends: hpath-posix >=0.13.2 build-depends: hpath-posix >=0.13.2
@@ -98,8 +111,8 @@ common http-io-streams
common io-streams common io-streams
build-depends: io-streams >=1.5 build-depends: io-streams >=1.5
common language-bash common libarchive
build-depends: language-bash >=0.9 build-depends: libarchive >= 2.2.5.0
common lzma common lzma
build-depends: lzma >=0.0.0.3 build-depends: lzma >=0.0.0.3
@@ -140,6 +153,9 @@ common safe
common safe-exceptions common safe-exceptions
build-depends: safe-exceptions >=0.1 build-depends: safe-exceptions >=0.1
common split
build-depends: split >=0.2.3.4
common streamly common streamly
build-depends: streamly >=0.7.1 build-depends: streamly >=0.7.1
@@ -158,17 +174,17 @@ common string-interpolate
common table-layout common table-layout
build-depends: table-layout >=0.8 build-depends: table-layout >=0.8
common tar-bytestring
build-depends: tar-bytestring >=0.6.3.1
common template-haskell common template-haskell
build-depends: template-haskell >=2.7 build-depends: template-haskell >=2.7
common tar-bytestring
build-depends: tar-bytestring >=0.6.3.1
common terminal-progress-bar common terminal-progress-bar
build-depends: terminal-progress-bar >=0.4.1 build-depends: terminal-progress-bar >=0.4.1
common text common text
build-depends: text >=1.2 build-depends: text >=1.2.4.0
common time common time
build-depends: time >=1.9.3 build-depends: time >=1.9.3
@@ -176,12 +192,18 @@ common time
common transformers common transformers
build-depends: transformers >=0.5 build-depends: transformers >=0.5
common os-release
build-depends: os-release >=1.0.0
common unix common unix
build-depends: unix >=2.7 build-depends: unix >=2.7
common unix-bytestring common unix-bytestring
build-depends: unix-bytestring >=0.3 build-depends: unix-bytestring >=0.3
common unordered-containers
build-depends: unordered-containers >= 0.2.10.0
common uri-bytestring common uri-bytestring
build-depends: uri-bytestring >=0.3.2.2 build-depends: uri-bytestring >=0.3.2.2
@@ -194,12 +216,15 @@ common vector
common versions common versions
build-depends: versions >=3.5 build-depends: versions >=3.5
common waargonaut common vty
build-depends: waargonaut >=0.8 build-depends: vty >=5.28.2
common word8 common word8
build-depends: word8 >=0.1.3 build-depends: word8 >=0.1.3
common yaml
build-depends: yaml >=0.11.4.0
common zlib common zlib
build-depends: zlib >=0.6.2.1 build-depends: zlib >=0.6.2.1
@@ -242,7 +267,6 @@ library
, hpath-filepath , hpath-filepath
, hpath-io , hpath-io
, hpath-posix , hpath-posix
, language-bash
, lzma , lzma
, megaparsec , megaparsec
, monad-logger , monad-logger
@@ -255,30 +279,30 @@ library
, resourcet , resourcet
, safe , safe
, safe-exceptions , safe-exceptions
, split
, streamly , streamly
, streamly-posix , streamly-posix
, streamly-bytestring , streamly-bytestring
, strict-base , strict-base
, string-interpolate , string-interpolate
, tar-bytestring
, template-haskell , template-haskell
, text , text
, time , time
, transformers , transformers
, os-release
, unix , unix
, unix-bytestring , unix-bytestring
, unordered-containers
, uri-bytestring , uri-bytestring
, utf8-string , utf8-string
, vector , vector
, versions , versions
, word8 , word8
, yaml
, zlib , zlib
exposed-modules: exposed-modules:
GHCup GHCup
GHCup.Data.GHCupDownloads
GHCup.Data.GHCupInfo
GHCup.Data.ToolRequirements
GHCup.Download GHCup.Download
GHCup.Download.Utils GHCup.Download.Utils
GHCup.Errors GHCup.Errors
@@ -288,7 +312,6 @@ library
GHCup.Types.JSON GHCup.Types.JSON
GHCup.Types.Optics GHCup.Types.Optics
GHCup.Utils GHCup.Utils
GHCup.Utils.Bash
GHCup.Utils.Dirs GHCup.Utils.Dirs
GHCup.Utils.File GHCup.Utils.File
GHCup.Utils.Logger GHCup.Utils.Logger
@@ -304,17 +327,26 @@ library
if flag(internal-downloader) if flag(internal-downloader)
import: import:
, HsOpenSSL HsOpenSSL
, http-io-streams , http-io-streams
, io-streams , io-streams
, terminal-progress-bar , terminal-progress-bar
exposed-modules: GHCup.Download.IOStreams exposed-modules: GHCup.Download.IOStreams
cpp-options: -DINTERNAL_DOWNLOADER cpp-options: -DINTERNAL_DOWNLOADER
if flag(tar)
import:
tar-bytestring
cpp-options: -DTAR
else
import:
libarchive
executable ghcup executable ghcup
import: import:
config config
, base , base
, aeson
, bytestring , bytestring
, containers , containers
, haskus-utils-variant , haskus-utils-variant
@@ -348,6 +380,19 @@ executable ghcup
if flag(internal-downloader) if flag(internal-downloader)
cpp-options: -DINTERNAL_DOWNLOADER cpp-options: -DINTERNAL_DOWNLOADER
if flag(tui)
import:
brick
, vector
, vty
other-modules: BrickMain
cpp-options: -DBRICK
if flag(tar)
cpp-options: -DTAR
else
import:
libarchive
executable ghcup-gen executable ghcup-gen
import: import:
@@ -373,6 +418,7 @@ executable ghcup-gen
, uri-bytestring , uri-bytestring
, utf8-string , utf8-string
, versions , versions
, yaml
-- --
main-is: Main.hs main-is: Main.hs

4
hie.yaml Normal file
View File

@@ -0,0 +1,4 @@
cradle:
cabal:
- path: "."
component: "ghcup:lib:ghcup"

View File

@@ -11,6 +11,21 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-|
Module : GHCup
Description : GHCup installation functions
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
This module contains the main functions that correspond
to the command line interface, like installation, listing versions
and so on.
These are the entry points.
-}
module GHCup where module GHCup where
@@ -27,6 +42,9 @@ import GHCup.Utils.String.QQ
import GHCup.Utils.Version.QQ import GHCup.Utils.Version.QQ
import GHCup.Version import GHCup.Version
#if !defined(TAR)
import Codec.Archive ( ArchiveResult )
#endif
import Control.Applicative import Control.Applicative
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad import Control.Monad
@@ -47,7 +65,7 @@ import Data.Versions
import Data.Word8 import Data.Word8
import GHC.IO.Exception import GHC.IO.Exception
import HPath import HPath
import HPath.IO import HPath.IO hiding ( hideError )
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Optics import Optics
import Prelude hiding ( abs import Prelude hiding ( abs
@@ -73,42 +91,41 @@ import qualified Data.Text.Encoding as E
------------------------- -------------------------
-- | Like 'installGHCBin', except takes the 'DownloadInfo' as
installGHCBin :: ( MonadFail m -- argument instead of looking it up from 'GHCupDownloads'.
, MonadMask m installGHCBindist :: ( MonadFail m
, MonadCatch m , MonadMask m
, MonadReader Settings m , MonadCatch m
, MonadLogger m , MonadReader Settings m
, MonadResource m , MonadLogger m
, MonadIO m , MonadResource m
) , MonadIO m
=> GHCupDownloads )
-> Version => DownloadInfo -- ^ where/how to download
-> Maybe PlatformRequest -- ^ if Nothing, looks up current host platform -> Version -- ^ the version to install
-> Excepts -> PlatformRequest -- ^ the platform to install on
'[ AlreadyInstalled -> Excepts
, BuildFailed '[ AlreadyInstalled
, DigestError , BuildFailed
, DistroNotFound , DigestError
, DownloadFailed , DownloadFailed
, NoCompatibleArch , NoDownload
, NoCompatiblePlatform , NotInstalled
, NoDownload , UnknownArchive
, NotInstalled , TarDirDoesNotExist
, UnknownArchive #if !defined(TAR)
] , ArchiveResult
m #endif
() ]
installGHCBin bDls ver mpfReq = do m
()
installGHCBindist dlinfo ver (PlatformRequest {..}) = do
let tver = (mkTVer ver) let tver = (mkTVer ver)
lift $ $(logDebug) [i|Requested to install GHC with #{ver}|] lift $ $(logDebug) [i|Requested to install GHC with #{ver}|]
whenM (liftIO $ ghcInstalled tver) whenM (lift $ ghcInstalled tver)
$ (throwE $ AlreadyInstalled GHC ver) $ (throwE $ AlreadyInstalled GHC ver)
Settings {..} <- lift ask
pfreq@(PlatformRequest {..}) <- maybe (liftE $ platformRequest) pure mpfReq
-- download (or use cached version) -- download (or use cached version)
dlinfo <- lE $ getDownloadInfo GHC ver pfreq bDls
dl <- liftE $ downloadCached dlinfo Nothing dl <- liftE $ downloadCached dlinfo Nothing
-- unpack -- unpack
@@ -117,10 +134,10 @@ installGHCBin bDls ver mpfReq = do
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ liftIO $ darwinNotarization _rPlatform tmpUnpack
-- prepare paths -- prepare paths
ghcdir <- liftIO $ ghcupGHCDir tver ghcdir <- lift $ ghcupGHCDir tver
-- the subdir of the archive where we do the work -- 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) liftE $ runBuildAction tmpUnpack (Just ghcdir) (installGHC' workdir ghcdir)
@@ -128,66 +145,106 @@ installGHCBin bDls ver mpfReq = do
where where
-- | Install an unpacked GHC distribution. This only deals with the GHC build system and nothing else. -- | Install an unpacked GHC distribution. This only deals with the GHC build system and nothing else.
installGHC' :: (MonadLogger m, MonadIO m) installGHC' :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m)
=> Path Abs -- ^ Path to the unpacked GHC bindist (where the configure script resides) => Path Abs -- ^ Path to the unpacked GHC bindist (where the configure script resides)
-> Path Abs -- ^ Path to install to -> Path Abs -- ^ Path to install to
-> Excepts '[ProcessError] m () -> Excepts '[ProcessError] m ()
installGHC' path inst = do installGHC' path inst = do
lift $ $(logInfo) "Installing GHC (this may take a while)" lift $ $(logInfo) "Installing GHC (this may take a while)"
lEM $ liftIO $ execLogged "./configure" lEM $ execLogged "./configure"
False False
["--prefix=" <> toFilePath inst] (["--prefix=" <> toFilePath inst] ++ alpineArgs)
[rel|ghc-configure|] [rel|ghc-configure|]
(Just path) (Just path)
Nothing Nothing
lEM $ liftIO $ make ["install"] (Just path) lEM $ make ["install"] (Just path)
pure () pure ()
alpineArgs
| ver >= [vver|8.2.2|]
, Linux Alpine <- _rPlatform = ["--disable-ld-override"]
| otherwise = []
installCabalBin :: ( MonadMask m
, MonadCatch m -- | Installs GHC into @~\/.ghcup\/ghc/\<ver\>@ and places the
, MonadReader Settings m -- following symlinks in @~\/.ghcup\/bin@:
, MonadLogger m --
, MonadResource m -- * @ghc-x.y.z -> ..\/ghc\/x.y.z\/bin/ghc@
, MonadIO m -- * @ghc-x.y -> ..\/ghc\/x.y.z\/bin/ghc@ (if x.y.z is the latest x.y version)
, MonadFail m installGHCBin :: ( MonadFail m
) , MonadMask m
=> GHCupDownloads , MonadCatch m
-> Version , MonadReader Settings m
-> Maybe PlatformRequest -- ^ if Nothing, looks up current host platform , MonadLogger m
-> Excepts , MonadResource m
'[ AlreadyInstalled , MonadIO m
, CopyError )
, DigestError => GHCupDownloads -- ^ the download info to look up the tarball from
, DistroNotFound -> Version -- ^ the version to install
, DownloadFailed -> PlatformRequest -- ^ the platform to install on
, NoCompatibleArch -> Excepts
, NoCompatiblePlatform '[ AlreadyInstalled
, NoDownload , BuildFailed
, NotInstalled , DigestError
, UnknownArchive , DownloadFailed
] , NoDownload
m , NotInstalled
() , UnknownArchive
installCabalBin bDls ver mpfReq = do , TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installGHCBin bDls ver pfreq = do
dlinfo <- lE $ getDownloadInfo GHC ver pfreq bDls
installGHCBindist dlinfo ver pfreq
-- | Like 'installCabalBin', except takes the 'DownloadInfo' as
-- argument instead of looking it up from 'GHCupDownloads'.
installCabalBindist :: ( MonadMask m
, MonadCatch m
, MonadReader Settings m
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadFail m
)
=> DownloadInfo
-> Version
-> PlatformRequest
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installCabalBindist dlinfo ver (PlatformRequest {..}) = do
lift $ $(logDebug) [i|Requested to install cabal version #{ver}|] lift $ $(logDebug) [i|Requested to install cabal version #{ver}|]
bindir <- liftIO ghcupBinDir Settings {dirs = Dirs {..}} <- lift ask
whenM whenM
(liftIO $ cabalInstalled ver >>= \a -> (lift (cabalInstalled ver) >>= \a -> liftIO $
handleIO (\_ -> pure False) handleIO (\_ -> pure False)
$ fmap (\x -> a && isSymbolicLink x) $ fmap (\x -> a && isSymbolicLink x)
-- ignore when the installation is a legacy cabal (binary, not symlink) -- 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) $ (throwE $ AlreadyInstalled Cabal ver)
Settings {..} <- lift ask
pfreq@(PlatformRequest {..}) <- maybe (liftE $ platformRequest) pure mpfReq
-- download (or use cached version) -- download (or use cached version)
dlinfo <- lE $ getDownloadInfo Cabal ver pfreq bDls
dl <- liftE $ downloadCached dlinfo Nothing dl <- liftE $ downloadCached dlinfo Nothing
-- unpack -- unpack
@@ -196,12 +253,12 @@ installCabalBin bDls ver mpfReq = do
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ liftIO $ darwinNotarization _rPlatform tmpUnpack
-- the subdir of the archive where we do the work -- 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 liftE $ installCabal' workdir binDir
-- create symlink if this is the latest version -- create symlink if this is the latest version
cVers <- liftIO $ fmap rights $ getInstalledCabals cVers <- lift $ fmap rights $ getInstalledCabals
let lInstCabal = headMay . reverse . sort $ cVers let lInstCabal = headMay . reverse . sort $ cVers
when (maybe True (ver >=) lInstCabal) $ liftE $ setCabal ver when (maybe True (ver >=) lInstCabal) $ liftE $ setCabal ver
@@ -216,7 +273,7 @@ installCabalBin bDls ver mpfReq = do
installCabal' path inst = do installCabal' path inst = do
lift $ $(logInfo) "Installing cabal" lift $ $(logInfo) "Installing cabal"
let cabalFile = [rel|cabal|] let cabalFile = [rel|cabal|]
liftIO $ createDirIfMissing newDirPerms inst liftIO $ createDirRecursive newDirPerms inst
destFileName <- lift $ parseRel (toFilePath cabalFile <> "-" <> verToBS ver) destFileName <- lift $ parseRel (toFilePath cabalFile <> "-" <> verToBS ver)
handleIO (throwE . CopyError . show) $ liftIO $ copyFile handleIO (throwE . CopyError . show) $ liftIO $ copyFile
(path </> cabalFile) (path </> cabalFile)
@@ -224,6 +281,41 @@ installCabalBin bDls ver mpfReq = do
Overwrite Overwrite
-- | Installs cabal into @~\/.ghcup\/bin/cabal-\<ver\>@ and
-- creates a default @cabal -> cabal-x.y.z.q@ symlink for
-- the latest installed version.
installCabalBin :: ( MonadMask m
, MonadCatch m
, MonadReader Settings m
, MonadLogger m
, MonadResource m
, MonadIO m
, MonadFail m
)
=> GHCupDownloads
-> Version
-> PlatformRequest
-> Excepts
'[ AlreadyInstalled
, CopyError
, DigestError
, DownloadFailed
, NoDownload
, NotInstalled
, UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
]
m
()
installCabalBin bDls ver pfreq = do
dlinfo <- lE $ getDownloadInfo Cabal ver pfreq bDls
installCabalBindist dlinfo ver pfreq
--------------------- ---------------------
--[ Set GHC/cabal ]-- --[ Set GHC/cabal ]--
@@ -231,26 +323,32 @@ installCabalBin bDls ver mpfReq = do
-- | Set GHC symlinks in ~/.ghcup/bin for the requested GHC version. The behavior depends -- | Set GHC symlinks in @~\/.ghcup\/bin@ for the requested GHC version. The behavior depends
-- on `SetGHC`: -- on `SetGHC`:
-- --
-- * SetGHCOnly: ~/.ghcup/bin/ghc -> ~/.ghcup/ghc/<ver>/bin/ghc -- * SetGHCOnly: @~\/.ghcup\/bin\/ghc -> ~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
-- * SetGHC_XY: ~/.ghcup/bin/ghc-X.Y -> ~/.ghcup/ghc/<ver>/bin/ghc -- * SetGHC_XY: @~\/.ghcup\/bin\/ghc-X.Y -> ~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
-- * SetGHC_XYZ: ~/.ghcup/bin/ghc-<ver> -> ~/.ghcup/ghc/<ver>/bin/ghc -- * SetGHC_XYZ: @~\/.ghcup\/bin\/ghc-\<ver\> -> ~\/.ghcup\/ghc\/\<ver\>\/bin\/ghc@
-- --
-- Additionally creates a ~/.ghcup/share -> ~/.ghcup/ghc/<ver>/share symlink -- Additionally creates a @~\/.ghcup\/share -> ~\/.ghcup\/ghc\/\<ver\>\/share symlink@
-- for `SetGHCOnly` constructor. -- for 'SetGHCOnly' constructor.
setGHC :: (MonadLogger m, MonadThrow m, MonadFail m, MonadIO m) setGHC :: ( MonadReader Settings m
, MonadLogger m
, MonadThrow m
, MonadFail m
, MonadIO m
, MonadCatch m
)
=> GHCTargetVersion => GHCTargetVersion
-> SetGHC -> SetGHC
-> Excepts '[NotInstalled] m GHCTargetVersion -> Excepts '[NotInstalled] m GHCTargetVersion
setGHC ver sghc = do setGHC ver sghc = do
let verBS = verToBS (_tvVersion ver) let verBS = verToBS (_tvVersion ver)
ghcdir <- liftIO $ ghcupGHCDir ver ghcdir <- lift $ ghcupGHCDir ver
-- symlink destination -- symlink destination
bindir <- liftIO $ ghcupBinDir Settings { dirs = Dirs {..} } <- lift ask
liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms bindir liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms binDir
-- first delete the old symlinks (this fixes compatibility issues -- first delete the old symlinks (this fixes compatibility issues
-- with old ghcup) -- with old ghcup)
@@ -262,19 +360,26 @@ setGHC ver sghc = do
-- for ghc tools (ghc, ghci, haddock, ...) -- for ghc tools (ghc, ghci, haddock, ...)
verfiles <- ghcToolFiles ver verfiles <- ghcToolFiles ver
forM_ verfiles $ \file -> do forM_ verfiles $ \file -> do
targetFile <- case sghc of mTargetFile <- case sghc of
SetGHCOnly -> pure file SetGHCOnly -> pure $ Just file
SetGHC_XY -> do SetGHC_XY -> do
major' <- (\(mj, mi) -> E.encodeUtf8 $ intToText mj <> "." <> intToText mi) v' <-
<$> getMajorMinorV (_tvVersion ver) handle
parseRel (toFilePath file <> B.singleton _hyphen <> major') (\(e :: ParseError) -> lift $ $(logWarn) [i|#{e}|] >> pure Nothing)
SetGHC_XYZ -> parseRel (toFilePath file <> B.singleton _hyphen <> verBS) $ 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 -- create symlink
let fullF = bindir </> targetFile forM mTargetFile $ \targetFile -> do
let destL = ghcLinkDestination (toFilePath file) ver let fullF = binDir </> targetFile
lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|] destL <- lift $ ghcLinkDestination (toFilePath file) ver
liftIO $ createSymlink fullF destL lift $ $(logDebug) [i|ln -s #{destL} #{toFilePath fullF}|]
liftIO $ createSymlink fullF destL
-- create symlink for share dir -- create symlink for share dir
when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verBS when (isNothing . _tvTarget $ ver) $ lift $ symlinkShareDir ghcdir verBS
@@ -283,12 +388,13 @@ setGHC ver sghc = do
where where
symlinkShareDir :: (MonadIO m, MonadLogger m) symlinkShareDir :: (MonadReader Settings m, MonadIO m, MonadLogger m)
=> Path Abs => Path Abs
-> ByteString -> ByteString
-> m () -> m ()
symlinkShareDir ghcdir verBS = do symlinkShareDir ghcdir verBS = do
destdir <- liftIO $ ghcupBaseDir Settings { dirs = Dirs {..} } <- ask
let destdir = baseDir
case sghc of case sghc of
SetGHCOnly -> do SetGHCOnly -> do
let sharedir = [rel|share|] let sharedir = [rel|share|]
@@ -304,8 +410,8 @@ setGHC ver sghc = do
-- | Set the ~/.ghcup/bin/cabal symlink. -- | 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 => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
setCabal ver = do setCabal ver = do
@@ -313,14 +419,14 @@ setCabal ver = do
targetFile <- parseRel ("cabal-" <> verBS) targetFile <- parseRel ("cabal-" <> verBS)
-- symlink destination -- symlink destination
bindir <- liftIO $ ghcupBinDir Settings {dirs = Dirs {..}} <- lift ask
liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms bindir liftIO $ hideError AlreadyExists $ createDirRecursive newDirPerms binDir
whenM (liftIO $ fmap not $ doesFileExist (bindir </> targetFile)) whenM (liftIO $ fmap not $ doesFileExist (binDir </> targetFile))
$ throwE $ throwE
$ NotInstalled Cabal (prettyVer ver) $ NotInstalled Cabal (prettyVer ver)
let cabalbin = bindir </> [rel|cabal|] let cabalbin = binDir </> [rel|cabal|]
-- delete old file (may be binary or symlink) -- delete old file (may be binary or symlink)
lift $ $(logDebug) [i|rm -f #{toFilePath cabalbin}|] lift $ $(logDebug) [i|rm -f #{toFilePath cabalbin}|]
@@ -344,10 +450,13 @@ setCabal ver = do
------------------ ------------------
-- | Filter data type for 'listVersions'.
data ListCriteria = ListInstalled data ListCriteria = ListInstalled
| ListSet | ListSet
deriving Show deriving Show
-- | A list result describes a single tool version
-- and various of its properties.
data ListResult = ListResult data ListResult = ListResult
{ lTool :: Tool { lTool :: Tool
, lVer :: Version , lVer :: Version
@@ -362,6 +471,7 @@ data ListResult = ListResult
deriving (Eq, Ord, Show) deriving (Eq, Ord, Show)
-- | Extract all available tool versions and their tags.
availableToolVersions :: GHCupDownloads -> Tool -> Map.Map Version [Tag] availableToolVersions :: GHCupDownloads -> Tool -> Map.Map Version [Tag]
availableToolVersions av tool = view availableToolVersions av tool = view
(at tool % non Map.empty % to (fmap (_viTags))) (at tool % non Map.empty % to (fmap (_viTags)))
@@ -375,39 +485,34 @@ listVersions :: ( MonadCatch m
, MonadThrow m , MonadThrow m
, MonadLogger m , MonadLogger m
, MonadIO m , MonadIO m
, MonadReader Settings m
) )
=> GHCupDownloads => GHCupDownloads
-> Maybe Tool -> Maybe Tool
-> Maybe ListCriteria -> Maybe ListCriteria
-> Excepts -> PlatformRequest
'[ NoCompatiblePlatform -> m [ListResult]
, NoCompatibleArch listVersions av lt criteria pfreq = do
, DistroNotFound
]
m
[ListResult]
listVersions av lt criteria = do
pfreq <- platformRequest
case lt of case lt of
Just t -> do Just t -> do
-- get versions from GHCupDownloads -- get versions from GHCupDownloads
let avTools = availableToolVersions av t let avTools = availableToolVersions av t
lr <- filter' <$> forM (Map.toList avTools) (liftIO . toListResult pfreq t) lr <- filter' <$> forM (Map.toList avTools) (toListResult t)
case t of case t of
-- append stray GHCs -- append stray GHCs
GHC -> do GHC -> do
slr <- lift $ strayGHCs avTools slr <- strayGHCs avTools
pure $ (sort (slr ++ lr)) pure $ (sort (slr ++ lr))
_ -> pure lr _ -> pure lr
Nothing -> do Nothing -> do
ghcvers <- listVersions av (Just GHC) criteria ghcvers <- listVersions av (Just GHC) criteria pfreq
cabalvers <- listVersions av (Just Cabal) criteria cabalvers <- listVersions av (Just Cabal) criteria pfreq
ghcupvers <- listVersions av (Just GHCup) criteria ghcupvers <- listVersions av (Just GHCup) criteria pfreq
pure (ghcvers <> cabalvers <> ghcupvers) pure (ghcvers <> cabalvers <> ghcupvers)
where where
strayGHCs :: (MonadThrow m, MonadLogger m, MonadIO m) strayGHCs :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m)
=> Map.Map Version [Tag] => Map.Map Version [Tag]
-> m [ListResult] -> m [ListResult]
strayGHCs avTools = do strayGHCs avTools = do
@@ -418,7 +523,7 @@ listVersions av lt criteria = do
Just _ -> pure Nothing Just _ -> pure Nothing
Nothing -> do Nothing -> do
lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet Nothing lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet Nothing
fromSrc <- liftIO $ ghcSrcInstalled tver fromSrc <- ghcSrcInstalled tver
pure $ Just $ ListResult pure $ Just $ ListResult
{ lTool = GHC { lTool = GHC
, lVer = _tvVersion , lVer = _tvVersion
@@ -431,7 +536,7 @@ listVersions av lt criteria = do
} }
Right tver@GHCTargetVersion{ .. } -> do Right tver@GHCTargetVersion{ .. } -> do
lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet _tvTarget lSet <- fmap (maybe False (\(GHCTargetVersion _ v ) -> v == _tvVersion)) $ ghcSet _tvTarget
fromSrc <- liftIO $ ghcSrcInstalled tver fromSrc <- ghcSrcInstalled tver
pure $ Just $ ListResult pure $ Just $ ListResult
{ lTool = GHC { lTool = GHC
, lVer = _tvVersion , lVer = _tvVersion
@@ -448,8 +553,8 @@ listVersions av lt criteria = do
pure Nothing pure Nothing
-- NOTE: this are not cross ones, because no bindists -- NOTE: this are not cross ones, because no bindists
toListResult :: PlatformRequest -> Tool -> (Version, [Tag]) -> IO ListResult toListResult :: (MonadReader Settings m, MonadIO m, MonadCatch m) => Tool -> (Version, [Tag]) -> m ListResult
toListResult pfreq t (v, tags) = case t of toListResult t (v, tags) = case t of
GHC -> do GHC -> do
let lNoBindist = isLeft $ getDownloadInfo GHC v pfreq av let lNoBindist = isLeft $ getDownloadInfo GHC v pfreq av
let tver = mkTVer v let tver = mkTVer v
@@ -496,13 +601,23 @@ listVersions av lt criteria = do
-------------------- --------------------
-- | This function may throw and crash in various ways. -- | Delete a ghc version and all its symlinks.
rmGHCVer :: (MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) --
-- 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
, MonadCatch m
)
=> GHCTargetVersion => GHCTargetVersion
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
rmGHCVer ver = do rmGHCVer ver = do
isSetGHC <- fmap (maybe False (== ver)) $ ghcSet (_tvTarget ver) isSetGHC <- lift $ fmap (maybe False (== ver)) $ ghcSet (_tvTarget ver)
dir <- liftIO $ ghcupGHCDir ver dir <- lift $ ghcupGHCDir ver
let d' = toFilePath dir let d' = toFilePath dir
exists <- liftIO $ doesDirectoryExist dir exists <- liftIO $ doesDirectoryExist dir
@@ -522,38 +637,46 @@ rmGHCVer ver = do
lift $ $(logInfo) [i|Removing/rewiring ghc-x.y symlinks|] lift $ $(logInfo) [i|Removing/rewiring ghc-x.y symlinks|]
-- first remove -- first remove
lift $ rmMajorSymlinks ver handle (\(_ :: ParseError) -> pure ()) $ lift $ rmMajorSymlinks ver
-- then fix them (e.g. with an earlier version) -- then fix them (e.g. with an earlier version)
(mj, mi) <- getMajorMinorV (_tvVersion ver) v' <-
getGHCForMajor mj mi (_tvTarget ver) >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) 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
liftIO liftIO
$ ghcupBaseDir $ hideError doesNotExistErrorType
>>= hideError doesNotExistErrorType $ deleteFile
. deleteFile $ (baseDir </> [rel|share|])
. (</> [rel|share|])
else throwE (NotInstalled GHC (ver ^. tvVersion % to prettyVer)) else throwE (NotInstalled GHC (ver ^. tvVersion % to prettyVer))
-- | This function may throw and crash in various ways. -- | Delete a cabal version. Will try to fix the @cabal@ symlink
rmCabalVer :: (MonadThrow m, MonadLogger m, MonadIO m, MonadFail m) -- after removal (e.g. setting it to an older version).
rmCabalVer :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m, MonadFail m, MonadCatch m)
=> Version => Version
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
rmCabalVer ver = do rmCabalVer ver = do
whenM (fmap not $ liftIO $ cabalInstalled ver) $ throwE (NotInstalled GHC (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) cabalFile <- lift $ parseRel ("cabal-" <> verToBS ver)
liftIO $ hideError doesNotExistErrorType $ deleteFile (bindir </> cabalFile) liftIO $ hideError doesNotExistErrorType $ deleteFile (binDir </> cabalFile)
when (maybe False (== ver) cSet) $ do when (maybe False (== ver) cSet) $ do
cVers <- liftIO $ fmap rights $ getInstalledCabals cVers <- lift $ fmap rights $ getInstalledCabals
case headMay . reverse . sort $ cVers of case headMay . reverse . sort $ cVers of
Just latestver -> setCabal latestver Just latestver -> setCabal latestver
Nothing -> liftIO $ hideError doesNotExistErrorType $ deleteFile Nothing -> liftIO $ hideError doesNotExistErrorType $ deleteFile
(bindir </> [rel|cabal|]) (binDir </> [rel|cabal|])
@@ -562,18 +685,19 @@ rmCabalVer ver = do
------------------ ------------------
getDebugInfo :: (MonadLogger m, MonadCatch m, MonadIO m) getDebugInfo :: (MonadReader Settings m, MonadLogger m, MonadCatch m, MonadIO m)
=> Excepts => Excepts
'[NoCompatiblePlatform , NoCompatibleArch , DistroNotFound] '[NoCompatiblePlatform , NoCompatibleArch , DistroNotFound]
m m
DebugInfo DebugInfo
getDebugInfo = do getDebugInfo = do
diBaseDir <- liftIO $ ghcupBaseDir Settings {dirs = Dirs {..}} <- lift ask
diBinDir <- liftIO $ ghcupBinDir let diBaseDir = baseDir
diGHCDir <- liftIO $ ghcupGHCBaseDir let diBinDir = binDir
diCacheDir <- liftIO $ ghcupCacheDir diGHCDir <- lift ghcupGHCBaseDir
diArch <- lE getArchitecture let diCacheDir = cacheDir
diPlatform <- liftE $ getPlatform diArch <- lE getArchitecture
diPlatform <- liftE $ getPlatform
pure $ DebugInfo { .. } pure $ DebugInfo { .. }
@@ -584,6 +708,8 @@ getDebugInfo = do
--------------- ---------------
-- | Compile a GHC from source. This behaves wrt symlinks and installation
-- the same as 'installGHCBin'.
compileGHC :: ( MonadMask m compileGHC :: ( MonadMask m
, MonadReader Settings m , MonadReader Settings m
, MonadThrow m , MonadThrow m
@@ -599,25 +725,27 @@ compileGHC :: ( MonadMask m
-> Maybe (Path Abs) -- ^ build config -> Maybe (Path Abs) -- ^ build config
-> Maybe (Path Abs) -- ^ patch directory -> Maybe (Path Abs) -- ^ patch directory
-> [Text] -- ^ additional args to ./configure -> [Text] -- ^ additional args to ./configure
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, BuildFailed , BuildFailed
, DigestError , DigestError
, DistroNotFound
, DownloadFailed , DownloadFailed
, GHCupSetError , GHCupSetError
, NoCompatibleArch
, NoCompatiblePlatform
, NoDownload , NoDownload
, NotFoundInPATH , NotFoundInPATH
, PatchFailed , PatchFailed
, UnknownArchive , UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
] ]
m m
() ()
compileGHC dls tver bstrap jobs mbuildConfig patchdir aargs = do compileGHC dls tver bstrap jobs mbuildConfig patchdir aargs PlatformRequest {..} = do
lift $ $(logDebug) [i|Requested to compile: #{tver} with #{bstrap}|] lift $ $(logDebug) [i|Requested to compile: #{tver} with #{bstrap}|]
whenM (liftIO $ ghcInstalled tver) whenM (lift $ ghcInstalled tver)
(throwE $ AlreadyInstalled GHC (tver ^. tvVersion)) (throwE $ AlreadyInstalled GHC (tver ^. tvVersion))
-- download source tarball -- download source tarball
@@ -629,14 +757,13 @@ compileGHC dls tver bstrap jobs mbuildConfig patchdir aargs = do
-- unpack -- unpack
tmpUnpack <- lift mkGhcupTmpDir tmpUnpack <- lift mkGhcupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
(PlatformRequest {..}) <- liftE $ platformRequest
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack void $ liftIO $ darwinNotarization _rPlatform tmpUnpack
bghc <- case bstrap of bghc <- case bstrap of
Right g -> pure $ Right g Right g -> pure $ Right g
Left bver -> Left <$> parseRel ("ghc-" <> verToBS bver) 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 <- liftIO $ ghcupGHCDir tver ghcdir <- lift $ ghcupGHCDir tver
liftE $ runBuildAction liftE $ runBuildAction
tmpUnpack tmpUnpack
@@ -662,7 +789,7 @@ BUILD_SPHINX_PDF = NO
HADDOCK_DOCS = NO HADDOCK_DOCS = NO
Stage1Only = YES|] Stage1Only = YES|]
compile :: (MonadCatch m, MonadLogger m, MonadIO m) compile :: (MonadReader Settings m, MonadThrow m, MonadCatch m, MonadLogger m, MonadIO m)
=> Either (Path Rel) (Path Abs) => Either (Path Rel) (Path Abs)
-> Path Abs -> Path Abs
-> Path Abs -> Path Abs
@@ -690,7 +817,7 @@ Stage1Only = YES|]
Left bver -> do Left bver -> do
spaths <- catMaybes . fmap parseAbs <$> liftIO getSearchPath spaths <- catMaybes . fmap parseAbs <$> liftIO getSearchPath
(liftIO $ searchPath spaths bver) !? NotFoundInPATH bver (liftIO $ searchPath spaths bver) !? NotFoundInPATH bver
lEM $ liftIO $ execLogged lEM $ execLogged
"./configure" "./configure"
False False
( ["--prefix=" <> toFilePath ghcdir] ( ["--prefix=" <> toFilePath ghcdir]
@@ -704,7 +831,7 @@ Stage1Only = YES|]
(Just workdir) (Just workdir)
(Just (("GHC", toFilePath bghcPath) : cEnv)) (Just (("GHC", toFilePath bghcPath) : cEnv))
| otherwise -> do | otherwise -> do
lEM $ liftIO $ execLogged lEM $ execLogged
"./configure" "./configure"
False False
( [ "--prefix=" <> toFilePath ghcdir ( [ "--prefix=" <> toFilePath ghcdir
@@ -729,11 +856,11 @@ Stage1Only = YES|]
liftIO $ writeFile (build_mk workdir) (Just newFilePerms) defaultConf liftIO $ writeFile (build_mk workdir) (Just newFilePerms) defaultConf
lift $ $(logInfo) [i|Building (this may take a while)...|] lift $ $(logInfo) [i|Building (this may take a while)...|]
lEM $ liftIO $ make (maybe [] (\j -> ["-j" <> fS (show j)]) jobs) lEM $ make (maybe [] (\j -> ["-j" <> fS (show j)]) jobs)
(Just workdir) (Just workdir)
lift $ $(logInfo) [i|Installing...|] lift $ $(logInfo) [i|Installing...|]
lEM $ liftIO $ make ["install"] (Just workdir) lEM $ make ["install"] (Just workdir)
markSrcBuilt ghcdir workdir = do markSrcBuilt ghcdir workdir = do
let dest = (ghcdir </> ghcUpSrcBuiltFile) let dest = (ghcdir </> ghcUpSrcBuiltFile)
@@ -765,6 +892,8 @@ Stage1Only = YES|]
-- | Compile a cabal from source. This behaves wrt symlinks and installation
-- the same as 'installCabalBin'.
compileCabal :: ( MonadReader Settings m compileCabal :: ( MonadReader Settings m
, MonadResource m , MonadResource m
, MonadMask m , MonadMask m
@@ -777,33 +906,35 @@ compileCabal :: ( MonadReader Settings m
-> Either Version (Path Abs) -- ^ version to bootstrap with -> Either Version (Path Abs) -- ^ version to bootstrap with
-> Maybe Int -> Maybe Int
-> Maybe (Path Abs) -> Maybe (Path Abs)
-> PlatformRequest
-> Excepts -> Excepts
'[ AlreadyInstalled '[ AlreadyInstalled
, BuildFailed , BuildFailed
, CopyError , CopyError
, DigestError , DigestError
, DistroNotFound
, DownloadFailed , DownloadFailed
, NoCompatibleArch
, NoCompatiblePlatform
, NoDownload , NoDownload
, NotInstalled , NotInstalled
, PatchFailed , PatchFailed
, UnknownArchive , UnknownArchive
, TarDirDoesNotExist
#if !defined(TAR)
, ArchiveResult
#endif
] ]
m m
() ()
compileCabal dls tver bghc jobs patchdir = do compileCabal dls tver bghc jobs patchdir PlatformRequest{..} = do
lift $ $(logDebug) [i|Requested to compile: #{tver} with ghc-#{bghc}|] lift $ $(logDebug) [i|Requested to compile: #{tver} with ghc-#{bghc}|]
bindir <- liftIO ghcupBinDir Settings {dirs = Dirs {..}} <- lift ask
whenM whenM
(liftIO $ cabalInstalled tver >>= \a -> (lift (cabalInstalled tver) >>= \a -> liftIO $
handleIO (\_ -> pure False) handleIO (\_ -> pure False)
$ fmap (\x -> a && isSymbolicLink x) $ fmap (\x -> a && isSymbolicLink x)
-- ignore when the installation is a legacy cabal (binary, not symlink) -- 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) $ (throwE $ AlreadyInstalled Cabal tver)
@@ -814,28 +945,27 @@ compileCabal dls tver bghc jobs patchdir = do
-- unpack -- unpack
tmpUnpack <- lift mkGhcupTmpDir tmpUnpack <- lift mkGhcupTmpDir
liftE $ unpackToDir tmpUnpack dl liftE $ unpackToDir tmpUnpack dl
(PlatformRequest {..}) <- liftE $ platformRequest
void $ liftIO $ darwinNotarization _rPlatform tmpUnpack 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) cbin <- liftE $ runBuildAction tmpUnpack Nothing (compile workdir)
destFileName <- lift $ parseRel ("cabal-" <> verToBS tver) destFileName <- lift $ parseRel ("cabal-" <> verToBS tver)
handleIO (throwE . CopyError . show) $ liftIO $ copyFile handleIO (throwE . CopyError . show) $ liftIO $ copyFile
cbin cbin
(bindir </> destFileName) (binDir </> destFileName)
Overwrite Overwrite
-- create symlink if this is the latest version -- create symlink if this is the latest version
cVers <- liftIO $ fmap rights $ getInstalledCabals cVers <- lift $ fmap rights $ getInstalledCabals
let lInstCabal = headMay . reverse . sort $ cVers let lInstCabal = headMay . reverse . sort $ cVers
when (maybe True (tver >=) lInstCabal) $ liftE $ setCabal tver when (maybe True (tver >=) lInstCabal) $ liftE $ setCabal tver
pure () pure ()
where where
compile :: (MonadThrow m, MonadLogger m, MonadIO m, MonadResource m) compile :: (MonadReader Settings m, MonadThrow m, MonadLogger m, MonadIO m, MonadResource m)
=> Path Abs => Path Abs
-> Excepts '[ProcessError , PatchFailed] m (Path Abs) -> Excepts '[ProcessError , PatchFailed] m (Path Abs)
compile workdir = do compile workdir = do
@@ -868,7 +998,7 @@ compileCabal dls tver bghc jobs patchdir = do
newEnv <- lift $ addToCurrentEnv (("PREFIX", toFilePath tmp) : ghcEnv) newEnv <- lift $ addToCurrentEnv (("PREFIX", toFilePath tmp) : ghcEnv)
lift $ $(logDebug) [i|Environment: #{newEnv}|] lift $ $(logDebug) [i|Environment: #{newEnv}|]
lEM $ liftIO $ execLogged "./bootstrap.sh" lEM $ execLogged "./bootstrap.sh"
False False
(maybe [] (\j -> ["-j", fS (show j)]) jobs) (maybe [] (\j -> ["-j", fS (show j)]) jobs)
[rel|cabal-bootstrap|] [rel|cabal-bootstrap|]
@@ -884,6 +1014,8 @@ compileCabal dls tver bghc jobs patchdir = do
--------------------- ---------------------
-- | Upgrade ghcup and place it in @~\/.ghcup\/bin\/ghcup@,
-- if no path is provided.
upgradeGHCup :: ( MonadMask m upgradeGHCup :: ( MonadMask m
, MonadReader Settings m , MonadReader Settings m
, MonadCatch m , MonadCatch m
@@ -896,23 +1028,21 @@ upgradeGHCup :: ( MonadMask m
-> Maybe (Path Abs) -- ^ full file destination to write ghcup into -> Maybe (Path Abs) -- ^ full file destination to write ghcup into
-> Bool -- ^ whether to force update regardless -> Bool -- ^ whether to force update regardless
-- of currently installed version -- of currently installed version
-> PlatformRequest
-> Excepts -> Excepts
'[ CopyError '[ CopyError
, DigestError , DigestError
, DistroNotFound
, DownloadFailed , DownloadFailed
, NoCompatibleArch
, NoCompatiblePlatform
, NoDownload , NoDownload
, NoUpdate , NoUpdate
] ]
m m
Version Version
upgradeGHCup dls mtarget force = do upgradeGHCup dls mtarget force pfreq = do
Settings {dirs = Dirs {..}} <- lift ask
lift $ $(logInfo) [i|Upgrading GHCup...|] lift $ $(logInfo) [i|Upgrading GHCup...|]
let latestVer = fromJust $ getLatest dls GHCup let latestVer = fromJust $ getLatest dls GHCup
when (not force && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate when (not force && (latestVer <= pvpToVersion ghcUpVer)) $ throwE NoUpdate
pfreq <- liftE platformRequest
dli <- lE $ getDownloadInfo GHCup latestVer pfreq dls dli <- lE $ getDownloadInfo GHCup latestVer pfreq dls
tmp <- lift withGHCupTmpDir tmp <- lift withGHCupTmpDir
let fn = [rel|ghcup|] let fn = [rel|ghcup|]
@@ -922,20 +1052,12 @@ upgradeGHCup dls mtarget force = do
`unionFileModes` ownerExecuteMode `unionFileModes` ownerExecuteMode
`unionFileModes` groupExecuteMode `unionFileModes` groupExecuteMode
`unionFileModes` otherExecuteMode `unionFileModes` otherExecuteMode
case mtarget of let fullDest = fromMaybe (binDir </> fn) mtarget
Nothing -> do liftIO $ hideError NoSuchThing $ deleteFile fullDest
dest <- liftIO $ ghcupBinDir handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
liftIO $ hideError NoSuchThing $ deleteFile (dest </> fn) fullDest
handleIO (throwE . CopyError . show) $ liftIO $ copyFile p Overwrite
(dest </> fn) liftIO $ setFileMode (toFilePath fullDest) fileMode'
Overwrite
liftIO $ setFileMode (toFilePath (dest </> fn)) fileMode'
Just fullDest -> do
liftIO $ hideError NoSuchThing $ deleteFile fullDest
handleIO (throwE . CopyError . show) $ liftIO $ copyFile p
fullDest
Overwrite
liftIO $ setFileMode (toFilePath fullDest) fileMode'
pure latestVer pure latestVer
@@ -945,15 +1067,26 @@ upgradeGHCup dls mtarget force = do
------------- -------------
-- | Creates ghc-x.y.z and ghc-x.y symlinks. This is used for -- | Creates @ghc-x.y.z@ and @ghc-x.y@ symlinks. This is used for
-- both installing from source and bindist. -- 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
, MonadCatch m
)
=> GHCTargetVersion => GHCTargetVersion
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
postGHCInstall ver@GHCTargetVersion{..} = do postGHCInstall ver@GHCTargetVersion {..} = do
void $ liftE $ setGHC ver SetGHC_XYZ void $ liftE $ setGHC ver SetGHC_XYZ
-- Create ghc-x.y symlinks. This may not be the current -- Create ghc-x.y symlinks. This may not be the current
-- version, create it regardless. -- version, create it regardless.
(mj, mi) <- getMajorMinorV _tvVersion v' <-
getGHCForMajor mj mi _tvTarget >>= mapM_ (\v -> liftE $ setGHC v SetGHC_XY) 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)

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +0,0 @@
module GHCup.Data.GHCupInfo where
import GHCup.Data.GHCupDownloads
import GHCup.Data.ToolRequirements
import GHCup.Types
ghcupInfo :: GHCupInfo
ghcupInfo = GHCupInfo { _toolRequirements = toolRequirements
, _ghcupDownloads = ghcupDownloads
}

View File

@@ -1,147 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module GHCup.Data.ToolRequirements where
import GHCup.Types
import GHCup.Utils.String.QQ
import GHCup.Utils.Version.QQ
import qualified Data.Map as M
-- | Currently 'GHC' is used for both GHC and cabal to simplify
-- this, until we need actual separation.
toolRequirements :: ToolRequirements
toolRequirements = M.fromList
[ ( GHC
, M.fromList
[ ( Nothing
, M.fromList
[ ( Linux UnknownLinux
, M.fromList
[ ( Nothing
, Requirements
[]
[s|You need the following packages: curl g++ gcc gmp make ncurses realpath xz-utils. Consult your distro documentation on the exact names of those packages.|]
)
]
)
, ( Linux Alpine
, M.fromList
[ ( Nothing
, Requirements
[ "curl"
, "gcc"
, "g++"
, "gmp-dev"
, "ncurses-dev"
, "libffi-dev"
, "make"
, "xz"
, "tar"
, "perl"
]
""
)
]
)
, ( Linux Ubuntu
, M.fromList
[ ( Nothing
, Requirements
[ "build-essential"
, "curl"
, "libffi-dev"
, "libffi6"
, "libgmp-dev"
, "libgmp10"
, "libncurses-dev"
, "libncurses5"
, "libtinfo5"
]
""
)
]
)
, ( Linux Debian
, M.fromList
[ ( Nothing
, Requirements
[ "build-essential"
, "curl"
, "libffi-dev"
, "libffi6"
, "libgmp-dev"
, "libgmp10"
, "libncurses-dev"
, "libncurses5"
, "libtinfo5"
]
""
)
]
)
, ( Linux CentOS
, M.fromList
[ ( Nothing
, Requirements
[ "gcc"
, "gcc-c++"
, "gmp"
, "gmp-devel"
, "make"
, "ncurses"
, "ncurses-compat-libs"
, "xz"
, "perl"
]
""
),
( Just [vers|7|]
, Requirements
[ "gcc"
, "gcc-c++"
, "gmp"
, "gmp-devel"
, "make"
, "ncurses"
, "xz"
, "perl"
]
""
)
]
)
, ( Darwin
, M.fromList
[ ( Nothing
, Requirements
[]
"On OS X, in the course of running ghcup you will be given a dialog box to install the command line tools. Accept and the requirements will be installed for you. You will then need to run the command again."
)
]
)
, ( FreeBSD
, M.fromList
[ ( Nothing
, Requirements
[ "curl"
, "gcc"
, "gmp"
, "gmake"
, "ncurses"
, "perl5"
, "libffi"
, "libiconv"
]
""
)
]
)
]
)
]
)
]

View File

@@ -9,6 +9,23 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-|
Module : GHCup.Download
Description : Downloading
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
Module for handling all download related functions.
Generally we support downloading via:
- curl (default)
- wget
- internal downloader (only when compiled)
-}
module GHCup.Download where module GHCup.Download where
#if defined(INTERNAL_DOWNLOADER) #if defined(INTERNAL_DOWNLOADER)
@@ -35,6 +52,7 @@ import Control.Monad.Reader
import Control.Monad.Trans.Resource import Control.Monad.Trans.Resource
hiding ( throwM ) hiding ( throwM )
import Data.Aeson import Data.Aeson
import Data.Bifunctor
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
#if defined(INTERNAL_DOWNLOADER) #if defined(INTERNAL_DOWNLOADER)
import Data.CaseInsensitive ( CI ) import Data.CaseInsensitive ( CI )
@@ -50,7 +68,7 @@ import Data.Versions
import Data.Word8 import Data.Word8
import GHC.IO.Exception import GHC.IO.Exception
import HPath import HPath
import HPath.IO as HIO import HPath.IO as HIO hiding ( hideError )
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Optics import Optics
import Prelude hiding ( abs import Prelude hiding ( abs
@@ -71,6 +89,7 @@ import qualified Data.Map.Strict as M
import qualified Data.Text as T import qualified Data.Text as T
#endif #endif
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified Data.Yaml as Y
import qualified System.Posix.Files.ByteString as PF import qualified System.Posix.Files.ByteString as PF
import qualified System.Posix.RawFilePath.Directory import qualified System.Posix.RawFilePath.Directory
as RD as RD
@@ -86,7 +105,7 @@ import qualified System.Posix.RawFilePath.Directory
-- | Like 'getDownloads', but tries to fall back to -- | Like 'getDownloads', but tries to fall back to
-- cached ~/.ghcup/cache/ghcup-<format-ver>.json -- cached ~/.ghcup/cache/ghcup-<format-ver>.yaml
getDownloadsF :: ( FromJSONKey Tool getDownloadsF :: ( FromJSONKey Tool
, FromJSONKey Version , FromJSONKey Version
, FromJSON VersionInfo , FromJSON VersionInfo
@@ -114,17 +133,17 @@ getDownloadsF urlSource = do
(OwnSpec _) -> liftE $ getDownloads urlSource (OwnSpec _) -> liftE $ getDownloads urlSource
where where
readFromCache = do readFromCache = do
Settings {dirs = Dirs {..}} <- lift ask
lift $ $(logWarn) lift $ $(logWarn)
[i|Could not get download info, trying cached version (this may not be recent!)|] [i|Could not get download info, trying cached version (this may not be recent!)|]
let path = view pathL' ghcupURL let path = view pathL' ghcupURL
cacheDir <- liftIO $ ghcupCacheDir yaml_file <- (cacheDir </>) <$> urlBaseName path
json_file <- (cacheDir </>) <$> urlBaseName path
bs <- bs <-
handleIO' NoSuchThing handleIO' NoSuchThing
(\_ -> throwE $ FileDoesNotExistError (toFilePath json_file)) (\_ -> throwE $ FileDoesNotExistError (toFilePath yaml_file))
$ liftIO $ liftIO
$ readFile json_file $ readFile yaml_file
lE' JSONDecodeError $ eitherDecode' bs lE' JSONDecodeError $ bimap show id $ Y.decodeEither' (L.toStrict bs)
-- | Downloads the download information! But only if we need to ;P -- | Downloads the download information! But only if we need to ;P
@@ -145,10 +164,10 @@ getDownloads urlSource = do
case urlSource of case urlSource of
GHCupURL -> do GHCupURL -> do
bs <- reThrowAll DownloadFailed $ smartDl ghcupURL bs <- reThrowAll DownloadFailed $ smartDl ghcupURL
lE' JSONDecodeError $ eitherDecode' bs lE' JSONDecodeError $ bimap show id $ Y.decodeEither' (L.toStrict bs)
(OwnSource url) -> do (OwnSource url) -> do
bs <- reThrowAll DownloadFailed $ downloadBS url bs <- reThrowAll DownloadFailed $ downloadBS url
lE' JSONDecodeError $ eitherDecode' bs lE' JSONDecodeError $ bimap show id $ Y.decodeEither' (L.toStrict bs)
(OwnSpec av) -> pure $ av (OwnSpec av) -> pure $ av
where where
@@ -181,8 +200,8 @@ getDownloads urlSource = do
m1 m1
L.ByteString L.ByteString
smartDl uri' = do smartDl uri' = do
Settings {dirs = Dirs {..}} <- lift ask
let path = view pathL' uri' let path = view pathL' uri'
cacheDir <- liftIO $ ghcupCacheDir
json_file <- (cacheDir </>) <$> urlBaseName path json_file <- (cacheDir </>) <$> urlBaseName path
e <- liftIO $ doesFileExist json_file e <- liftIO $ doesFileExist json_file
if e if e
@@ -207,7 +226,7 @@ getDownloads urlSource = do
else -- access in less than 5 minutes, re-use file else -- access in less than 5 minutes, re-use file
liftIO $ readFile json_file liftIO $ readFile json_file
else do else do
liftIO $ createDirIfMissing newDirPerms cacheDir liftIO $ createDirRecursive newDirPerms cacheDir
getModTime >>= \case getModTime >>= \case
Just modTime -> dlWithMod modTime json_file Just modTime -> dlWithMod modTime json_file
Nothing -> do Nothing -> do
@@ -270,7 +289,10 @@ getDownloadInfo :: Tool
getDownloadInfo t v (PlatformRequest a p mv) dls = maybe getDownloadInfo t v (PlatformRequest a p mv) dls = maybe
(Left NoDownload) (Left NoDownload)
Right Right
(with_distro <|> without_distro_ver <|> without_distro) (case p of
-- non-musl won't work on alpine
Linux Alpine -> with_distro <|> without_distro_ver
_ -> with_distro <|> without_distro_ver <|> without_distro)
where where
with_distro = distro_preview id id with_distro = distro_preview id id
@@ -370,15 +392,15 @@ downloadCached dli mfn = do
cache <- lift getCache cache <- lift getCache
case cache of case cache of
True -> do True -> do
cachedir <- liftIO $ ghcupCacheDir Settings {dirs = Dirs {..}} <- lift ask
fn <- maybe (urlBaseName $ view (dlUri % pathL') dli) pure mfn fn <- maybe (urlBaseName $ view (dlUri % pathL') dli) pure mfn
let cachfile = cachedir </> fn let cachfile = cacheDir </> fn
fileExists <- liftIO $ doesFileExist cachfile fileExists <- liftIO $ doesFileExist cachfile
if if
| fileExists -> do | fileExists -> do
liftE $ checkDigest dli cachfile liftE $ checkDigest dli cachfile
pure $ cachfile pure $ cachfile
| otherwise -> liftE $ download dli cachedir mfn | otherwise -> liftE $ download dli cacheDir mfn
False -> do False -> do
tmp <- lift withGHCupTmpDir tmp <- lift withGHCupTmpDir
liftE $ download dli tmp mfn liftE $ download dli tmp mfn

View File

@@ -3,6 +3,15 @@
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-|
Module : GHCup.Errors
Description : GHCup error types
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Errors where module GHCup.Errors where
import GHCup.Types import GHCup.Types
@@ -80,6 +89,9 @@ data JSONError = JSONDecodeError String
data FileDoesNotExistError = FileDoesNotExistError ByteString data FileDoesNotExistError = FileDoesNotExistError ByteString
deriving Show deriving Show
data TarDirDoesNotExist = TarDirDoesNotExist TarDir
deriving Show
-- | File digest verification failed. -- | File digest verification failed.
data DigestError = DigestError Text Text data DigestError = DigestError Text Text
deriving Show deriving Show

View File

@@ -6,13 +6,21 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-|
Module : GHCup.Plaform
Description : Retrieving platform information
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Platform where module GHCup.Platform where
import GHCup.Errors import GHCup.Errors
import GHCup.Types import GHCup.Types
import GHCup.Types.JSON ( ) import GHCup.Types.JSON ( )
import GHCup.Utils.Bash
import GHCup.Utils.File import GHCup.Utils.File
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import GHCup.Utils.String.QQ import GHCup.Utils.String.QQ
@@ -36,6 +44,7 @@ import Prelude hiding ( abs
, writeFile , writeFile
) )
import System.Info import System.Info
import System.OsRelease
import Text.Regex.Posix import Text.Regex.Posix
import qualified Data.Text as T import qualified Data.Text as T
@@ -48,10 +57,7 @@ import qualified Data.Text as T
-- | Get the full platform request, consisting of architecture, distro, ... -- | Get the full platform request, consisting of architecture, distro, ...
platformRequest :: (MonadLogger m, MonadCatch m, MonadIO m) platformRequest :: (MonadLogger m, MonadCatch m, MonadIO m)
=> Excepts => Excepts
'[ NoCompatiblePlatform '[NoCompatiblePlatform, NoCompatibleArch, DistroNotFound]
, NoCompatibleArch
, DistroNotFound
]
m m
PlatformRequest PlatformRequest
platformRequest = do platformRequest = do
@@ -62,15 +68,21 @@ platformRequest = do
getArchitecture :: Either NoCompatibleArch Architecture getArchitecture :: Either NoCompatibleArch Architecture
getArchitecture = case arch of getArchitecture = case arch of
"x86_64" -> Right A_64 "x86_64" -> Right A_64
"i386" -> Right A_32 "i386" -> Right A_32
what -> Left (NoCompatibleArch what) "powerpc" -> Right A_PowerPC
"powerpc64" -> Right A_PowerPC64
"powerpc64le" -> Right A_PowerPC64
"sparc" -> Right A_Sparc
"sparc64" -> Right A_Sparc64
"arm" -> Right A_ARM
"aarch64" -> Right A_ARM64
what -> Left (NoCompatibleArch what)
getPlatform :: (MonadLogger m, MonadCatch m, MonadIO m) getPlatform :: (MonadLogger m, MonadCatch m, MonadIO m)
=> Excepts => Excepts
'[NoCompatiblePlatform , DistroNotFound] '[NoCompatiblePlatform, DistroNotFound]
m m
PlatformResult PlatformResult
getPlatform = do getPlatform = do
@@ -82,6 +94,7 @@ getPlatform = do
ver <- ver <-
( either (const Nothing) Just ( either (const Nothing) Just
. versioning . versioning
-- TODO: maybe do this somewhere else
. getMajorVersion . getMajorVersion
. decUTF8Safe . decUTF8Safe
) )
@@ -111,7 +124,6 @@ getLinuxDistro = do
(name, ver) <- handleIO (\_ -> throwE DistroNotFound) $ liftIO $ asum (name, ver) <- handleIO (\_ -> throwE DistroNotFound) $ liftIO $ asum
[ try_os_release [ try_os_release
, try_lsb_release_cmd , try_lsb_release_cmd
, try_lsb_release
, try_redhat_release , try_redhat_release
, try_debian_version , try_debian_version
] ]
@@ -136,10 +148,6 @@ getLinuxDistro = do
where where
regex x = makeRegexOpts compIgnoreCase execBlank ([s|\<|] ++ x ++ [s|\>|]) regex x = makeRegexOpts compIgnoreCase execBlank ([s|\<|] ++ x ++ [s|\>|])
os_release :: Path Abs
os_release = [abs|/etc/os-release|]
lsb_release :: Path Abs
lsb_release = [abs|/etc/lsb-release|]
lsb_release_cmd :: Path Rel lsb_release_cmd :: Path Rel
lsb_release_cmd = [rel|lsb-release|] lsb_release_cmd = [rel|lsb-release|]
redhat_release :: Path Abs redhat_release :: Path Abs
@@ -149,9 +157,9 @@ getLinuxDistro = do
try_os_release :: IO (Text, Maybe Text) try_os_release :: IO (Text, Maybe Text)
try_os_release = do try_os_release = do
(Just name) <- getAssignmentValueFor os_release "NAME" Just (OsRelease { name = name, version_id = version_id }) <-
ver <- getAssignmentValueFor os_release "VERSION_ID" fmap osRelease <$> parseOsRelease
pure (T.pack name, fmap T.pack ver) pure (T.pack name, fmap T.pack version_id)
try_lsb_release_cmd :: IO (Text, Maybe Text) try_lsb_release_cmd :: IO (Text, Maybe Text)
try_lsb_release_cmd = do try_lsb_release_cmd = do
@@ -160,12 +168,6 @@ getLinuxDistro = do
ver <- fmap _stdOut $ executeOut lsb_release_cmd ["-sr"] Nothing ver <- fmap _stdOut $ executeOut lsb_release_cmd ["-sr"] Nothing
pure (decUTF8Safe name, Just $ decUTF8Safe ver) pure (decUTF8Safe name, Just $ decUTF8Safe ver)
try_lsb_release :: IO (Text, Maybe Text)
try_lsb_release = do
(Just name) <- getAssignmentValueFor lsb_release "DISTRIB_ID"
ver <- getAssignmentValueFor lsb_release "DISTRIB_RELEASE"
pure (T.pack name, fmap T.pack ver)
try_redhat_release :: IO (Text, Maybe Text) try_redhat_release :: IO (Text, Maybe Text)
try_redhat_release = do try_redhat_release = do
t <- fmap decUTF8Safe' $ readFile redhat_release t <- fmap decUTF8Safe' $ readFile redhat_release

View File

@@ -1,5 +1,14 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-|
Module : GHCup.Requirements
Description : Requirements utilities
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Requirements where module GHCup.Requirements where
import GHCup.Types import GHCup.Types

View File

@@ -2,6 +2,15 @@
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-|
Module : GHCup.Types
Description : GHCup types
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Types where module GHCup.Types where
import Data.Map.Strict ( Map ) import Data.Map.Strict ( Map )
@@ -83,6 +92,7 @@ data VersionInfo = VersionInfo
-- | A tag. These are currently attached to a version of a tool. -- | A tag. These are currently attached to a version of a tool.
data Tag = Latest data Tag = Latest
| Recommended | Recommended
| Prerelease
| Base PVP | Base PVP
| UnknownTag String -- ^ used for upwardscompat | UnknownTag String -- ^ used for upwardscompat
deriving (Ord, Eq, Show) -- FIXME: manual JSON instance deriving (Ord, Eq, Show) -- FIXME: manual JSON instance
@@ -90,6 +100,12 @@ data Tag = Latest
data Architecture = A_64 data Architecture = A_64
| A_32 | A_32
| A_PowerPC
| A_PowerPC64
| A_Sparc
| A_Sparc64
| A_ARM
| A_ARM64
deriving (Eq, GHC.Generic, Ord, Show) deriving (Eq, GHC.Generic, Ord, Show)
@@ -121,7 +137,7 @@ data LinuxDistro = Debian
-- to download, extract and install a tool. -- to download, extract and install a tool.
data DownloadInfo = DownloadInfo data DownloadInfo = DownloadInfo
{ _dlUri :: URI { _dlUri :: URI
, _dlSubdir :: Maybe (Path Rel) , _dlSubdir :: Maybe TarDir
, _dlHash :: Text , _dlHash :: Text
} }
deriving (Eq, Show) deriving (Eq, Show)
@@ -134,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. -- | Where to fetch GHCupDownloads from.
data URLSource = GHCupURL data URLSource = GHCupURL
| OwnSource URI | OwnSource URI
@@ -142,13 +164,25 @@ data URLSource = GHCupURL
data Settings = Settings data Settings = Settings
{ cache :: Bool { -- set by user
cache :: Bool
, noVerify :: Bool , noVerify :: Bool
, keepDirs :: KeepDirs , keepDirs :: KeepDirs
, downloader :: Downloader , downloader :: Downloader
, verbose :: Bool
-- set on app start
, dirs :: Dirs
} }
deriving Show deriving Show
data Dirs = Dirs
{ baseDir :: Path Abs
, binDir :: Path Abs
, cacheDir :: Path Abs
, logsDir :: Path Abs
}
deriving Show
data KeepDirs = Always data KeepDirs = Always
| Errors | Errors

View File

@@ -10,11 +10,21 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-|
Module : GHCup.Types.JSON
Description : GHCup JSON types/instances
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Types.JSON where module GHCup.Types.JSON where
import GHCup.Types import GHCup.Types
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import Control.Applicative ( (<|>) )
import Data.Aeson import Data.Aeson
import Data.Aeson.TH import Data.Aeson.TH
import Data.Aeson.Types import Data.Aeson.Types
@@ -44,6 +54,7 @@ deriveJSON defaultOptions { fieldLabelModifier = removeLensFieldLabel } ''Requir
instance ToJSON Tag where instance ToJSON Tag where
toJSON Latest = String "Latest" toJSON Latest = String "Latest"
toJSON Recommended = String "Recommended" toJSON Recommended = String "Recommended"
toJSON Prerelease = String "Prerelease"
toJSON (Base pvp'') = String ("base-" <> prettyPVP pvp'') toJSON (Base pvp'') = String ("base-" <> prettyPVP pvp'')
toJSON (UnknownTag x ) = String (T.pack x) toJSON (UnknownTag x ) = String (T.pack x)
@@ -51,6 +62,7 @@ instance FromJSON Tag where
parseJSON = withText "Tag" $ \t -> case T.unpack t of parseJSON = withText "Tag" $ \t -> case T.unpack t of
"Latest" -> pure Latest "Latest" -> pure Latest
"Recommended" -> pure Recommended "Recommended" -> pure Recommended
"Prerelease" -> pure Prerelease
('b' : 'a' : 's' : 'e' : '-' : ver') -> case pvp (T.pack ver') of ('b' : 'a' : 's' : 'e' : '-' : ver') -> case pvp (T.pack ver') of
Right x -> pure $ Base x Right x -> pure $ Base x
Left e -> fail . show $ e Left e -> fail . show $ e
@@ -182,3 +194,18 @@ instance FromJSON (Path Rel) where
case parseRel d of case parseRel d of
Right x -> pure x Right x -> pure x
Left e -> fail $ "Failure in HPath Rel (FromJSON)" <> show e Left e -> fail $ "Failure in HPath Rel (FromJSON)" <> show e
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

View File

@@ -1,5 +1,14 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-|
Module : GHCup.Types.Optics
Description : GHCup optics
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Types.Optics where module GHCup.Types.Optics where
import GHCup.Types import GHCup.Types

View File

@@ -6,7 +6,18 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-|
Module : GHCup.Utils
Description : GHCup domain specific utilities
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
This module contains GHCup helpers specific to
installation and introspection of files/versions etc.
-}
module GHCup.Utils module GHCup.Utils
( module GHCup.Utils.Dirs ( module GHCup.Utils.Dirs
, module GHCup.Utils , module GHCup.Utils
@@ -24,6 +35,9 @@ import GHCup.Utils.MegaParsec
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import GHCup.Utils.String.QQ import GHCup.Utils.String.QQ
#if !defined(TAR)
import Codec.Archive
#endif
import Control.Applicative import Control.Applicative
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad import Control.Monad
@@ -34,7 +48,9 @@ import Control.Monad.Logger
import Control.Monad.Reader import Control.Monad.Reader
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
import Data.Either import Data.Either
import Data.Foldable
import Data.List import Data.List
import Data.List.Split
import Data.Maybe import Data.Maybe
import Data.String.Interpolate import Data.String.Interpolate
import Data.Text ( Text ) import Data.Text ( Text )
@@ -42,7 +58,7 @@ import Data.Versions
import Data.Word8 import Data.Word8
import GHC.IO.Exception import GHC.IO.Exception
import HPath import HPath
import HPath.IO import HPath.IO hiding ( hideError )
import Haskus.Utils.Variant.Excepts import Haskus.Utils.Variant.Excepts
import Optics import Optics
import Prelude hiding ( abs import Prelude hiding ( abs
@@ -58,12 +74,18 @@ import System.Posix.Files.ByteString ( readSymbolicLink )
import Text.Regex.Posix import Text.Regex.Posix
import URI.ByteString import URI.ByteString
#if defined(TAR)
import qualified Codec.Archive.Tar as Tar import qualified Codec.Archive.Tar as Tar
#endif
import qualified Codec.Compression.BZip as BZip import qualified Codec.Compression.BZip as BZip
import qualified Codec.Compression.GZip as GZip import qualified Codec.Compression.GZip as GZip
import qualified Codec.Compression.Lzma as Lzma import qualified Codec.Compression.Lzma as Lzma
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
#if !defined(TAR)
import qualified Data.Text as T
#endif
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified Text.Megaparsec as MP import qualified Text.Megaparsec as MP
@@ -77,20 +99,24 @@ import qualified Text.Megaparsec as MP
-- | The symlink destination of a ghc tool. -- | The symlink destination of a ghc tool.
ghcLinkDestination :: ByteString -- ^ the tool, such as 'ghc', 'haddock' etc. ghcLinkDestination :: (MonadReader Settings m, MonadThrow m, MonadIO m)
=> ByteString -- ^ the tool, such as 'ghc', 'haddock' etc.
-> GHCTargetVersion -> GHCTargetVersion
-> ByteString -> m ByteString
ghcLinkDestination tool ver = ghcLinkDestination tool ver = do
"../ghc/" <> E.encodeUtf8 (prettyTVer ver) <> "/bin/" <> tool Settings {dirs = Dirs {..}} <- ask
t <- parseRel tool
ghcd <- ghcupGHCDir ver
pure (relativeSymlink binDir (ghcd </> [rel|bin|] </> t))
-- e.g. ghc-8.6.5 -- | 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 rmMinorSymlinks GHCTargetVersion {..} = do
bindir <- liftIO $ ghcupBinDir Settings {dirs = Dirs {..}} <- ask
files <- liftIO $ findFiles' files <- liftIO $ findFiles'
bindir binDir
( maybe mempty (\x -> MP.chunk (x <> "-")) _tvTarget ( maybe mempty (\x -> MP.chunk (x <> "-")) _tvTarget
*> parseUntil1 (MP.chunk $ prettyVer _tvVersion) *> parseUntil1 (MP.chunk $ prettyVer _tvVersion)
*> (MP.chunk $ prettyVer _tvVersion) *> (MP.chunk $ prettyVer _tvVersion)
@@ -98,42 +124,41 @@ rmMinorSymlinks GHCTargetVersion {..} = do
) )
forM_ files $ \f -> do forM_ files $ \f -> do
let fullF = (bindir </> f) let fullF = (binDir </> f)
$(logDebug) [i|rm -f #{toFilePath fullF}|] $(logDebug) [i|rm -f #{toFilePath fullF}|]
liftIO $ hideError doesNotExistErrorType $ deleteFile fullF liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
-- Removes the set ghc version for the given target, if any. -- | 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 => Maybe Text -- ^ target
-> Excepts '[NotInstalled] m () -> Excepts '[NotInstalled] m ()
rmPlain target = do rmPlain target = do
mtv <- ghcSet target Settings {dirs = Dirs {..}} <- lift ask
mtv <- lift $ ghcSet target
forM_ mtv $ \tv -> do forM_ mtv $ \tv -> do
files <- liftE $ ghcToolFiles tv files <- liftE $ ghcToolFiles tv
bindir <- liftIO $ ghcupBinDir
forM_ files $ \f -> do forM_ files $ \f -> do
let fullF = (bindir </> f) let fullF = (binDir </> f)
lift $ $(logDebug) [i|rm -f #{toFilePath fullF}|] lift $ $(logDebug) [i|rm -f #{toFilePath fullF}|]
liftIO $ hideError doesNotExistErrorType $ deleteFile fullF liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
-- old ghcup -- old ghcup
let hdc_file = (bindir </> [rel|haddock-ghc|]) let hdc_file = (binDir </> [rel|haddock-ghc|])
lift $ $(logDebug) [i|rm -f #{toFilePath hdc_file}|] lift $ $(logDebug) [i|rm -f #{toFilePath hdc_file}|]
liftIO $ hideError doesNotExistErrorType $ deleteFile hdc_file liftIO $ hideError doesNotExistErrorType $ deleteFile hdc_file
-- e.g. ghc-8.6 -- | 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 => GHCTargetVersion
-> m () -> m ()
rmMajorSymlinks GHCTargetVersion {..} = do rmMajorSymlinks GHCTargetVersion {..} = do
Settings {dirs = Dirs {..}} <- ask
(mj, mi) <- getMajorMinorV _tvVersion (mj, mi) <- getMajorMinorV _tvVersion
let v' = intToText mj <> "." <> intToText mi let v' = intToText mj <> "." <> intToText mi
bindir <- liftIO ghcupBinDir
files <- liftIO $ findFiles' files <- liftIO $ findFiles'
bindir binDir
( maybe mempty (\x -> MP.chunk (x <> "-")) _tvTarget ( maybe mempty (\x -> MP.chunk (x <> "-")) _tvTarget
*> parseUntil1 (MP.chunk v') *> parseUntil1 (MP.chunk v')
*> MP.chunk v' *> MP.chunk v'
@@ -141,7 +166,7 @@ rmMajorSymlinks GHCTargetVersion {..} = do
) )
forM_ files $ \f -> do forM_ files $ \f -> do
let fullF = (bindir </> f) let fullF = (binDir </> f)
$(logDebug) [i|rm -f #{toFilePath fullF}|] $(logDebug) [i|rm -f #{toFilePath fullF}|]
liftIO $ hideError doesNotExistErrorType $ deleteFile fullF liftIO $ hideError doesNotExistErrorType $ deleteFile fullF
@@ -153,98 +178,128 @@ rmMajorSymlinks GHCTargetVersion {..} = do
----------------------------------- -----------------------------------
ghcInstalled :: GHCTargetVersion -> IO Bool -- | Whethe the given GHC versin is installed.
ghcInstalled :: (MonadIO m, MonadReader Settings m, MonadThrow m) => GHCTargetVersion -> m Bool
ghcInstalled ver = do ghcInstalled ver = do
ghcdir <- ghcupGHCDir ver ghcdir <- ghcupGHCDir ver
doesDirectoryExist ghcdir liftIO $ doesDirectoryExist ghcdir
ghcSrcInstalled :: GHCTargetVersion -> IO Bool -- | Whether the given GHC version is installed from source.
ghcSrcInstalled :: (MonadIO m, MonadReader Settings m, MonadThrow m) => GHCTargetVersion -> m Bool
ghcSrcInstalled ver = do ghcSrcInstalled ver = do
ghcdir <- ghcupGHCDir ver ghcdir <- ghcupGHCDir ver
doesFileExist (ghcdir </> ghcUpSrcBuiltFile) liftIO $ doesFileExist (ghcdir </> ghcUpSrcBuiltFile)
ghcSet :: (MonadThrow m, MonadIO m) -- | Whether the given GHC version is set as the current.
ghcSet :: (MonadReader Settings m, MonadThrow m, MonadIO m)
=> Maybe Text -- ^ the target of the GHC version, if any => Maybe Text -- ^ the target of the GHC version, if any
-- (e.g. armv7-unknown-linux-gnueabihf) -- (e.g. armv7-unknown-linux-gnueabihf)
-> m (Maybe GHCTargetVersion) -> m (Maybe GHCTargetVersion)
ghcSet mtarget = do ghcSet mtarget = do
Settings {dirs = Dirs {..}} <- ask
ghc <- parseRel $ E.encodeUtf8 (maybe "ghc" (<> "-ghc") mtarget) ghc <- parseRel $ E.encodeUtf8 (maybe "ghc" (<> "-ghc") mtarget)
ghcBin <- (</> ghc) <$> liftIO ghcupBinDir let ghcBin = binDir </> ghc
-- link destination is of the form ../ghc/<ver>/bin/ghc -- link destination is of the form ../ghc/<ver>/bin/ghc
-- for old ghcup, it is ../ghc/<ver>/bin/ghc-<ver> -- for old ghcup, it is ../ghc/<ver>/bin/ghc-<ver>
liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ do liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ do
link <- readSymbolicLink $ toFilePath ghcBin link <- readSymbolicLink $ toFilePath ghcBin
Just <$> ghcLinkVersion link Just <$> ghcLinkVersion link
ghcLinkVersion :: MonadThrow m => ByteString -> m GHCTargetVersion
ghcLinkVersion bs = do
t <- throwEither $ E.decodeUtf8' bs
throwEither $ MP.parse parser "ghcLinkVersion" t
where where
ghcLinkVersion :: MonadThrow m => ByteString -> m GHCTargetVersion parser =
ghcLinkVersion bs = do (do
t <- throwEither $ E.decodeUtf8' bs _ <- parseUntil1 (MP.chunk "/ghc/")
throwEither $ MP.parse parser "" t _ <- MP.chunk "/ghc/"
where r <- parseUntil1 (MP.chunk "/")
parser = rest <- MP.getInput
MP.chunk "../ghc/" MP.setInput r
*> (do x <- ghcTargetVerP
r <- parseUntil1 (MP.chunk "/") MP.setInput rest
rest <- MP.getInput pure x
MP.setInput r )
x <- ghcTargetVerP <* MP.chunk "/"
MP.setInput rest <* MP.takeRest
pure x <* MP.eof
)
<* MP.chunk "/"
<* MP.takeRest
<* MP.eof
-- | Get all installed GHCs by reading ~/.ghcup/ghc/<dir>. -- | Get all installed GHCs by reading ~/.ghcup/ghc/<dir>.
-- If a dir cannot be parsed, returns left. -- 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 getInstalledGHCs = do
ghcdir <- liftIO $ ghcupGHCBaseDir ghcdir <- ghcupGHCBaseDir
fs <- liftIO $ hideErrorDef [NoSuchThing] [] $ getDirsFiles' ghcdir fs <- liftIO $ hideErrorDef [NoSuchThing] [] $ getDirsFiles' ghcdir
forM fs $ \f -> case parseGHCupGHCDir f of forM fs $ \f -> case parseGHCupGHCDir f of
Right r -> pure $ Right r Right r -> pure $ Right r
Left _ -> pure $ Left f Left _ -> pure $ Left f
getInstalledCabals :: IO [Either (Path Rel) Version] -- | Get all installed cabals, by matching on @~\/.ghcup\/bin/cabal-*@.
getInstalledCabals :: (MonadReader Settings m, MonadIO m, MonadCatch m)
=> m [Either (Path Rel) Version]
getInstalledCabals = do getInstalledCabals = do
bindir <- liftIO $ ghcupBinDir Settings {dirs = Dirs {..}} <- ask
bins <- liftIO $ handleIO (\_ -> pure []) $ findFiles bins <- liftIO $ handleIO (\_ -> pure []) $ findFiles
bindir binDir
(makeRegexOpts compExtended execBlank ([s|^cabal-.*$|] :: ByteString)) (makeRegexOpts compExtended execBlank ([s|^cabal-.*$|] :: ByteString))
vs <- forM bins $ \f -> case fmap version (fmap decUTF8Safe . B.stripPrefix "cabal-" . toFilePath $ f) of vs <- forM bins $ \f -> case fmap version (fmap decUTF8Safe . B.stripPrefix "cabal-" . toFilePath $ f) of
Just (Right r) -> pure $ Right r Just (Right r) -> pure $ Right r
Just (Left _) -> pure $ Left f Just (Left _) -> pure $ Left f
Nothing -> pure $ Left f Nothing -> pure $ Left f
cs <- cabalSet -- for legacy cabal cs <- cabalSet -- for legacy cabal
pure $ maybe vs (\x -> Right x:vs) cs pure $ maybe vs (\x -> nub $ Right x:vs) cs
cabalInstalled :: Version -> IO Bool -- | Whether the given cabal version is installed.
cabalInstalled :: (MonadIO m, MonadReader Settings m, MonadCatch m) => Version -> m Bool
cabalInstalled ver = do cabalInstalled ver = do
vers <- fmap rights $ getInstalledCabals vers <- fmap rights $ getInstalledCabals
pure $ elem ver $ vers pure $ elem ver $ vers
cabalSet :: (MonadIO m, MonadThrow m) => m (Maybe Version) -- Return the currently set cabal version, if any.
cabalSet :: (MonadReader Settings m, MonadIO m, MonadThrow m, MonadCatch m) => m (Maybe Version)
cabalSet = do cabalSet = do
cabalbin <- (</> [rel|cabal|]) <$> liftIO ghcupBinDir Settings {dirs = Dirs {..}} <- ask
mc <- liftIO $ handleIO (\_ -> pure Nothing) $ fmap Just $ executeOut let cabalbin = binDir </> [rel|cabal|]
cabalbin b <- handleIO (\_ -> pure False) $ fmap (== SymbolicLink) $ liftIO $ getFileType cabalbin
["--numeric-version"] if
Nothing | b -> do
fmap join $ forM mc $ \c -> if liftIO $ handleIO' NoSuchThing (\_ -> pure $ Nothing) $ do
| not (B.null (_stdOut c)) broken <- isBrokenSymlink cabalbin
, _exitCode c == ExitSuccess -> do if broken
let reportedVer = fst . B.spanEnd (== _lf) . _stdOut $ c then pure Nothing
case version $ decUTF8Safe reportedVer of else do
Left e -> throwM e link <- readSymbolicLink $ toFilePath cabalbin
Right r -> pure $ Just r Just <$> linkVersion link
| otherwise -> pure Nothing | 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'
@@ -253,6 +308,7 @@ cabalSet = do
----------------------------------------- -----------------------------------------
-- | Extract (major, minor) from any version.
getMajorMinorV :: MonadThrow m => Version -> m (Int, Int) getMajorMinorV :: MonadThrow m => Version -> m (Int, Int)
getMajorMinorV Version {..} = case _vChunks of getMajorMinorV Version {..} = case _vChunks of
([Digits x] : [Digits y] : _) -> pure (fromIntegral x, fromIntegral y) ([Digits x] : [Digits y] : _) -> pure (fromIntegral x, fromIntegral y)
@@ -267,7 +323,7 @@ matchMajor v' major' minor' = case getMajorMinorV v' of
-- | Get the latest installed full GHC version that satisfies X.Y. -- | Get the latest installed full GHC version that satisfies X.Y.
-- This reads `ghcupGHCBaseDir`. -- This reads `ghcupGHCBaseDir`.
getGHCForMajor :: (MonadIO m, MonadThrow m) getGHCForMajor :: (MonadReader Settings m, MonadIO m, MonadThrow m)
=> Int -- ^ major version component => Int -- ^ major version component
-> Int -- ^ minor version component -> Int -- ^ minor version component
-> Maybe Text -- ^ the target triple -> Maybe Text -- ^ the target triple
@@ -310,28 +366,67 @@ getLatestGHCFor major' minor' dls = do
unpackToDir :: (MonadLogger m, MonadIO m, MonadThrow m) unpackToDir :: (MonadLogger m, MonadIO m, MonadThrow m)
=> Path Abs -- ^ destination dir => Path Abs -- ^ destination dir
-> Path Abs -- ^ archive path -> Path Abs -- ^ archive path
-> Excepts '[UnknownArchive] m () -> Excepts '[UnknownArchive
#if !defined(TAR)
, ArchiveResult
#endif
] m ()
unpackToDir dest av = do unpackToDir dest av = do
fp <- (decUTF8Safe . toFilePath) <$> basename av fp <- (decUTF8Safe . toFilePath) <$> basename av
let dfp = decUTF8Safe . toFilePath $ dest let dfp = decUTF8Safe . toFilePath $ dest
lift $ $(logInfo) [i|Unpacking: #{fp} to #{dfp}|] lift $ $(logInfo) [i|Unpacking: #{fp} to #{dfp}|]
fn <- toFilePath <$> basename av fn <- toFilePath <$> basename av
let untar = Tar.unpack (toFilePath dest) . Tar.read
#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)
rf :: MonadIO m => Path Abs -> Excepts '[ArchiveResult] m BL.ByteString
rf = liftIO . readFile
#endif
-- extract, depending on file extension -- extract, depending on file extension
if if
| ".tar.gz" `B.isSuffixOf` fn -> liftIO | ".tar.gz" `B.isSuffixOf` fn -> liftE
(untar . GZip.decompress =<< readFile av) (untar . GZip.decompress =<< rf av)
| ".tar.xz" `B.isSuffixOf` fn -> do | ".tar.xz" `B.isSuffixOf` fn -> do
filecontents <- liftIO $ readFile av filecontents <- liftE $ rf av
let decompressed = Lzma.decompress filecontents let decompressed = Lzma.decompress filecontents
liftIO $ untar decompressed liftE $ untar decompressed
| ".tar.bz2" `B.isSuffixOf` fn -> liftIO | ".tar.bz2" `B.isSuffixOf` fn ->
(untar . BZip.decompress =<< readFile av) liftE (untar . BZip.decompress =<< rf av)
| ".tar" `B.isSuffixOf` fn -> liftIO (untar =<< readFile av) | ".tar" `B.isSuffixOf` fn -> liftE (untar =<< rf av)
| otherwise -> throwE $ UnknownArchive fn | 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
------------ ------------
@@ -388,16 +483,17 @@ urlBaseName :: MonadThrow m
urlBaseName = parseRel . snd . B.breakEnd (== _slash) . urlDecode False urlBaseName = parseRel . snd . B.breakEnd (== _slash) . urlDecode False
-- Get tool files from '~/.ghcup/bin/ghc/<ver>/bin/*' -- | Get tool files from @~\/.ghcup\/bin\/ghc\/\<ver\>\/bin\/\*@
-- while ignoring *-<ver> symlinks and accounting for cross triple prefix. -- while ignoring @*-\<ver\>@ symlinks and accounting for cross triple prefix.
-- --
-- Returns unversioned relative files, e.g.: -- Returns unversioned relative files, e.g.:
-- ["hsc2hs","haddock","hpc","runhaskell","ghc","ghc-pkg","ghci","runghc","hp2ps"] --
ghcToolFiles :: (MonadThrow m, MonadFail m, MonadIO m) -- - @["hsc2hs","haddock","hpc","runhaskell","ghc","ghc-pkg","ghci","runghc","hp2ps"]@
ghcToolFiles :: (MonadReader Settings m, MonadThrow m, MonadFail m, MonadIO m)
=> GHCTargetVersion => GHCTargetVersion
-> Excepts '[NotInstalled] m [Path Rel] -> Excepts '[NotInstalled] m [Path Rel]
ghcToolFiles ver = do ghcToolFiles ver = do
ghcdir <- liftIO $ ghcupGHCDir ver ghcdir <- lift $ ghcupGHCDir ver
let bindir = ghcdir </> [rel|bin|] let bindir = ghcdir </> [rel|bin|]
-- fail if ghc is not installed -- fail if ghc is not installed
@@ -417,26 +513,42 @@ ghcToolFiles ver = do
([s|^([a-zA-Z0-9_-]*[a-zA-Z0-9_]-)?ghc$|] :: ByteString) ([s|^([a-zA-Z0-9_-]*[a-zA-Z0-9_]-)?ghc$|] :: ByteString)
) )
(Just symver) <- let ghcbinPath = bindir </> ghcbin
(B.stripPrefix (toFilePath ghcbin <> "-") . takeFileName) ghcIsHadrian <- liftIO $ isHadrian ghcbinPath
<$> (liftIO $ readSymbolicLink $ toFilePath (bindir </> ghcbin)) onlyUnversioned <- if ghcIsHadrian
when (B.null symver) then pure id
(throwIO $ userError $ "Fatal: ghc symlink target is broken") else do
(Just symver) <-
(B.stripPrefix (toFilePath ghcbin <> "-") . takeFileName)
<$> (liftIO $ readSymbolicLink $ toFilePath ghcbinPath)
when (B.null symver)
(throwIO $ userError $ "Fatal: ghc symlink target is broken")
pure $ filter (\x -> not $ symver `B.isSuffixOf` toFilePath x)
pure . filter (\x -> not $ symver `B.isSuffixOf` toFilePath x) $ files pure $ onlyUnversioned files
where
-- GHC is moving some builds to Hadrian for bindists,
-- which doesn't create versioned binaries.
-- https://gitlab.haskell.org/haskell/ghcup-hs/issues/31
isHadrian :: Path Abs -- ^ ghcbin path
-> IO Bool
isHadrian = fmap (/= SymbolicLink) . getFileType
-- | This file, when residing in ~/.ghcup/ghc/<ver>/ signals that -- | This file, when residing in @~\/.ghcup\/ghc\/\<ver\>\/@ signals that
-- this GHC was built from source. It contains the build config. -- this GHC was built from source. It contains the build config.
ghcUpSrcBuiltFile :: Path Rel ghcUpSrcBuiltFile :: Path Rel
ghcUpSrcBuiltFile = [rel|.ghcup_src_built|] ghcUpSrcBuiltFile = [rel|.ghcup_src_built|]
-- | Calls gmake if it exists in PATH, otherwise make. -- | Calls gmake if it exists in PATH, otherwise make.
make :: [ByteString] -> Maybe (Path Abs) -> IO (Either ProcessError ()) make :: (MonadThrow m, MonadIO m, MonadReader Settings m)
=> [ByteString]
-> Maybe (Path Abs)
-> m (Either ProcessError ())
make args workdir = do make args workdir = do
spaths <- catMaybes . fmap parseAbs <$> getSearchPath spaths <- catMaybes . fmap parseAbs <$> (liftIO getSearchPath)
has_gmake <- isJust <$> searchPath spaths [rel|gmake|] has_gmake <- isJust <$> (liftIO $ searchPath spaths [rel|gmake|])
let mymake = if has_gmake then "gmake" else "make" let mymake = if has_gmake then "gmake" else "make"
execLogged mymake True args [rel|ghc-make|] workdir Nothing execLogged mymake True args [rel|ghc-make|] workdir Nothing
@@ -461,6 +573,7 @@ applyPatches pdir ddir = do
!? PatchFailed !? PatchFailed
-- | https://gitlab.haskell.org/ghc/ghc/-/issues/17353
darwinNotarization :: Platform -> Path Abs -> IO (Either ProcessError ()) darwinNotarization :: Platform -> Path Abs -> IO (Either ProcessError ())
darwinNotarization Darwin path = exec darwinNotarization Darwin path = exec
"xattr" "xattr"
@@ -489,24 +602,18 @@ runBuildAction :: (Show (V e), MonadReader Settings m, MonadIO m, MonadMask m)
-> Excepts '[BuildFailed] m a -> Excepts '[BuildFailed] m a
runBuildAction bdir instdir action = do runBuildAction bdir instdir action = do
Settings {..} <- lift ask Settings {..} <- lift ask
v <- flip let exAction = do
onException
(do
forM_ instdir $ \dir -> forM_ instdir $ \dir ->
liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive dir liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive dir
when (keepDirs == Never) when (keepDirs == Never)
$ liftIO $ liftIO
$ hideError doesNotExistErrorType $ hideError doesNotExistErrorType
$ deleteDirRecursive bdir $ deleteDirRecursive bdir
) v <-
flip onException exAction
$ catchAllE $ catchAllE
(\es -> do (\es -> do
forM_ instdir $ \dir -> exAction
liftIO $ hideError doesNotExistErrorType $ deleteDirRecursive dir
when (keepDirs == Never)
$ liftIO
$ hideError doesNotExistErrorType
$ deleteDirRecursive bdir
throwE (BuildFailed bdir es) throwE (BuildFailed bdir es)
) )
$ action $ action

View File

@@ -1,69 +0,0 @@
module GHCup.Utils.Bash
( findAssignment
, equalsAssignmentWith
, getRValue
, getAssignmentValueFor
)
where
import Control.Monad
import Data.ByteString.UTF8 ( toString )
import Data.List
import Data.Maybe
import HPath
import HPath.IO
import Language.Bash.Parse
import Language.Bash.Syntax
import Language.Bash.Word
import Prelude hiding ( readFile )
import qualified Data.ByteString.Lazy.UTF8 as UTF8
extractAssignments :: List -> [Assign]
extractAssignments (List stms) = join $ fmap getAssign $ getCommands stms
where
getCommands :: [Statement] -> [Command]
getCommands = join . fmap commands . catMaybes . fmap findPipes
where
findPipes (Statement (Last p@(Pipeline{})) Sequential) = Just p
findPipes _ = Nothing
getAssign :: Command -> [Assign]
getAssign (Command (SimpleCommand ass _) _) = ass
getAssign _ = []
-- | Find an assignment matching the predicate in the given file.
findAssignment :: Path b -> (Assign -> Bool) -> IO (Maybe Assign)
findAssignment p predicate = do
fileContents <- readFile p
-- TODO: this should accept bytestring:
-- https://github.com/knrafto/language-bash/issues/37
case parse (toString . toFilePath $ p) (UTF8.toString fileContents) of
Left e -> fail $ show e
Right l -> pure $ find predicate (extractAssignments $ l)
-- | Check that the assignment is of the form Foo= ignoring the
-- right hand-side.
equalsAssignmentWith :: String -> Assign -> Bool
equalsAssignmentWith n ass = case ass of
(Assign (Parameter name' Nothing) Equals _) -> n == name'
_ -> False
-- | This pretty-prints the right hand of an Equals assignment, removing
-- quotations. No evaluation is performed.
getRValue :: Assign -> Maybe String
getRValue ass = case ass of
(Assign (Parameter _ _) Equals (RValue w)) -> Just $ unquote w
_ -> Nothing
-- | Given a bash assignment such as Foo="Bar" in the given file,
-- will return "Bar" (without quotations).
getAssignmentValueFor :: Path b -> String -> IO (Maybe String)
getAssignmentValueFor p n = do
mass <- findAssignment p (equalsAssignmentWith n)
pure (mass >>= getRValue)

View File

@@ -1,8 +1,27 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
module GHCup.Utils.Dirs where {-|
Module : GHCup.Utils.Dirs
Description : Definition of GHCup directories
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Utils.Dirs
( getDirs
, ghcupGHCBaseDir
, ghcupGHCDir
, parseGHCupGHCDir
, mkGhcupTmpDir
, withGHCupTmpDir
, relativeSymlink
)
where
import GHCup.Types import GHCup.Types
@@ -15,6 +34,7 @@ import Control.Exception.Safe
import Control.Monad import Control.Monad
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.Trans.Resource import Control.Monad.Trans.Resource
import Data.ByteString ( ByteString )
import Data.Maybe import Data.Maybe
import HPath import HPath
import HPath.IO import HPath.IO
@@ -26,6 +46,7 @@ import Prelude hiding ( abs
import System.Posix.Env.ByteString ( getEnv import System.Posix.Env.ByteString ( getEnv
, getEnvDefault , getEnvDefault
) )
import System.Posix.FilePath hiding ( (</>) )
import System.Posix.Temp.ByteString ( mkdtemp ) import System.Posix.Temp.ByteString ( mkdtemp )
import qualified Data.ByteString.UTF8 as UTF8 import qualified Data.ByteString.UTF8 as UTF8
@@ -36,33 +57,117 @@ import qualified Text.Megaparsec as MP
------------------------------
--[ GHCup base directories ]--
------------------------------
-- | ~/.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
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|])
-- | 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 = 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 = 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 = 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|])
getDirs :: IO Dirs
getDirs = do
baseDir <- ghcupBaseDir
binDir <- ghcupBinDir
cacheDir <- ghcupCacheDir
logsDir <- ghcupLogsDir
pure Dirs { .. }
------------------------- -------------------------
--[ GHCup directories ]-- --[ GHCup directories ]--
------------------------- -------------------------
-- | ~/.ghcup by default
ghcupBaseDir :: IO (Path Abs)
ghcupBaseDir = do
bdir <- getEnv "GHCUP_INSTALL_BASE_PREFIX" >>= \case
Just r -> parseAbs r
Nothing -> liftIO getHomeDirectory
pure (bdir </> [rel|.ghcup|])
-- | ~/.ghcup/ghc by default. -- | ~/.ghcup/ghc by default.
ghcupGHCBaseDir :: IO (Path Abs) ghcupGHCBaseDir :: (MonadReader Settings m) => m (Path Abs)
ghcupGHCBaseDir = ghcupBaseDir <&> (</> [rel|ghc|]) ghcupGHCBaseDir = do
Settings {..} <- ask
pure (baseDir dirs </> [rel|ghc|])
-- | Gets '~/.ghcup/ghc/<ghcupGHCDir>'. -- | Gets '~/.ghcup/ghc/<ghcupGHCDir>'.
-- The dir may be of the form -- The dir may be of the form
-- * armv7-unknown-linux-gnueabihf-8.8.3 -- * armv7-unknown-linux-gnueabihf-8.8.3
-- * 8.8.4 -- * 8.8.4
ghcupGHCDir :: GHCTargetVersion -> IO (Path Abs) ghcupGHCDir :: (MonadReader Settings m, MonadThrow m)
=> GHCTargetVersion
-> m (Path Abs)
ghcupGHCDir ver = do ghcupGHCDir ver = do
ghcbasedir <- ghcupGHCBaseDir ghcbasedir <- ghcupGHCBaseDir
verdir <- parseRel $ E.encodeUtf8 (prettyTVer ver) verdir <- parseRel $ E.encodeUtf8 (prettyTVer ver)
pure (ghcbasedir </> verdir) pure (ghcbasedir </> verdir)
@@ -73,16 +178,6 @@ parseGHCupGHCDir (toFilePath -> f) = do
throwEither $ MP.parse ghcTargetVerP "" fp throwEither $ MP.parse ghcTargetVerP "" fp
ghcupBinDir :: IO (Path Abs)
ghcupBinDir = ghcupBaseDir <&> (</> [rel|bin|])
ghcupCacheDir :: IO (Path Abs)
ghcupCacheDir = ghcupBaseDir <&> (</> [rel|cache|])
ghcupLogsDir :: IO (Path Abs)
ghcupLogsDir = ghcupBaseDir <&> (</> [rel|logs|])
mkGhcupTmpDir :: (MonadThrow m, MonadIO m) => m (Path Abs) mkGhcupTmpDir :: (MonadThrow m, MonadIO m) => m (Path Abs)
mkGhcupTmpDir = do mkGhcupTmpDir = do
tmpdir <- liftIO $ getEnvDefault "TMPDIR" "/tmp" tmpdir <- liftIO $ getEnvDefault "TMPDIR" "/tmp"
@@ -94,6 +189,8 @@ withGHCupTmpDir :: (MonadResource m, MonadThrow m, MonadIO m) => m (Path Abs)
withGHCupTmpDir = snd <$> allocate mkGhcupTmpDir deleteDirRecursive withGHCupTmpDir = snd <$> allocate mkGhcupTmpDir deleteDirRecursive
-------------- --------------
--[ Others ]-- --[ Others ]--
-------------- --------------
@@ -107,3 +204,23 @@ getHomeDirectory = do
Nothing -> do Nothing -> do
h <- PU.homeDirectory <$> (PU.getEffectiveUserID >>= PU.getUserEntryForID) h <- PU.homeDirectory <$> (PU.getEffectiveUserID >>= PU.getUserEntryForID)
parseAbs $ UTF8.fromString h -- this is a guess 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))

View File

@@ -1,37 +1,47 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-|
Module : GHCup.Utils.File
Description : File and unix APIs
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
This module handles file and executable handling.
Some of these functions use sophisticated logging.
-}
module GHCup.Utils.File where module GHCup.Utils.File where
import GHCup.Utils.Dirs
import GHCup.Utils.Prelude import GHCup.Utils.Prelude
import GHCup.Types
import Control.Concurrent import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception ( evaluate ) import Control.Exception ( evaluate )
import Control.Exception.Safe import Control.Exception.Safe
import Control.Monad import Control.Monad
import Control.Monad.Reader
import Control.Monad.Trans.State.Strict
import Data.ByteString ( ByteString ) import Data.ByteString ( ByteString )
import Data.ByteString.Unsafe ( unsafeUseAsCStringLen )
import Data.Char
import Data.Foldable import Data.Foldable
import Data.Functor import Data.Functor
import Data.IORef import Data.IORef
import Data.Maybe import Data.Maybe
import Data.Sequence ( Seq, (|>) )
import Data.Text ( Text ) import Data.Text ( Text )
import Data.Void import Data.Void
import GHC.Foreign ( peekCStringLen ) import Data.Word8
import GHC.IO.Encoding ( getLocaleEncoding )
import GHC.IO.Exception import GHC.IO.Exception
import HPath import HPath
import HPath.IO import HPath.IO hiding ( hideError )
import Optics import Optics hiding ((<|), (|>))
import Streamly
import Streamly.External.ByteString
import Streamly.External.ByteString.Lazy
import System.Console.Pretty import System.Console.Pretty
import System.Console.Regions import System.Console.Regions
import System.IO
import System.IO.Error import System.IO.Error
import System.Posix.Directory.ByteString import System.Posix.Directory.ByteString
import System.Posix.FD as FD import System.Posix.FD as FD
@@ -45,31 +55,20 @@ import Text.Regex.Posix
import qualified Control.Exception as EX import qualified Control.Exception as EX
import qualified Data.Sequence as Sq
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified System.Posix.Process.ByteString import qualified System.Posix.Process.ByteString
as SPPB as SPPB
import Streamly.External.Posix.DirStream import Streamly.External.Posix.DirStream
import qualified Streamly.Internal.Memory.ArrayStream
as AS
import qualified Streamly.FileSystem.Handle as FH
import qualified Streamly.Internal.Data.Unfold as SU
import qualified Streamly.Prelude as S import qualified Streamly.Prelude as S
import qualified Text.Megaparsec as MP import qualified Text.Megaparsec as MP
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as L
import qualified "unix-bytestring" System.Posix.IO.ByteString import qualified "unix-bytestring" System.Posix.IO.ByteString
as SPIB as SPIB
-- | Bool signals whether the regions should be cleaned.
data StopThread = StopThread Bool
deriving Show
instance Exception StopThread
data ProcessError = NonZeroExit Int ByteString [ByteString] data ProcessError = NonZeroExit Int ByteString [ByteString]
| PTerminated ByteString [ByteString] | PTerminated ByteString [ByteString]
| PStopped ByteString [ByteString] | PStopped ByteString [ByteString]
@@ -87,25 +86,6 @@ data CapturedProcess = CapturedProcess
makeLenses ''CapturedProcess makeLenses ''CapturedProcess
readFd :: Fd -> IO L.ByteString
readFd fd = do
handle' <- fdToHandle fd
fromChunksIO $ (S.unfold (SU.finallyIO hClose FH.readChunks) handle')
-- | Read the lines of a file into a stream. The stream holds
-- a file handle as a resource and will close it once the stream
-- terminates (either through exception or because it's drained).
readFileLines :: Path b -> IO (SerialT IO ByteString)
readFileLines p = do
stream <- readFileStream p
pure
. (fmap fromArray)
. AS.splitOn (fromIntegral $ ord '\n')
. (fmap toArray)
$ stream
-- | Find the given executable by searching all *absolute* PATH components. -- | Find the given executable by searching all *absolute* PATH components.
-- Relative paths in PATH are ignored. -- Relative paths in PATH are ignored.
-- --
@@ -133,110 +113,155 @@ executeOut path args chdir = captureOutStreams $ do
SPPB.executeFile (toFilePath path) True args Nothing SPPB.executeFile (toFilePath path) True args Nothing
execLogged :: ByteString -- ^ thing to execute execLogged :: (MonadReader Settings m, MonadIO m, MonadThrow m)
=> ByteString -- ^ thing to execute
-> Bool -- ^ whether to search PATH for the thing -> Bool -- ^ whether to search PATH for the thing
-> [ByteString] -- ^ args for the thing -> [ByteString] -- ^ args for the thing
-> Path Rel -- ^ log filename -> Path Rel -- ^ log filename
-> Maybe (Path Abs) -- ^ optionally chdir into this -> Maybe (Path Abs) -- ^ optionally chdir into this
-> Maybe [(ByteString, ByteString)] -- ^ optional environment -> Maybe [(ByteString, ByteString)] -- ^ optional environment
-> IO (Either ProcessError ()) -> m (Either ProcessError ())
execLogged exe spath args lfile chdir env = do execLogged exe spath args lfile chdir env = do
ldir <- ghcupLogsDir Settings {dirs = Dirs {..}, ..} <- ask
logfile <- (ldir </>) <$> parseRel (toFilePath lfile <> ".log") logfile <- (logsDir </>) <$> parseRel (toFilePath lfile <> ".log")
bracket (createFile (toFilePath logfile) newFilePerms) closeFd action liftIO $ bracket (createFile (toFilePath logfile) newFilePerms)
closeFd
(action verbose)
where where
action fd = do action verbose fd = do
actionWithPipes $ \(stdoutRead, stdoutWrite) -> do actionWithPipes $ \(stdoutRead, stdoutWrite) -> do
-- start the thread that logs to stdout in a region -- start the thread that logs to stdout
done <- newEmptyMVar pState <- newEmptyMVar
tid <- done <- newEmptyMVar
forkIO void
$ EX.handle (\(_ :: StopThread) -> pure ()) $ forkIO
$ EX.handle (\(_ :: IOException) -> pure ()) $ EX.handle (\(_ :: IOException) -> pure ())
$ flip finally (putMVar done ()) $ flip EX.finally (putMVar done ())
$ printToRegion fd stdoutRead 6 $ (if verbose
then tee fd stdoutRead
else printToRegion fd stdoutRead 6 pState
)
-- fork our subprocess -- fork the subprocess
pid <- SPPB.forkProcess $ do pid <- SPPB.forkProcess $ do
void $ dupTo stdoutWrite stdOutput void $ dupTo stdoutWrite stdOutput
void $ dupTo stdoutWrite stdError void $ dupTo stdoutWrite stdError
closeFd stdoutWrite
closeFd stdoutRead closeFd stdoutRead
closeFd stdoutWrite
-- execute the action -- execute the action
maybe (pure ()) (changeWorkingDirectory . toFilePath) chdir maybe (pure ()) (changeWorkingDirectory . toFilePath) chdir
SPPB.executeFile exe spath args env void $ SPPB.executeFile exe spath args env
closeFd stdoutWrite closeFd stdoutWrite
-- wait for the subprocess to finish -- wait for the subprocess to finish
e <- SPPB.getProcessStatus True True pid >>= \case e <- toProcessError exe args <$!> SPPB.getProcessStatus True True pid
i@(Just (SPPB.Exited _)) -> pure $ toProcessError exe args i putMVar pState (either (const False) (const True) e)
i -> pure $ toProcessError exe args i
-- make sure the logging thread stops
case e of
Left _ -> EX.throwTo tid (StopThread False)
Right _ -> EX.throwTo tid (StopThread True)
takeMVar done
void $ race (takeMVar done) (threadDelay (1000000 * 3))
closeFd stdoutRead closeFd stdoutRead
pure e pure e
tee :: Fd -> Fd -> IO ()
tee fileFd fdIn = readTilEOF lineAction fdIn
where
lineAction :: ByteString -> IO ()
lineAction bs' = do
void $ SPIB.fdWrite fileFd (bs' <> "\n")
void $ SPIB.fdWrite stdOutput (bs' <> "\n")
-- Reads fdIn and logs the output in a continous scrolling area -- Reads fdIn and logs the output in a continous scrolling area
-- of 'size' terminal lines. Also writes to a log file. -- of 'size' terminal lines. Also writes to a log file.
printToRegion fileFd fdIn size = do printToRegion :: Fd -> Fd -> Int -> MVar Bool -> IO ()
ref <- newIORef ([] :: [ByteString]) printToRegion fileFd fdIn size pState = do
displayConsoleRegions $ do void $ displayConsoleRegions $ do
rs <- sequence . replicate size . openConsoleRegion $ Linear rs <-
flip finally (readTilEOF (lineAction ref rs) fdIn) -- make sure the last few lines don't get cut off liftIO
. fmap Sq.fromList
. sequence
. replicate size
. openConsoleRegion
$ Linear
flip runStateT mempty
$ handle $ handle
(\(StopThread b) -> do (\(ex :: SomeException) -> do
when b (forM_ rs closeConsoleRegion) ps <- liftIO $ takeMVar pState
EX.throw (StopThread b) when (ps == True) (forM_ rs (liftIO . closeConsoleRegion))
throw ex
) )
$ do $ readTilEOF (lineAction rs) fdIn
hideError eofErrorType $ readTilEOF (lineAction ref rs) fdIn
-- wait for explicit stop from the parent to signal what cleanup to run
forever (threadDelay 5000)
where where
-- action to perform line by line -- action to perform line by line
lineAction ref rs bs' = do -- TODO: do this with vty for efficiency
modifyIORef' ref (swapRegs bs') lineAction :: (MonadMask m, MonadIO m)
regs <- readIORef ref => Seq ConsoleRegion
void $ SPIB.fdWrite fileFd (bs' <> "\n") -> ByteString
forM (zip regs rs) $ \(bs, r) -> do -> StateT (Seq ByteString) m ()
setConsoleRegion r $ do lineAction rs = \bs' -> do
w <- consoleWidth void $ liftIO $ SPIB.fdWrite fileFd (bs' <> "\n")
return modify (swapRegs bs')
. T.pack regs <- get
. color Blue liftIO $ forM_ (Sq.zip regs rs) $ \(bs, r) -> setConsoleRegion r $ do
. T.unpack w <- consoleWidth
. decUTF8Safe return
. trim w . T.pack
. (\b -> "[ " <> toFilePath lfile <> " ] " <> b) . color Blue
$ bs . T.unpack
. decUTF8Safe
. trim w
. (\b -> "[ " <> toFilePath lfile <> " ] " <> b)
$ bs
swapRegs bs regs | length regs < size = regs ++ [bs] swapRegs :: a -> Seq a -> Seq a
| otherwise = tail regs ++ [bs] swapRegs bs = \regs -> if
| Sq.length regs < size -> regs |> bs
| otherwise -> Sq.drop 1 regs |> bs
-- trim output line to terminal width -- trim output line to terminal width
trim w bs | BS.length bs > w && w > 5 = BS.take (w - 4) bs <> "..." trim :: Int -> ByteString -> ByteString
| otherwise = bs trim w = \bs -> if
| BS.length bs > w && w > 5 -> BS.take (w - 4) bs <> "..."
| otherwise -> bs
-- read an entire line from the file descriptor (removes the newline char) -- Consecutively read from Fd in 512 chunks until we hit
readLine fd' = do -- newline or EOF.
bs <- SPIB.fdRead fd' 1 readLine :: MonadIO m
if => Fd -- ^ input file descriptor
| bs == "\n" -> pure "" -> ByteString -- ^ rest buffer (read across newline)
| bs == "" -> pure "" -> m (ByteString, ByteString, Bool) -- ^ (full line, rest, eof)
| otherwise -> fmap (bs <>) $ readLine fd' readLine fd = \inBs -> go inBs
where
go inBs = do
-- if buffer is not empty, process it first
mbs <- if BS.length inBs == 0
-- otherwise attempt read
then liftIO
$ handleIO (\e -> if isEOFError e then pure Nothing else ioError e)
$ fmap Just
$ SPIB.fdRead fd 512
else pure $ Just inBs
case mbs of
Nothing -> pure ("", "", True)
Just bs -> do
-- split on newline
let (line, rest) = BS.span (/= _lf) bs
if
| BS.length rest /= 0 -> pure (line, BS.tail rest, False)
-- if rest is empty, then there was no newline, process further
| otherwise -> (\(l, r, b) -> (line <> l, r, b)) <$!> go mempty
readTilEOF action' fd' = do readTilEOF :: MonadIO m => (ByteString -> m a) -> Fd -> m ()
bs <- readLine fd' readTilEOF ~action' fd' = go mempty
void $ action' bs where
readTilEOF action' fd' go bs' = do
(bs, rest, eof) <- readLine fd' bs'
if eof
then liftIO $ ioError (mkIOError eofErrorType "" Nothing Nothing)
else (void $ action' bs) >> go rest
-- | Capture the stdout and stderr of the given action, which -- | Capture the stdout and stderr of the given action, which
@@ -273,13 +298,12 @@ captureOutStreams action = do
done <- newEmptyMVar done <- newEmptyMVar
_ <- _ <-
forkIO forkIO
$ EX.handle (\(_ :: StopThread) -> pure ())
$ EX.handle (\(_ :: IOException) -> pure ()) $ EX.handle (\(_ :: IOException) -> pure ())
$ flip finally (putMVar done ()) $ flip EX.finally (putMVar done ())
$ writeStds parentStdoutRead parentStderrRead refOut refErr $ writeStds parentStdoutRead parentStderrRead refOut refErr
status <- SPPB.getProcessStatus True True pid status <- SPPB.getProcessStatus True True pid
takeMVar done void $ race (takeMVar done) (threadDelay (1000000 * 3))
case status of case status of
-- readFd will take care of closing the fd -- readFd will take care of closing the fd
@@ -299,13 +323,13 @@ captureOutStreams action = do
void void
$ forkIO $ forkIO
$ hideError eofErrorType $ hideError eofErrorType
$ flip finally (putMVar doneOut ()) $ flip EX.finally (putMVar doneOut ())
$ readTilEOF (\x -> modifyIORef' rout (<> x)) pout $ readTilEOF (\x -> modifyIORef' rout (<> x)) pout
doneErr <- newEmptyMVar doneErr <- newEmptyMVar
void void
$ forkIO $ forkIO
$ hideError eofErrorType $ hideError eofErrorType
$ flip finally (putMVar doneErr ()) $ flip EX.finally (putMVar doneErr ())
$ readTilEOF (\x -> modifyIORef' rerr (<> x)) perr $ readTilEOF (\x -> modifyIORef' rerr (<> x)) perr
takeMVar doneOut takeMVar doneOut
takeMVar doneErr takeMVar doneErr
@@ -358,14 +382,6 @@ toProcessError exe args mps = case mps of
Nothing -> Left $ NoSuchPid exe args Nothing -> Left $ NoSuchPid exe args
-- | Convert the String to a ByteString with the current
-- system encoding.
unsafePathToString :: Path b -> IO FilePath
unsafePathToString p = do
enc <- getLocaleEncoding
unsafeUseAsCStringLen (toFilePath p) (peekCStringLen enc)
-- | Search for a file in the search paths. -- | Search for a file in the search paths.
-- --
-- Catches `PermissionDenied` and `NoSuchThing` and returns `Nothing`. -- Catches `PermissionDenied` and `NoSuchThing` and returns `Nothing`.
@@ -409,3 +425,12 @@ findFiles' path parser = do
Right p' -> isJust $ MP.parseMaybe parser p') Right p' -> isJust $ MP.parseMaybe parser p')
$ dirContentsStream dirStream $ dirContentsStream dirStream
pure $ join $ fmap parseRel f 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

View File

@@ -1,10 +1,24 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleContexts #-}
{-|
Module : GHCup.Utils.Logger
Description : logger definition
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
Here we define our main logger.
-}
module GHCup.Utils.Logger where module GHCup.Utils.Logger where
import GHCup.Utils import GHCup.Types
import Control.Monad import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.Logger import Control.Monad.Logger
import HPath import HPath
import HPath.IO import HPath.IO
@@ -50,11 +64,12 @@ myLoggerT LoggerConfig {..} loggingt = runLoggingT loggingt mylogger
rawOutter outr rawOutter outr
initGHCupFileLogging :: Path Rel -> IO (Path Abs) initGHCupFileLogging :: (MonadIO m, MonadReader Settings m) => Path Rel -> m (Path Abs)
initGHCupFileLogging context = do initGHCupFileLogging context = do
logs <- ghcupLogsDir Settings {dirs = Dirs {..}} <- ask
let logfile = logs </> context let logfile = logsDir </> context
createDirIfMissing newDirPerms logs liftIO $ do
hideError doesNotExistErrorType $ deleteFile logfile createDirRecursive newDirPerms logsDir
createRegularFile newFilePerms logfile hideError doesNotExistErrorType $ deleteFile logfile
pure logfile createRegularFile newFilePerms logfile
pure logfile

View File

@@ -1,6 +1,15 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-|
Module : GHCup.Utils.MegaParsec
Description : MegaParsec utilities
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Utils.MegaParsec where module GHCup.Utils.MegaParsec where
import GHCup.Types import GHCup.Types

View File

@@ -8,6 +8,17 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
{-|
Module : GHCup.Utils.Prelude
Description : MegaParsec utilities
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
GHCup specific prelude. Lots of Excepts functionality.
-}
module GHCup.Utils.Prelude where module GHCup.Utils.Prelude where
import Control.Applicative import Control.Applicative
@@ -165,6 +176,11 @@ liftIOException errType ex =
. lift . lift
-- | Uses safe-exceptions.
hideError :: (MonadIO m, MonadCatch m) => IOErrorType -> m () -> m ()
hideError err = handleIO (\e -> if err == ioeGetErrorType e then pure () else liftIO . ioError $ e)
hideErrorDef :: [IOErrorType] -> a -> IO a -> IO a hideErrorDef :: [IOErrorType] -> a -> IO a -> IO a
hideErrorDef errs def = hideErrorDef errs def =
handleIO (\e -> if ioeGetErrorType e `elem` errs then pure def else ioError e) handleIO (\e -> if ioeGetErrorType e `elem` errs then pure def else ioError e)

View File

@@ -1,25 +1,35 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
-- | QuasiQuoter for non-interpolated strings, texts and bytestrings. {-|
-- Module : GHCup.Utils.String.QQ
-- The "s" quoter contains a multi-line string with no interpolation at all, Description : String quasi quoters
-- except that the leading newline is trimmed and carriage returns stripped. Copyright : (c) Audrey Tang <audreyt@audreyt.org> 2019, Julian Ospald <hasufell@posteo.de> 2020
-- License : LGPL-3.0
-- @ Maintainer : hasufell@hasufell.de
-- {-\# LANGUAGE QuasiQuotes #-} Stability : experimental
-- import Data.Text (Text) Portability : POSIX
-- import Data.String.QQ
-- foo :: Text -- "String", "ByteString" etc also works QuasiQuoter for non-interpolated strings, texts and bytestrings.
-- foo = [s|
-- Well here is a The "s" quoter contains a multi-line string with no interpolation at all,
-- multi-line string! except that the leading newline is trimmed and carriage returns stripped.
-- |]
-- @ @
-- {-\# LANGUAGE QuasiQuotes #-}
-- Any instance of the IsString type is permitted. import Data.Text (Text)
-- import Data.String.QQ
-- (For GHC versions 6, write "[$s||]" instead of "[s||]".) foo :: Text -- "String", "ByteString" etc also works
-- foo = [s|
Well here is a
multi-line string!
|]
@
Any instance of the IsString type is permitted.
(For GHC versions 6, write "[$s||]" instead of "[s||]".)
-}
module GHCup.Utils.String.QQ module GHCup.Utils.String.QQ
( s ( s
) )

View File

@@ -7,6 +7,15 @@
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-|
Module : GHCup.Utils.Version.QQ
Description : Version quasi-quoters
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Utils.Version.QQ where module GHCup.Utils.Version.QQ where
import Data.Data import Data.Data
@@ -42,7 +51,6 @@ deriving instance Data VUnit
#if !MIN_VERSION_base(4,13,0) #if !MIN_VERSION_base(4,13,0)
deriving instance Lift (NonEmpty Word) deriving instance Lift (NonEmpty Word)
instance Lift Text
#endif #endif
qq :: (Text -> Q Exp) -> QuasiQuoter qq :: (Text -> Q Exp) -> QuasiQuoter

View File

@@ -1,6 +1,15 @@
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-|
Module : GHCup.Version
Description : Static version information
Copyright : (c) Julian Ospald, 2020
License : LGPL-3.0
Maintainer : hasufell@hasufell.de
Stability : experimental
Portability : POSIX
-}
module GHCup.Version where module GHCup.Version where
import GHCup.Utils.Version.QQ import GHCup.Utils.Version.QQ
@@ -11,12 +20,14 @@ import URI.ByteString.QQ
import qualified Data.Text as T import qualified Data.Text as T
-- | This reflects the API version of the JSON. -- | This reflects the API version of the YAML.
ghcupURL :: URI ghcupURL :: URI
ghcupURL = [uri|https://www.haskell.org/ghcup/data/ghcup-0.0.2.json|] ghcupURL = [uri|https://www.haskell.org/ghcup/data/ghcup-0.0.2.yaml|]
-- | The current ghcup version.
ghcUpVer :: PVP ghcUpVer :: PVP
ghcUpVer = [pver|0.1.5|] ghcUpVer = [pver|0.1.8|]
-- | ghcup version as numeric string.
numericVer :: String numericVer :: String
numericVer = T.unpack . prettyPVP $ ghcUpVer numericVer = T.unpack . prettyPVP $ ghcUpVer

View File

@@ -101,6 +101,7 @@ body#idx p.other-help {
.instructions div.command-button { .instructions div.command-button {
display: flex; display: flex;
align-items: center;
} }
.instructions div.command-button button { .instructions div.command-button button {
@@ -111,7 +112,7 @@ body#idx p.other-help {
border-style: solid; border-style: solid;
border-radius: 3px; border-radius: 3px;
margin-left: 1rem; margin-left: 0.5rem;
margin-right: auto; margin-right: auto;
margin-top: 25px; margin-top: 25px;
margin-bottom: 25px; margin-bottom: 25px;
@@ -134,20 +135,21 @@ hr {
#platform-instructions-linux > div > pre, #platform-instructions-linux > div > pre,
#platform-instructions-mac > div > pre, #platform-instructions-mac > div > pre,
#platform-instructions-freebsd > div > pre, #platform-instructions-freebsd > div > pre,
#platform-instructions-win32 > pre, #platform-instructions-win32 > div > pre,
#platform-instructions-win64 > pre, #platform-instructions-win64 > div > pre,
#platform-instructions-default > div > div > pre, #platform-instructions-default > div > div > pre,
#platform-instructions-unknown > div > div > pre { #platform-instructions-unknown > div > div > pre {
background-color: #515151; background-color: #515151;
color: white; color: white;
margin-left: auto; margin-left: auto;
margin-right: auto;
padding-top: 1rem; padding-top: 1rem;
padding-bottom: 1rem; padding-bottom: 1rem;
padding-right: 1rem; padding-right: 1rem;
text-align: center; text-align: center;
border-radius: 3px; border-radius: 3px;
box-shadow: inset 0px 0px 20px 0px #333333; box-shadow: inset 0px 0px 20px 0px #333333;
font-size: 0.6em;
width: 40rem;
} }
#platform-instructions-win32 a.windows-download, #platform-instructions-win32 a.windows-download,

View File

@@ -46,6 +46,9 @@
<p> <p>
To install Haskell, follow the instructions on To install Haskell, follow the instructions on
<a class="windows-download" href="https://www.haskell.org/platform/#windows">Haskell Platform</a> <a class="windows-download" href="https://www.haskell.org/platform/#windows">Haskell Platform</a>
<p>If you're a Windows Subsystem for Linux user run the following in your terminal, then follow the onscreen instructions to install Haskell.
</p>
<div class="command-button"><pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre><button class="tooltip" onclick="copyToClipboard()"><img src="copy.svg" alt="" /><span class="tooltiptext">Copy to clipboard</span></button></div>
</p> </p>
<p class="other-help">You appear to be running Windows 32-bit. If not, <a class="default-platform-button" href="#">display all supported installers</a>.</p> <p class="other-help">You appear to be running Windows 32-bit. If not, <a class="default-platform-button" href="#">display all supported installers</a>.</p>
</div> </div>
@@ -55,6 +58,9 @@
To install Haskell, follow the instructions on To install Haskell, follow the instructions on
<a class="windows-download" href="https://www.haskell.org/platform/#windows">Haskell Platform</a> <a class="windows-download" href="https://www.haskell.org/platform/#windows">Haskell Platform</a>
</p> </p>
<p>If you're a Windows Subsystem for Linux user run the following in your terminal, then follow the onscreen instructions to install Haskell.
</p>
<div class="command-button"><pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre><button class="tooltip" onclick="copyToClipboard()"><img src="copy.svg" alt="" /><span class="tooltiptext">Copy to clipboard</span></button></div>
<p class="other-help">You appear to be running Windows 64-bit. If not, <a class="default-platform-button" href="#">display all supported installers</a>.</p> <p class="other-help">You appear to be running Windows 64-bit. If not, <a class="default-platform-button" href="#">display all supported installers</a>.</p>
</div> </div>
@@ -77,7 +83,7 @@
<!-- duplicate the default cross-platform instructions --> <!-- duplicate the default cross-platform instructions -->
<div> <div>
<p>If you are running Linux, macOS or FreeBSD,<br/>run the following in your terminal (as a user other than root), then follow the onscreen instructions.</p> <p>If you are running Linux, macOS, FreeBSD or Windows Subsystem for Linux, run the following in your terminal (as a user other than root), then follow the onscreen instructions.</p>
<div class="command-button"><pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre><button class="tooltip" onclick="copyToClipboard()"><img src="copy.svg" alt="" /><span class="tooltiptext">Copy to clipboard</span></button></div> <div class="command-button"><pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre><button class="tooltip" onclick="copyToClipboard()"><img src="copy.svg" alt="" /><span class="tooltiptext">Copy to clipboard</span></button></div>
<p class="other-help">If you don't like curl | sh, see <a href="https://gitlab.haskell.org/haskell/ghcup-hs#manual-install">other installation methods</a>.</p> <p class="other-help">If you don't like curl | sh, see <a href="https://gitlab.haskell.org/haskell/ghcup-hs#manual-install">other installation methods</a>.</p>
</div> </div>
@@ -95,7 +101,7 @@
<div id="platform-instructions-default" class="instructions"> <div id="platform-instructions-default" class="instructions">
<div> <div>
<p>To install Haskell, if you are running Linux, macOS or FreeBSD,<br/>run the following <p>To install Haskell, if you are running Linux, macOS, FreeBSD or Windows Subsystem for Linux, run the following
in your terminal (as a user other than root), then follow the onscreen instructions.</p> in your terminal (as a user other than root), then follow the onscreen instructions.</p>
<div class="command-button"><pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre><button class="tooltip" onclick="copyToClipboard()"><img src="copy.svg" alt="" /><span class="tooltiptext">Copy to clipboard</span></button></div> <div class="command-button"><pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre><button class="tooltip" onclick="copyToClipboard()"><img src="copy.svg" alt="" /><span class="tooltiptext">Copy to clipboard</span></button></div>
<p class="other-help">If you don't like curl | sh, see <a href="https://gitlab.haskell.org/haskell/ghcup-hs#manual-install">other installation methods</a>.</p> <p class="other-help">If you don't like curl | sh, see <a href="https://gitlab.haskell.org/haskell/ghcup-hs#manual-install">other installation methods</a>.</p>
@@ -140,7 +146,7 @@
<div id="platform-instructions-default" class="instructions"> <div id="platform-instructions-default" class="instructions">
<div> <div>
<p>To install Haskell, if you are running Linux, macOS or FreeBSD,<br/>run the following <p>To install Haskell, if you are running Linux, macOS, FreeBSD or Windows Subsystem for Linux, run the following
in your terminal (as a user other than root), then follow the onscreen instructions.</p> in your terminal (as a user other than root), then follow the onscreen instructions.</p>
<pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre> <pre><span class='ghcup-command'>curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh</span></pre>
<p class="other-help">If you don't like curl | sh, see <a href="https://gitlab.haskell.org/haskell/ghcup-hs#manual-install">other installation methods</a>.</p> <p class="other-help">If you don't like curl | sh, see <a href="https://gitlab.haskell.org/haskell/ghcup-hs#manual-install">other installation methods</a>.</p>