diff --git a/.available-versions b/.available-versions new file mode 100644 index 0000000..7299557 --- /dev/null +++ b/.available-versions @@ -0,0 +1,11 @@ +# fmt-version=1 +# tab separated columns +# tool | version | tag | +ghc 8.0.2 +ghc 8.2.2 +ghc 8.4.3 +ghc 8.4.4 recommended +ghc 8.6.1 latest,bad +cabal-install 2.2.0.0 +cabal-install 2.4.0.0 latest,recommended + diff --git a/.download-urls b/.download-urls new file mode 100644 index 0000000..925e7cd --- /dev/null +++ b/.download-urls @@ -0,0 +1,35 @@ +# fmt-version=1 +# tool | version | arch | supported-distros for given url | url | + +ghc 8.0.2 x86_64 debian=7 https://downloads.haskell.org/~ghc/8.0.2/ghc-8.0.2-x86_64-deb7-linux.tar.xz +ghc 8.0.2 i386 debian=7 https://downloads.haskell.org/~ghc/8.0.2/ghc-8.0.2-i386-deb7-linux.tar.xz +ghc 8.0.2 x86_64 debian=8,debian,ubuntu,unknown https://downloads.haskell.org/~ghc/8.0.2/ghc-8.0.2-x86_64-deb8-linux.tar.xz +ghc 8.0.2 i386 debian=8,debian,ubuntu,unknown https://downloads.haskell.org/~ghc/8.0.2/ghc-8.0.2-i386-deb8-linux.tar.xz + +ghc 8.2.2 x86_64 debian=7 https://downloads.haskell.org/~ghc/8.2.2/ghc-8.2.2-x86_64-deb7-linux.tar.xz +ghc 8.2.2 i386 debian=7 https://downloads.haskell.org/~ghc/8.2.2/ghc-8.2.2-i386-deb7-linux.tar.xz +ghc 8.2.2 x86_64 debian=8,debian,ubuntu https://downloads.haskell.org/~ghc/8.2.2/ghc-8.2.2-x86_64-deb8-linux.tar.xz +ghc 8.2.2 i386 debian=8,debian,ubuntu,unknown https://downloads.haskell.org/~ghc/8.2.2/ghc-8.2.2-i386-deb8-linux.tar.xz +ghc 8.2.2 x86_64 unknown https://downloads.haskell.org/~ghc/8.2.2/ghc-8.2.2-x86_64-unknown-linux.tar.xz + +ghc 8.4.3 i386 debian=8,debian,ubuntu,unknown https://downloads.haskell.org/~ghc/8.4.3/ghc-8.4.3-i386-deb8-linux.tar.xz +ghc 8.4.3 x86_64 debian=8 https://downloads.haskell.org/~ghc/8.4.3/ghc-8.4.3-x86_64-deb8-linux.tar.xz +ghc 8.4.3 x86_64 debian=9,debian,ubuntu https://downloads.haskell.org/~ghc/8.4.3/ghc-8.4.3-x86_64-deb9-linux.tar.xz +ghc 8.4.3 x86_64 fedora=27,fedora,unknown https://downloads.haskell.org/~ghc/8.4.3/ghc-8.4.3-x86_64-fedora27-linux.tar.xz + +ghc 8.4.4 i386 debian=8,debian,ubuntu,unknown https://downloads.haskell.org/~ghc/8.4.4/ghc-8.4.4-i386-deb8-linux.tar.xz +ghc 8.4.4 x86_64 debian=8 https://downloads.haskell.org/~ghc/8.4.4/ghc-8.4.4-x86_64-deb8-linux.tar.xz +ghc 8.4.4 x86_64 debian=9,debian,ubuntu https://downloads.haskell.org/~ghc/8.4.4/ghc-8.4.4-x86_64-deb9-linux.tar.xz +ghc 8.4.4 x86_64 centos=7,centos https://downloads.haskell.org/~ghc/8.4.4/ghc-8.4.4-x86_64-centos70-linux.tar.xz +ghc 8.4.4 x86_64 fedora=27,fedora,unknown https://downloads.haskell.org/~ghc/8.4.4/ghc-8.4.4-x86_64-fedora27-linux.tar.xz + +ghc 8.6.1 i386 debian=8,debian,ubuntu,unknown https://downloads.haskell.org/~ghc/8.6.1/ghc-8.6.1-i386-deb8-linux.tar.xz +ghc 8.6.1 x86_64 debian=8 https://downloads.haskell.org/~ghc/8.6.1/ghc-8.6.1-x86_64-deb8-linux.tar.xz +ghc 8.6.1 x86_64 debian=9,debian,ubuntu https://downloads.haskell.org/~ghc/8.6.1/ghc-8.6.1-x86_64-deb9-linux.tar.xz +ghc 8.6.1 x86_64 fedora=27,fedora,unknown https://downloads.haskell.org/~ghc/8.6.1/ghc-8.6.1-x86_64-fedora27-linux.tar.xz + +cabal-install 2.2.0.0 i386 unknown https://downloads.haskell.org/~cabal/cabal-install-2.2.0.0/cabal-install-2.2.0.0-i386-unknown-linux.tar.gz +cabal-install 2.2.0.0 x86_64 unknown https://downloads.haskell.org/~cabal/cabal-install-2.2.0.0/cabal-install-2.2.0.0-x86_64-unknown-linux.tar.gz + +cabal-install 2.4.0.0 x86_64 unknown https://downloads.haskell.org/~cabal/cabal-install-2.4.0.0/cabal-install-2.4.0.0-x86_64-unknown-linux.tar.gz + diff --git a/.travis.sh b/.travis.sh index ca2408c..d837e1f 100755 --- a/.travis.sh +++ b/.travis.sh @@ -26,6 +26,9 @@ edo ./ghcup -v rm -f 8.6.1 # set GHC edo ./ghcup -v set 8.2.2 +# install default GHC +edo ./ghcup -v install + export PATH="$HOME/.cabal/bin:$HOME/.ghcup/bin:$HOME/.local/bin:$PATH" edo mkdir -p "$HOME"/.local/bin @@ -48,11 +51,15 @@ edo mv shellcheck-latest/shellcheck "$HOME"/.local/bin/shellcheck # check our script for errors edo shellcheck ghcup -# self update -edo ghcup self-update +edo ghcup -v show -edo ghcup show +edo ghcup -v debug-info -edo ghcup debug-info +edo ghcup -v list +edo ghcup -v list -t ghc +edo ghcup -v list -t cabal-install edo ghc --version + +# self update destructively +edo ghcup -v self-update diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1976775 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contributing + +* PR or email +* this script is POSIX shell +* use [shellcheck](https://github.com/koalaman/shellcheck) and `checkbashisms.pl` from [debian devscripts](http://http.debian.net/debian/pool/main/d/devscripts/devscripts_2.18.4.tar.xz) +* whitespaces, no tabs + +## Adding a new distro, updating GHC versions, ... + +This script makes use of two files: + +1. [.download-urls](https://raw.githubusercontent.com/haskell/ghcup/master/.download-urls), + which is meta information on what binary tarball to download for the given version, architecture and distribution. + If you know your distribution XY works with a tarball, add a `=` key to that line. `` + will be the fallback and after that `unknown`. Lines are unique per tarball url. +2. [.available-versions](https://raw.githubusercontent.com/haskell/ghcup/master/.available-versions), + which just lists available upstream versions and tags. + +## TODO + +- [ ] FreeBSD support ([#4](https://github.com/haskell/ghcup/issues/4)) +- [x] Make fetching tarballs more robust ([#5](https://github.com/haskell/ghcup/issues/5)) +- [x] More code documentation +- [x] Allow to compile from source ([#2](https://github.com/haskell/ghcup/issues/2)) +- [x] Allow to install cabal-install as well ([#3](https://github.com/haskell/ghcup/issues/3)) diff --git a/README.md b/README.md index 2d627af..21fbf3e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,21 @@ export PATH="$HOME/.cabal/bin:$HOME/.ghcup/bin:$PATH" See `ghcup --help`. +Common use cases are: + +```sh +# install the last known "best" GHC version +ghcup install +# install a specific GHC version +ghcup install 8.2.2 +# set the currently "active" GHC version +ghcup set 8.4.4 +# install cabal-install +ghcup install-cabal +# update cabal-install +cabal new-install cabal-install +``` + Generally this is meant to be used with [`cabal-install`](https://hackage.haskell.org/package/cabal-install), which handles your haskell packages and can demand that [a specific version](https://cabal.readthedocs.io/en/latest/nix-local-build.html#cfg-flag---with-compiler) of `ghc` is available, which `ghcup` can do. @@ -52,13 +67,6 @@ Alternatively, you can also tell it to compile from source (note that this might In addition this script can also install `cabal-install`. -## Contributing - -* PR or email -* this script is POSIX shell -* use [shellcheck](https://github.com/koalaman/shellcheck) and `checkbashisms.pl` from [debian devscripts](http://http.debian.net/debian/pool/main/d/devscripts/devscripts_2.18.4.tar.xz) -* whitespaces, no tabs - ## Known problems ### Limited distributions supported @@ -75,16 +83,6 @@ ncurses and has no compatibility symlinks in place. Ask your distributor on how to solve this or try to compile from source via `ghcup compile `. -### Unreliable download location - -There is no single reliable URL where to download future -GHC binary releases from, since the tarball names contain -the distro name and version they were built on. As such, -we cannot foresee what will be the next tarball name. - -In such a case, consider to update this script via -`ghcup self-update`. - ### Compilation Although this script can compile GHC for you, it's just a very thin diff --git a/ghcup b/ghcup index 23240fb..60e1694 100755 --- a/ghcup +++ b/ghcup @@ -40,7 +40,7 @@ # @VARIABLE: VERSION # @DESCRIPTION: # Version of this script. -VERSION=0.0.5 +VERSION=0.0.6 # @VARIABLE: SCRIPT # @DESCRIPTION: @@ -100,12 +100,6 @@ SCRIPT_UPDATE_URL="https://raw.githubusercontent.com/haskell/ghcup/master/ghcup" # Base URL for all GHC tarballs. GHC_DOWNLOAD_BASEURL="https://downloads.haskell.org/~ghc" -# @VARIABLE: KNOWN_GOOD_CABAL -# @DESCRIPTION: -# The latests known good cabal-install version for -# which a pre-built binary exists. -KNOWN_GOOD_CABAL="2.4.0.0" - # @VARIABLE: JOBS # @DESCRIPTION: # How many jobs to use for compiling GHC. @@ -117,6 +111,37 @@ JOBS="1" # the script name. SOURCE="$0" +# @VARIABLE: META_DOWNLOAD_URL +# DESCRIPTION: +# The url of the meta file for getting +# download information for ghc/cabal-install etc. +META_DOWNLOAD_URL="https://raw.githubusercontent.com/haskell/ghcup/master/.download-urls" + +# @VARIABLE: META_DOWNLOAD_URL_FORMAT +# DESCRIPTION: +# The version of the meta file format. +# This determines whether this script can read the +# file from "${META_DOWNLOAD_URL}". +META_DOWNLOAD_URL_FORMAT="1" + +# @VARIABLE: META_VERSION_URL +# DESCRIPTION: +# The url of the meta file for getting +# available versions for ghc/cabal-install etc. +META_VERSION_URL="https://raw.githubusercontent.com/haskell/ghcup/master/.available-versions" + +# @VARIABLE: META_VERSION_FORMAT +# DESCRIPTION: +# The version of the meta file format. +# This determines whether this script can read the +# file from "${META_VERSION_URL}". +META_VERSION_FORMAT="1" + +# @VARIABLE: BUG_URL +# DESCRIPTION: +# The url to report bugs to. +BUG_URL="https://github.com/haskell/ghcup/issues" + # @VARIABLE: CACHING # @DESCRIPTION: # Whether to cache tarballs in $CACHE_LOCATION. @@ -153,6 +178,7 @@ SUBCOMMANDS: compile Compile and install GHC from source show Show current/installed GHC set Set currently active GHC version + list Show available GHCs and other tools (upstream) self-update Update this script in-place rm Remove an already installed GHC install-cabal Install cabal-install @@ -172,20 +198,21 @@ DISCUSSION: # and exit the script with status code 1. install_usage() { (>&2 echo "ghcup-install -Install the specified GHC version +Install GHC from binary tarball USAGE: - ${SCRIPT} install [FLAGS] + ${SCRIPT} install [FLAGS] [VERSION] FLAGS: -h, --help Prints help information -f, --force Overwrite already existing installation ARGS: - E.g. \"8.4.3\" or \"8.6.1\" + [VERSION] E.g. \"8.4.3\" or \"8.6.1\" + (default: discovers recommended version) DISCUSSION: - Installs the specified GHC version into + Installs the specified GHC version (or a recommended default one) into a self-contained \"~/.ghcup/ghc/\" directory and symlinks the ghc binaries to \"~/.ghcup/bin/-\". ") @@ -291,10 +318,10 @@ FLAGS: -h, --help Prints help information ARGS: - E.g. \"2.4.0.0\" + [VERSION] E.g. \"2.4.0.0\" DISCUSSION: - Installs the specified cabal-install version (or the default ${KNOWN_GOOD_CABAL}) + Installs the specified cabal-install version (or the default recommended) into \"${BIN_LOCATION}\", so it can be overwritten by later \"cabal new-install cabal-install\", which installs into \"~/.cabal/bin\". Make sure to set up your PATH appropriately, so @@ -357,6 +384,29 @@ DISCUSSION: exit 1 } +# @FUNCTION: list_usage +# @DESCRIPTION: +# Print the help message for 'ghcup list' to STDERR +# and exit the script with status code 1. +list_usage() { + (>&2 echo "ghcup-list +Show available GHCs and other tools (from upstream) + +USAGE: + ${SCRIPT} list + +FLAGS: + -h, --help Prints help information + -t, --tool Tool to list versions for (e.g. 'ghc' or 'cabal-install'). + Default is showing all tools. + +DISCUSSION: + Prints tools (e.g. GHC and cabal-install) and their + available upstream versions. +") + exit 1 +} + @@ -439,6 +489,18 @@ red_message() { printf "\\033[0;31m%s\\033[0m\\n" "$1" } +# @FUNCTION: command_exists +# @USAGE: +# @DESCRIPTION: +# Check if a command exists (no arguments). +# @RETURNS: 0 if the command exists, non-zero otherwise +command_exists() { + [ -z "$1" ] && die "Internal error: no argument given to command_exists" + + command -V "$1" >/dev/null 2>&1 + return $? +} + # @FUNCTION: get_distro_name # @DESCRIPTION: # Gets the current distro identifier following @@ -450,7 +512,7 @@ get_distro_name() { # shellcheck disable=SC1091 . /etc/os-release printf "%s" "$NAME" - elif command -V lsb_release >/dev/null 2>&1; then + elif command_exists lsb_release ; then # linuxbase.org printf "%s" "$(lsb_release -si)" elif [ -f /etc/lsb-release ]; then @@ -478,7 +540,7 @@ get_distro_ver() { # shellcheck disable=SC1091 . /etc/os-release printf "%s" "$VERSION_ID" - elif command -V lsb_release >/dev/null 2>&1; then + elif command_exists lsb_release ; then # linuxbase.org printf "%s" "$(lsb_release -sr)" elif [ -f /etc/lsb-release ]; then @@ -519,52 +581,143 @@ get_arch() { unset myarch } -# @FUNCTION: get_download_url -# @USAGE: +# @FUNCTION: try_download_url +# @USAGE: # @DESCRIPTION: -# Gets the right (hopefully) download url for the given ghc version +# Tries to get the download url of a tool with our +# specified format for download urls (see ${META_DOWNLOAD_URL}"). +# STDOUT: the download url, if an appropriate was found +try_download_url() { + [ "$#" -lt 5 ] && die "Internal error: not enough arguments to try_download_url" + + tool=$1 + ver=$2 + arch=$3 + distro_ident=$4 + filename=$5 + + awk " + NF { + split(\$4,a,\",\") + if (\$1 == \"${tool}\" && \$2 == \"${ver}\" && \$3 == \"${arch}\") { + for (i in a) if (a[i] == \"${distro_ident}\") { + print \$5 + exit + } + } + }" "${filename}" || die "awk failed!" + + unset tool ver arch distro_ident filename +} + +# @FUNCTION: check_meta_file_version +# @USAGE: +# @DESCRIPTION: +# Check that the given meta file has the same format version +# as specified, otherwise die. +check_meta_file_version() { + { [ -z "$1" ] || [ -z "$2" ] ;} && die "Internal error: not enough arguments given to check_meta_file_version" + + mymetavar=$(awk " + NR==1 { + if (\$2 ~ \"fmt-version\") { + { + split(\$2,a,\"=\") + print a[2] + exit + } + } + }" "$1") + + if [ "${mymetavar}" != "$2" ] ; then + die "Unsupported meta file format, run: ghcup self-update" + fi + + unset mymetavar +} + +# @FUNCTION: get_download_url +# @USAGE: +# @DESCRIPTION: +# Gets the download url for the given tool and version # and the current distro and architecture (which it tries to discover). -# @STDOUT: ghc download url +# This uses "${META_DOWNLOAD_URL}" for url discovery. +# @STDOUT: download url or nothing if no appropriate was found get_download_url() { - [ -z "$1" ] && die "Internal error: no argument given to get_download_url" + { [ -z "$1" ] || [ -z "$2" ] ;} && die "Internal error: not enough arguments given to get_download_url" - myghcver=$1 + mytool=$1 + myver=$2 myarch=$(get_arch) - mydistro=$(get_distro_name) + mydistro=$(get_distro_alias "$(get_distro_name)") mydistrover=$(get_distro_ver) + meta_file_name="$(basename "${META_DOWNLOAD_URL}")" - # TODO: awkward, restructure - case "${mydistro},${mydistrover},${myarch},${myghcver}" in - Debian*,7,i386,8.2.2) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb${mydistrover}-linux.tar.xz" - ;; - *,*,i386,*) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz" - ;; - Debian*,*,*,8.2.2) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz" - ;; - Debian*,8,*,*) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz" - ;; - Debian*,*,*,*) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb9-linux.tar.xz" - ;; - Ubuntu*,*,*,8.2.2) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz" - ;; - Ubuntu*,*,*,*) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb9-linux.tar.xz" - ;; - *,*,*,8.2.2) - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz" - ;; - *,*,*,*) # this is our best guess - printf "%s" "${GHC_DOWNLOAD_BASEURL}/${myghcver}/ghc-${myghcver}-${myarch}-fedora27-linux.tar.xz" - ;; - esac + ( + edo cd "${CACHE_LOCATION}" - unset myghcver myarch mydistro mydistrover + download_silent "${META_DOWNLOAD_URL}" + + check_meta_file_version "${meta_file_name}" "${META_DOWNLOAD_URL_FORMAT}" + + # 1st try with full distro=ver + url=$(try_download_url "${mytool}" "${myver}" "${myarch}" "${mydistro}=${mydistrover}" "${meta_file_name}") + if [ -n "${url}" ] ; then + printf "%s" "${url}" + exit 0 + fi + + # 2nd try with just distro + url=$(try_download_url "${mytool}" "${myver}" "${myarch}" "${mydistro}" "${meta_file_name}") + if [ -n "${url}" ] ; then + printf "%s" "${url}" + exit 0 + fi + + # 3rd try with unknown + url=$(try_download_url "${mytool}" "${myver}" "${myarch}" "unknown" "${meta_file_name}") + if [ -n "${url}" ] ; then + printf "%s" "${url}" + exit 0 + fi + ) + + unset mytool myver myarch mydistro mydistrover meta_file_name +} + +# @FUNCTION: get_tool_ver_from_tag +# @USAGE: +# @DESCRIPTION: +# Gets the tool version with the given tag (first match) from +# "${META_VERSION_URL}". +# STDOUT: the version, if any, or nothing +get_tool_ver_from_tag() { + { [ -z "$1" ] || [ -z "$2" ] ;} && die "Internal error: not enough arguments given to get_tool_ver_from_tag" + + mytool=$1 + mytag=$2 + meta_file_name="$(basename "${META_VERSION_URL}")" + + ( + edo cd "${CACHE_LOCATION}" + + download_silent "${META_VERSION_URL}" + + check_meta_file_version "${meta_file_name}" "${META_VERSION_FORMAT}" + + awk " + NF { + if (\$1 == \"${mytool}\") { + split(\$3,a,\",\"); + for (i in a) if (a[i] == \"${mytag}\") { + print \$2 + exit + } + } + }" "${meta_file_name}" || die "awk failed!" + ) + + unset mytool mytag meta_file_name } # @FUNCTION: ghc_already_installed @@ -611,6 +764,23 @@ download() { edo ${DOWNLOADER} ${DOWNLOADER_OPTS} "$1" } +# @FUNCTION: download_silent +# @USAGE: +# @DESCRIPTION: +# Downloads the given url as a file into the current directory, silent, unless +# verbosity is on. +download_silent() { + [ -z "$1" ] && die "Internal error: no argument given to download" + + if ${VERBOSE} ; then + # shellcheck disable=SC2086 + edo ${DOWNLOADER} ${DOWNLOADER_OPTS} "$1" + else + # shellcheck disable=SC2086 + edo ${DOWNLOADER} ${DOWNLOADER_OPTS} "$1" 1> /dev/null 2> /dev/null + fi +} + # @FUNCTION: unpack # @USAGE: # @DESCRIPTION: @@ -668,6 +838,72 @@ ask_for_confirmation() { unset confirmation_msg answer } +# @FUNCTION: get_distro_alias +# @USAGE: +# @DESCRIPTION: +# For a given known distro name, return our internal +# unique distro alias. E.g.: +# Debian GNU/Linux -> debian +# STDOUT: our internal distro alias +get_distro_alias() { + distro_name=$1 + distro_alias=unknown + + case "${distro_name}" in + "Debian"|"Debian GNU/Linux"|"debian") + distro_alias=debian + ;; + "Ubuntu"|"ubuntu") + distro_alias=ubuntu + ;; + "Exherbo"|"exherbo") + distro_alias=exherbo + ;; + "Fedora"|"fedora") + distro_alias=fedora + ;; + "CentOS Linux"|"CentOS"|"centos") + distro_alias=centos + ;; + esac + + printf "%s" "${distro_alias}" + + unset distro_name distro_alias +} + +# @FUNCTION: posix_realpath +# @USAGE: +# @DESCRIPTION: +# Portably gets the realpath and prints it to stdout. +# This was initially inspired by +# https://gist.github.com/tvlooy/cbfbdb111a4ebad8b93e +# and +# https://stackoverflow.com/a/246128 +# +# If the file does not exist, just prints the argument unchanged. +# @STDOUT: realpath of the given file +posix_realpath() { + [ -z "$1" ] && die "Internal error: no argument given to posix_realpath" + mysource=$1 + + while [ -h "${mysource}" ]; do + mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null 2>&1 && pwd )" + mysource="$(readlink "${mysource}")" + [ "${mysource%${mysource#?}}"x != '/x' ] && mysource="${mydir}/${mysource}" + done + mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null 2>&1 && pwd )" + + if [ -z "${mydir}" ] ; then + (>&2 echo "${1}: Permission denied") + elif [ ! -e "$1" ] ; then + echo "${mysource}" + else + echo "${mydir%/}/$(basename "${mysource}")" + fi + + unset mysource mydir posix_realpath_error +} @@ -686,10 +922,16 @@ install_ghc() { myghcver=$1 inst_location=$(get_ghc_location "$1") - download_url=$(get_download_url "${myghcver}") + download_url=$(get_download_url "ghc" "${myghcver}") download_tarball_name=$(basename "${download_url}") first_install=true + if [ -z "${download_url}" ] ; then + die "Could not find an appropriate download for the requested GHC-${myghcver} on your system! Please report a bug at ${BUG_URL}" + fi + + status_message "Installing GHC-${myghcver} for $(get_distro_name) on architecture $(get_arch)" + if ghc_already_installed "${myghcver}" ; then if ${FORCE} ; then echo "GHC already installed in ${inst_location}, overwriting!" @@ -699,15 +941,10 @@ install_ghc() { first_install=false fi - status_message "Installing GHC for $(get_distro_name) on architecture $(get_arch)" tmp_dir=$(mktemp -d) [ -z "${tmp_dir}" ] && die "Failed to create temporary directory" ( if ${CACHING} ; then - [ -e "${CACHE_LOCATION}" ] || { - [ -e "${INSTALL_BASE}" ] || edo mkdir "${INSTALL_BASE}" - edo mkdir "${CACHE_LOCATION}" - } if [ ! -e "${CACHE_LOCATION}/${download_tarball_name}" ] ; then edo cd "${CACHE_LOCATION}" download "${download_url}" @@ -806,24 +1043,6 @@ set_ghc() { #--[ Subcommand self-update ]--# ################################ -# @FUNCTION: script_dir -# @DESCRIPTION: -# Portably gets the full directory of where -# this script resides in and prints it to stdout. -# @STDOUT: script directory -script_dir() { - mysource=${SOURCE} - - while [ -h "${mysource}" ]; do - mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null && pwd )" - mysource="$(readlink "${mysource}")" - [ "${mysource%${mysource#?}}"x != '/x' ] && mysource="${mydir}/${mysource}" - done - mydir="$( cd -P "$( dirname "${mysource}" )" > /dev/null && pwd )" - echo "${mydir}" - - unset mysource mydir -} # @FUNCTION: self_update # @USAGE: @@ -889,10 +1108,10 @@ show_ghc() { # @STDOUT: current GHC version show_ghc_installed() { current_ghc="${BIN_LOCATION}/ghc" - real_ghc=$(realpath "${current_ghc}" 2>/dev/null) + real_ghc=$(posix_realpath "${current_ghc}") if [ -L "${current_ghc}" ] ; then # is symlink - if [ -e "${real_ghc}" ] ; then # exists (realpath was called) + if [ -e "${real_ghc}" ] ; then # exists (posix_realpath was called) real_ghc="$(basename "${real_ghc}" | sed 's#ghc-##')" printf "%s" "${real_ghc}" else # is a broken symlink @@ -985,9 +1204,15 @@ install_cabal() { mycabalver=$1 myarch=$(get_arch) inst_location=$BIN_LOCATION - download_url="https://downloads.haskell.org/~cabal/cabal-install-${mycabalver}/cabal-install-${mycabalver}-${myarch}-unknown-linux.tar.gz" + download_url=$(get_download_url "cabal-install" "${mycabalver}") download_tarball_name=$(basename "${download_url}") + if [ -z "${download_url}" ] ; then + die "Could not find an appropriate download for the requested cabal-install-${mycabalver} on your system! Please report a bug at ${BUG_URL}" + fi + + status_message "Installing cabal-install-${mycabalver} into \"${inst_location}\"" + [ -e "${inst_location}" ] || { # TODO: this is a bit shaky because we don't use -p edo mkdir "${INSTALL_BASE}" @@ -998,10 +1223,6 @@ install_cabal() { [ -z "${tmp_dir}" ] && die "Failed to create temporary directory" ( if ${CACHING} ; then - [ -e "${CACHE_LOCATION}" ] || { - [ -e "${INSTALL_BASE}" ] || edo mkdir "${INSTALL_BASE}" - edo mkdir "${CACHE_LOCATION}" - } if [ ! -e "${CACHE_LOCATION}/${download_tarball_name}" ] ; then edo cd "${CACHE_LOCATION}" download "${download_url}" @@ -1069,10 +1290,6 @@ compile_ghc() { [ -z "${tmp_dir}" ] && die "Failed to create temporary directory" ( if ${CACHING} ; then - [ -e "${CACHE_LOCATION}" ] || { - [ -e "${INSTALL_BASE}" ] || edo mkdir "${INSTALL_BASE}" - edo mkdir "${CACHE_LOCATION}" - } if [ ! -e "${CACHE_LOCATION}/${download_tarball_name}" ] ; then edo cd "${CACHE_LOCATION}" download "${download_url}" @@ -1154,7 +1371,10 @@ print_debug_info() { echo " Downloader: ${DOWNLOADER} ${DOWNLOADER_OPTS} " echo " Script update url: ${SCRIPT_UPDATE_URL}" echo " GHC download baseurl: ${GHC_DOWNLOAD_BASEURL}" - echo " Known good cabal version: ${KNOWN_GOOD_CABAL}" + echo " Meta download url ${META_DOWNLOAD_URL}" + echo " Meta download format ${META_DOWNLOAD_URL_FORMAT}" + echo " Meta version url ${META_VERSION_URL}" + echo " Meta version format ${META_VERSION_FORMAT}" echo echo "Detected system information:" echo " Architecture: $(get_arch)" @@ -1163,6 +1383,51 @@ print_debug_info() { } + ######################### + #--[ Subcommand list ]--# + ######################### + + +# @FUNCTION: list +# @USAGE: [tool] +# @DESCRIPTION: +# List available tools and their versions from upstream. +list() { + mytool=$1 + + meta_file_name="$(basename "${META_VERSION_URL}")" + + ( + edo cd "${CACHE_LOCATION}" + + download_silent "${META_VERSION_URL}" + + check_meta_file_version "${meta_file_name}" "${META_VERSION_FORMAT}" + + echo "Available upstream versions:" + echo + if [ -z "${mytool}" ] ; then + awk " + NF { + if (\$1 != \"#\") { + print \$1 \" \" \$2 + } + }" "${meta_file_name}" || die "awk failed!" + else + awk " + NF { + if (\$1 == \"${mytool}\") { + print \$1 \" \" \$2 + } + }" "${meta_file_name}" || die "awk failed!" + fi + ) + + unset mytool meta_file_name +} + + + ####################### #--[ Sanity checks ]--# ####################### @@ -1172,6 +1437,15 @@ if [ -z "$HOME" ] ; then die "HOME env not set, cannot operate" fi +if [ ! -e "${INSTALL_BASE}" ] ; then + edo mkdir "${INSTALL_BASE}" +fi + +if [ ! -e "${CACHE_LOCATION}" ] ; then + edo mkdir "${CACHE_LOCATION}" +fi + + ############################################## @@ -1210,7 +1484,13 @@ while [ $# -gt 0 ] ; do usage fi ;; - *) # TODO: here comes command availability checking + *) + # check for available commands + for com in ${DOWNLOADER} awk uname basename tar xz gzip mktemp dirname ; do + command_exists "${com}" || die "Command \"${com}\" is required, but does not exist! Please install." + done + unset com + case $1 in install) shift 1 @@ -1223,8 +1503,15 @@ while [ $# -gt 0 ] ; do break;; esac done - [ "${GHC_VER}" ] || install_usage - install_ghc "${GHC_VER}" + if [ -z "${GHC_VER}" ] ; then + _tool_ver="$(get_tool_ver_from_tag "ghc" "recommended")" + if [ -z "${_tool_ver}" ] ; then + die "Could not find a recommended GHC version, please report a bug at ${BUG_URL}!" + fi + install_ghc "${_tool_ver}" + else + install_ghc "${GHC_VER}" + fi break;; set) shift 1 @@ -1250,7 +1537,7 @@ while [ $# -gt 0 ] ; do if [ "${TARGET_LOCATION}" ] ; then self_update "${TARGET_LOCATION}" else - self_update "$(script_dir)" + self_update "$(dirname "$(posix_realpath "${SOURCE}")")" fi break;; show) @@ -1295,10 +1582,14 @@ while [ $# -gt 0 ] ; do break;; esac done - if [ "${CABAL_VER}" ] ; then + if [ -n "${CABAL_VER}" ] ; then install_cabal "${CABAL_VER}" else - install_cabal "${KNOWN_GOOD_CABAL}" + _cabal_ver="$(get_tool_ver_from_tag "cabal-install" "recommended")" + if [ -z "${_cabal_ver}" ] ; then + die "Could not find a recommended cabal-install version, please report a bug at ${BUG_URL}!" + fi + install_cabal "${_cabal_ver}" fi break;; compile) @@ -1331,6 +1622,18 @@ while [ $# -gt 0 ] ; do done print_debug_info break;; + list) + shift 1 + while [ $# -gt 0 ] ; do + case $1 in + -h|--help) list_usage;; + -t|--tool) TOOL=$2 + shift 2;; + *) list_usage;; + esac + done + list "${TOOL}" + break;; *) usage;; esac break;;