From 29911cd420825eaab2d21134cfd8c08c0196e920 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 30 Oct 2018 11:16:08 +0800 Subject: [PATCH 1/6] Introduce and use a meta file format for URLs and available versions Fixes #21 Fixes #9 Fixes #10 Fixes #5 --- .available-versions | 11 ++ .download-urls | 35 ++++ .travis.sh | 15 +- README.md | 21 +-- ghcup | 409 ++++++++++++++++++++++++++++++++++++-------- 5 files changed, 407 insertions(+), 84 deletions(-) create mode 100644 .available-versions create mode 100644 .download-urls 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/README.md b/README.md index 2d627af..aaee437 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,17 @@ In addition this script can also install `cabal-install`. * 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 +### Download locations + +Download locations are maintained in the file [.download-urls](https://raw.githubusercontent.com/haskell/ghcup/master/.download-urls) +and may slightly be behind a very recent release or not tested +on all distro combinations. + +In such a case, consider providing a PR to fix it. + +Similarly, available versions are maintained in +[.available-versions](https://raw.githubusercontent.com/haskell/ghcup/master/.available-versions). + ## Known problems ### Limited distributions supported @@ -75,16 +86,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..4696e9e 100755 --- a/ghcup +++ b/ghcup @@ -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 +} + @@ -519,52 +569,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 +752,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 +826,39 @@ 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 +} @@ -686,10 +877,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 +896,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}" @@ -985,9 +1177,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 +1196,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 +1263,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 +1344,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 +1356,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 +1410,14 @@ 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 + ############################################## @@ -1223,8 +1469,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 @@ -1295,10 +1548,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 +1588,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;; From cbb19a01c066299b4fa5a379fac4b563aa249d91 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Thu, 18 Oct 2018 23:56:55 +0800 Subject: [PATCH 2/6] Check for required commands Fixes #8 --- ghcup | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/ghcup b/ghcup index 4696e9e..9e1390e 100755 --- a/ghcup +++ b/ghcup @@ -489,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 @@ -500,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 @@ -528,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 @@ -1420,6 +1432,7 @@ fi + ############################################## #--[ Command line parsing and entry point ]--# ############################################## @@ -1456,7 +1469,13 @@ while [ $# -gt 0 ] ; do usage fi ;; - *) # TODO: here comes command availability checking + *) + # check for available commands + for com in ${DOWNLOADER} realpath 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 From 1501a2e8fac33c100007c3f5d6877a23e6bbbe63 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 30 Oct 2018 11:16:53 +0800 Subject: [PATCH 3/6] Add CONTRIBUTING.md --- CONTRIBUTING.md | 25 +++++++++++++++++++++++++ README.md | 18 ------------------ 2 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 CONTRIBUTING.md 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 aaee437..70e9f5c 100644 --- a/README.md +++ b/README.md @@ -52,24 +52,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 - -### Download locations - -Download locations are maintained in the file [.download-urls](https://raw.githubusercontent.com/haskell/ghcup/master/.download-urls) -and may slightly be behind a very recent release or not tested -on all distro combinations. - -In such a case, consider providing a PR to fix it. - -Similarly, available versions are maintained in -[.available-versions](https://raw.githubusercontent.com/haskell/ghcup/master/.available-versions). - ## Known problems ### Limited distributions supported From 324d6d9d8cb09f3c920282a7af95811ca56bea61 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 30 Oct 2018 11:34:40 +0800 Subject: [PATCH 4/6] Improve README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 70e9f5c..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. From 488079e3a336f0c84fb96854b179828db420a5e8 Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 30 Oct 2018 11:31:28 +0800 Subject: [PATCH 5/6] Bump VERSION to 0.0.6 --- ghcup | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ghcup b/ghcup index 9e1390e..053c331 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: From c1f7ce75f072148615402d3e847112cb7e73195a Mon Sep 17 00:00:00 2001 From: Julian Ospald Date: Tue, 30 Oct 2018 18:06:12 +0800 Subject: [PATCH 6/6] Get rid of realpath requirement, fixes #31 --- ghcup | 59 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/ghcup b/ghcup index 053c331..60e1694 100755 --- a/ghcup +++ b/ghcup @@ -872,6 +872,39 @@ get_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 +} + @@ -1010,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: @@ -1093,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 @@ -1471,7 +1486,7 @@ while [ $# -gt 0 ] ; do ;; *) # check for available commands - for com in ${DOWNLOADER} realpath awk uname basename tar xz gzip mktemp dirname ; do + 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 @@ -1522,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)