2018-09-29 07:50:26 +00:00
#!/bin/sh
2018-09-29 15:31:07 +00:00
#
# Copyright (c) 2018, Julian Ospald <hasufell@posteo.de>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the <ORGANIZATION> nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
2018-10-30 13:31:31 +00:00
2018-09-29 07:50:26 +00:00
2018-09-29 08:51:36 +00:00
2018-09-30 06:26:59 +00:00
##########################
#--[ Global Variables ]--#
##########################
2018-09-29 07:50:26 +00:00
2018-09-30 06:20:32 +00:00
# @VARIABLE: VERSION
# @DESCRIPTION:
# Version of this script.
2018-10-30 03:31:28 +00:00
VERSION=0.0.6
2018-09-30 06:20:32 +00:00
# @VARIABLE: SCRIPT
# @DESCRIPTION:
2018-11-04 10:20:50 +00:00
# Name of this script. This will be the
# shell name if this script is sourced, so
# only rely on this for echos and trivial things.
2018-09-29 13:46:39 +00:00
SCRIPT="$(basename "$0")"
2018-09-30 06:20:32 +00:00
# @VARIABLE: VERBOSE
# @DESCRIPTION:
# Whether to print verbose messages in this script.
2018-09-29 07:50:26 +00:00
VERBOSE=false
2018-09-30 06:20:32 +00:00
# @VARIABLE: FORCE
# @DESCRIPTION:
# Whether to force installation and overwrite files.
2018-09-29 08:51:36 +00:00
FORCE=false
2018-09-30 06:20:32 +00:00
# @VARIABLE: INSTALL_BASE
# @DESCRIPTION:
# The main install directory where all ghcup stuff happens.
2018-09-29 08:51:36 +00:00
INSTALL_BASE="$HOME/.ghcup"
2018-09-30 06:20:32 +00:00
# @VARIABLE: GHC_LOCATION
# @DESCRIPTION:
# The location where ghcup will install different ghc versions.
2018-10-05 02:44:23 +00:00
# This is expected to be a subdirectory of INSTALL_BASE.
2018-09-29 18:57:03 +00:00
GHC_LOCATION="$INSTALL_BASE/ghc"
2018-09-30 06:20:32 +00:00
# @VARIABLE: BIN_LOCATION
# @DESCRIPTION:
# The location where ghcup will create symlinks for GHC binaries.
2018-10-05 02:44:23 +00:00
# This is expected to be a subdirectory of INSTALL_BASE.
2018-09-29 18:57:03 +00:00
BIN_LOCATION="$INSTALL_BASE/bin"
2018-09-30 06:20:32 +00:00
2018-10-28 09:34:19 +00:00
# @VARIABLE: CACHE_LOCATION
# @DESCRIPTION:
# The location where ghcup will put tarballs for caching.
# This is expected to be a subdirectory of INSTALL_BASE.
CACHE_LOCATION="$INSTALL_BASE/cache"
2018-09-30 06:20:32 +00:00
# @VARIABLE: DOWNLOADER
# @DESCRIPTION:
# What program to use for downloading files.
2018-09-30 05:25:17 +00:00
DOWNLOADER="curl"
2018-09-30 06:20:32 +00:00
# @VARIABLE: DOWNLOADER_OPTS
# @DESCRIPTION:
# Options passed to the download program.
2018-09-30 05:25:17 +00:00
DOWNLOADER_OPTS="--fail -O"
2018-09-30 06:20:32 +00:00
# @VARIABLE: SCRIPT_UPDATE_URL
# @DESCRIPTION:
# Location to update this script from.
2018-10-16 06:41:42 +00:00
SCRIPT_UPDATE_URL="https://raw.githubusercontent.com/haskell/ghcup/master/ghcup"
2018-09-30 06:20:32 +00:00
# @VARIABLE: GHC_DOWNLOAD_BASEURL
# @DESCRIPTION:
# Base URL for all GHC tarballs.
2018-09-30 05:34:54 +00:00
GHC_DOWNLOAD_BASEURL="https://downloads.haskell.org/~ghc"
2018-09-29 07:50:26 +00:00
2018-09-30 11:39:40 +00:00
# @VARIABLE: JOBS
# @DESCRIPTION:
# How many jobs to use for compiling GHC.
JOBS="1"
2018-10-15 09:07:00 +00:00
# @VARIABLE: SOURCE
# @DESCRIPTION:
# The $0 argument, which contains
# the script name.
SOURCE="$0"
2018-09-30 06:26:59 +00:00
2018-10-30 03:16:08 +00:00
# @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"
2018-11-06 05:32:55 +00:00
# @VARIABLE: META_DOWNLOAD_FORMAT
2018-10-30 03:16:08 +00:00
# DESCRIPTION:
# The version of the meta file format.
# This determines whether this script can read the
# file from "${META_DOWNLOAD_URL}".
2018-11-06 05:32:55 +00:00
META_DOWNLOAD_FORMAT="1"
2018-10-30 03:16:08 +00:00
# @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"
2018-10-28 09:34:19 +00:00
# @VARIABLE: CACHING
# @DESCRIPTION:
# Whether to cache tarballs in $CACHE_LOCATION.
CACHING=false
2018-09-30 06:26:59 +00:00
####################
#--[ Print Help ]--#
####################
2018-09-29 07:50:26 +00:00
2018-09-30 06:20:32 +00:00
# @FUNCTION: usage
# @DESCRIPTION:
# Print the help message for 'ghcup' to STDERR
# and exit the script with status code 1.
2018-09-29 07:50:26 +00:00
usage() {
2018-09-29 13:03:24 +00:00
(>&2 echo "ghcup ${VERSION}
GHC up toolchain installer
USAGE:
${SCRIPT} [FLAGS] <SUBCOMMAND>
2018-09-29 07:50:26 +00:00
FLAGS:
-v, --verbose Enable verbose output
-h, --help Prints help information
-V, --version Prints version information
2018-10-28 09:00:54 +00:00
-w, --wget Use wget instead of curl
2018-10-28 09:34:19 +00:00
-c, --cache Use \"${CACHE_LOCATION}\" for caching tarballs
(these will not be removed by ghcup)
2018-09-29 07:50:26 +00:00
SUBCOMMANDS:
2018-09-29 08:51:36 +00:00
install Install GHC
2018-10-05 02:34:26 +00:00
compile Compile and install GHC from source
2018-09-29 15:01:39 +00:00
show Show current/installed GHC
2018-09-29 13:07:57 +00:00
set Set currently active GHC version
2018-10-30 03:16:08 +00:00
list Show available GHCs and other tools (upstream)
2018-11-04 08:53:49 +00:00
upgrade Upgrade this script in-place
2018-09-29 18:58:55 +00:00
rm Remove an already installed GHC
2018-09-30 08:46:50 +00:00
install-cabal Install cabal-install
2018-10-17 10:19:43 +00:00
debug-info Print debug info (e.g. detected system/distro)
2018-09-29 17:46:21 +00:00
DISCUSSION:
ghcup installs the Glasgow Haskell Compiler from the official
release channels, enabling you to easily switch between different
versions.
2018-09-29 07:50:26 +00:00
")
exit 1
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: install_usage
# @DESCRIPTION:
# Print the help message for 'ghcup install' to STDERR
# and exit the script with status code 1.
2018-09-29 07:50:26 +00:00
install_usage() {
2018-09-29 13:03:24 +00:00
(>&2 echo "ghcup-install
2018-10-30 03:16:08 +00:00
Install GHC from binary tarball
2018-09-29 13:03:24 +00:00
USAGE:
2018-11-06 05:32:55 +00:00
${SCRIPT} install [FLAGS] [VERSION|TAG]
2018-09-29 07:50:26 +00:00
FLAGS:
-h, --help Prints help information
2018-09-29 08:51:36 +00:00
-f, --force Overwrite already existing installation
2018-09-29 07:50:26 +00:00
ARGS:
2018-11-06 05:32:55 +00:00
[VERSION|TAG] E.g. \"8.4.3\" or \"8.6.1\" or
a tag like \"recommended\" or \"latest\"
2018-10-30 03:16:08 +00:00
(default: discovers recommended version)
2018-09-29 17:46:21 +00:00
DISCUSSION:
2018-10-30 03:16:08 +00:00
Installs the specified GHC version (or a recommended default one) into
2018-09-29 17:46:21 +00:00
a self-contained \"~/.ghcup/ghc/<ghcver>\" directory
and symlinks the ghc binaries to \"~/.ghcup/bin/<binary>-<ghcver>\".
2018-09-29 07:50:26 +00:00
")
exit 1
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: set_usage
# @DESCRIPTION:
# Print the help message for 'ghcup set' to STDERR
# and exit the script with status code 1.
2018-09-29 13:07:57 +00:00
set_usage() {
(>&2 echo "ghcup-set
2018-09-29 13:03:24 +00:00
Set the currently active GHC to the specified version
USAGE:
2018-09-29 13:07:57 +00:00
${SCRIPT} set [FLAGS] <VERSION>
2018-09-29 08:51:36 +00:00
FLAGS:
-h, --help Prints help information
ARGS:
<VERSION> E.g. \"8.4.3\" or \"8.6.1\"
2018-09-29 17:46:21 +00:00
DISCUSSION:
Sets the the current GHC version by creating non-versioned
symlinks for all ghc binaries of the specified version in
\"~/.ghcup/bin/<binary>\".
2018-09-29 08:51:36 +00:00
")
exit 1
}
2018-11-04 08:53:49 +00:00
# @FUNCTION: upgrade_usage
2018-09-30 06:20:32 +00:00
# @DESCRIPTION:
2018-11-04 08:53:49 +00:00
# Print the help message for 'ghcup upgrade' to STDERR
2018-09-30 06:20:32 +00:00
# and exit the script with status code 1.
2018-11-04 08:53:49 +00:00
upgrade_usage() {
(>&2 echo "ghcup-upgrade
2018-09-29 17:17:14 +00:00
Update the ghcup script in-place
2018-09-29 13:03:24 +00:00
USAGE:
2018-11-04 08:53:49 +00:00
${SCRIPT} upgrade [FLAGS] [TARGET-LOCATION]
2018-09-29 08:51:36 +00:00
FLAGS:
-h, --help Prints help information
ARGS:
2018-10-15 14:16:40 +00:00
[TARGET-LOCATION] Where to place the updated script (defaults to directory of the script).
2018-09-29 08:51:36 +00:00
")
exit 1
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: show_usage
# @DESCRIPTION:
# Print the help message for 'ghcup show' to STDERR
# and exit the script with status code 1.
2018-09-29 15:01:39 +00:00
show_usage() {
(>&2 echo "ghcup-show
Show the installed/current GHC versions
USAGE:
${SCRIPT} show [FLAGS]
FLAGS:
-h, --help Prints help information
-i, --installed Show installed GHC version only
")
exit 1
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: rm_usage
# @DESCRIPTION:
# Print the help message for 'ghcup rm' to STDERR
# and exit the script with status code 1.
2018-09-29 18:58:55 +00:00
rm_usage() {
(>&2 echo "ghcup-rm
Remove the given GHC version installed by ghcup
USAGE:
${SCRIPT} rm [FLAGS] <VERSION>
FLAGS:
-h, --help Prints help information
2018-10-27 08:18:14 +00:00
-f, --force Don't prompt user
2018-09-29 18:58:55 +00:00
ARGS:
<VERSION> E.g. \"8.4.3\" or \"8.6.1\"
")
exit 1
}
2018-09-30 08:46:50 +00:00
# @FUNCTION: install_cabal_usage
# @DESCRIPTION:
# Print the help message for 'ghcup install-cabal' to STDERR
# and exit the script with status code 1.
install_cabal_usage() {
(>&2 echo "ghcup-install-cabal
Install the specified or a default cabal version
USAGE:
2018-11-06 05:32:55 +00:00
${SCRIPT} install-cabal [FLAGS] [VERSION|TAG]
2018-09-30 08:46:50 +00:00
FLAGS:
-h, --help Prints help information
ARGS:
2018-11-06 05:32:55 +00:00
[VERSION|TAG] E.g. \"2.4.0.0\" or a tag
like \"recommended\" or \"latest\"
2018-09-30 08:46:50 +00:00
DISCUSSION:
2018-10-30 03:16:08 +00:00
Installs the specified cabal-install version (or the default recommended)
2018-10-05 02:44:23 +00:00
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
the cabal installation takes precedence.
2018-09-30 08:46:50 +00:00
")
exit 1
}
2018-09-30 11:39:40 +00:00
# @FUNCTION: compile_usage
# @DESCRIPTION:
# Print the help message for 'ghcup compile' to STDERR
# and exit the script with status code 1.
compile_usage() {
(>&2 echo "ghcup-compile
Compile and install the specified GHC version
USAGE:
${SCRIPT} compile [FLAGS] <VERSION> <BOOTSTRAP-GHC>
FLAGS:
2018-10-16 08:18:08 +00:00
-h, --help Prints help information
-f, --force Overwrite already existing installation
-j, --jobs <n> How many jobs for compilation
-c, --build-config <filepath> Use the given config file as build config
2018-09-30 11:39:40 +00:00
ARGS:
<VERSION> E.g. \"8.4.3\" or \"8.6.1\"
<BOOTSTRAP-GHC> E.g. \"ghc-8.2.2\" or a full path
DISCUSSION:
Compiles and installs the specified GHC version into
a self-contained \"~/.ghcup/ghc/<ghcver>\" directory
and symlinks the ghc binaries to \"~/.ghcup/bin/<binary>-<ghcver>\".
EXAMPLE:
ghcup -v compile -f -j 4 8.4.2 ghc-8.2.2
")
exit 1
}
2018-10-17 10:19:43 +00:00
# @FUNCTION: debug_info_usage
# @DESCRIPTION:
# Print the help message for 'ghcup debug-info' to STDERR
# and exit the script with status code 1.
debug_info_usage() {
(>&2 echo "ghcup-debug-info
Print debug info (e.g. detected system/distro)
USAGE:
${SCRIPT} debug-info
FLAGS:
-h, --help Prints help information
DISCUSSION:
Prints debug information, e.g. detected system architecture,
distribution, version, as well as script variables. This
is mainly useful for debugging purposes.
")
exit 1
}
2018-10-30 03:16:08 +00:00
# @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
}
2018-09-30 08:46:50 +00:00
2018-09-29 15:01:39 +00:00
2018-09-30 06:26:59 +00:00
###########################
#--[ Utility functions ]--#
###########################
2018-09-29 07:50:26 +00:00
2018-09-30 06:20:32 +00:00
# @FUNCTION: die
# @USAGE: [msg]
# @DESCRIPTION:
# Exits the shell script with status code 2
# and prints the given message in red to STDERR, if any.
2018-09-29 07:50:26 +00:00
die() {
2018-09-29 19:14:56 +00:00
(>&2 red_message "$1")
2018-09-29 07:50:26 +00:00
exit 2
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: edo
# @USAGE: <command>
# @DESCRIPTION:
# Executes the given command. Also prints what
# command that is (in blue) if verbosity is enabled.
# Exits with status code 2 if the command failed.
2018-09-29 14:31:08 +00:00
edo()
{
if ${VERBOSE} ; then
2018-09-29 17:33:08 +00:00
printf "\\033[0;34m%s\\033[0m\\n" "$*" 1>&2
2018-09-29 14:31:08 +00:00
fi
"$@" || exit 2
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: debug_message
# @USAGE: <msg>
# @DESCRIPTION:
# Print a blue debug message if verbosity is enabled.
2018-09-29 18:57:03 +00:00
debug_message() {
2018-09-29 07:50:26 +00:00
if ${VERBOSE} ; then
2018-09-29 17:33:08 +00:00
printf "\\033[0;34m%s\\033[0m\\n" "$1"
2018-09-29 07:50:26 +00:00
fi
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: optionv
# @USAGE: <arg1> [arg2]
# @DESCRIPTION:
# If verbosity is enabled, echo the first argument, otherwise
# the second (if any).
# @STDOUT: first or second argument
2018-09-29 17:54:01 +00:00
optionv() {
if ${VERBOSE} ; then
echo "$1"
else
if [ -n "$2" ] ; then
echo "$2"
fi
fi
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: status_message
# @USAGE: <msg>
# @DESCRIPTION:
# Print a green status message.
2018-09-29 18:57:03 +00:00
status_message() {
2018-09-29 13:46:39 +00:00
printf "\\033[0;32m%s\\033[0m\\n" "$1"
2018-09-29 07:50:26 +00:00
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: warning_message
# @USAGE: <msg>
# @DESCRIPTION:
# Print a yellow warning message.
2018-09-29 18:57:03 +00:00
warning_message() {
printf "\\033[1;33m%s\\033[0m\\n" "$1"
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: red_message
# @USAGE: <msg>
# @DESCRIPTION:
# Print a red message.
2018-09-29 19:14:56 +00:00
red_message() {
printf "\\033[0;31m%s\\033[0m\\n" "$1"
}
2018-10-18 15:56:55 +00:00
# @FUNCTION: command_exists
# @USAGE: <command>
# @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 $?
}
2018-11-04 10:21:23 +00:00
# @FUNCTION: check_required_commands
# @USAGE: [additional-commands]
# @DESCRIPTION:
# Check that all required commands for this script exist.
# @STDOUT: The commands that do not exist
# @RETURNS: 0 if all command exists, non-zero otherwise
check_required_commands() {
_missing_commands=
for com in "$@" awk uname basename tar xz gzip mktemp dirname ; do
command_exists "${com}" || {
_missing_commands="${_missing_commands} ${com}"
}
done
unset com
if [ -n "${_missing_commands}" ] ; then
printf "%s" "${_missing_commands}"
unset _missing_commands
return 1
else
unset _missing_commands
return 0
fi
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: get_distro_name
# @DESCRIPTION:
# Gets the current distro identifier following
# https://unix.stackexchange.com/a/6348
# @STDOUT: current distro identifier
2018-09-29 07:50:26 +00:00
get_distro_name() {
if [ -f /etc/os-release ]; then
# freedesktop.org and systemd
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC1091
2018-09-29 07:50:26 +00:00
. /etc/os-release
2018-09-29 13:46:39 +00:00
printf "%s" "$NAME"
2018-10-18 15:56:55 +00:00
elif command_exists lsb_release ; then
2018-09-29 07:50:26 +00:00
# linuxbase.org
2018-09-29 13:46:39 +00:00
printf "%s" "$(lsb_release -si)"
2018-09-29 07:50:26 +00:00
elif [ -f /etc/lsb-release ]; then
# For some versions of Debian/Ubuntu without lsb_release command
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC1091
2018-09-29 07:50:26 +00:00
. /etc/lsb-release
2018-09-29 13:46:39 +00:00
printf "%s" "$DISTRIB_ID"
2018-09-29 07:50:26 +00:00
elif [ -f /etc/debian_version ]; then
# Older Debian/Ubuntu/etc.
printf "Debian"
else
# Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
2018-09-29 13:46:39 +00:00
printf "%s" "$(uname -s)"
2018-09-29 07:50:26 +00:00
fi
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: get_distro_ver
# @DESCRIPTION:
# Gets the current distro version (if any) following
# https://unix.stackexchange.com/a/6348
# @STDOUT: current distro version, if any
2018-09-29 07:50:26 +00:00
get_distro_ver() {
if [ -f /etc/os-release ]; then
# freedesktop.org and systemd
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC1091
2018-09-29 07:50:26 +00:00
. /etc/os-release
2018-09-29 13:46:39 +00:00
printf "%s" "$VERSION_ID"
2018-10-18 15:56:55 +00:00
elif command_exists lsb_release ; then
2018-09-29 07:50:26 +00:00
# linuxbase.org
2018-09-29 13:46:39 +00:00
printf "%s" "$(lsb_release -sr)"
2018-09-29 07:50:26 +00:00
elif [ -f /etc/lsb-release ]; then
# For some versions of Debian/Ubuntu without lsb_release command
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC1091
2018-09-29 07:50:26 +00:00
. /etc/lsb-release
2018-09-29 13:46:39 +00:00
printf "%s" "$DISTRIB_RELEASE"
2018-09-29 07:50:26 +00:00
elif [ -f /etc/debian_version ]; then
# Older Debian/Ubuntu/etc.
2018-09-29 13:46:39 +00:00
printf "%s" "$(cat /etc/debian_version)"
2018-09-29 07:50:26 +00:00
else
# Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
2018-09-29 13:46:39 +00:00
printf "%s" "$(uname -r)"
2018-09-29 07:50:26 +00:00
fi
}
2018-09-30 06:20:32 +00:00
# @FUNCTION: get_arch
# @DESCRIPTION:
# Gets the architecture following
# https://unix.stackexchange.com/a/6348
# Fails for any architecture that we don't know a GHC version for.
# @STDOUT: current architecture
2018-09-29 07:50:26 +00:00
get_arch() {
myarch=$(uname -m)
case "${myarch}" in
x86_64)
printf "x86_64" # or AMD64 or Intel64 or whatever
;;
i*86)
printf "i386" # or IA32 or Intel32 or whatever
;;
*)
die "Cannot figure out architecture (was: ${myarch})"
;;
esac
unset myarch
}
2018-10-30 03:16:08 +00:00
# @FUNCTION: try_download_url
# @USAGE: <tool> <ver> <arch> <distro-ident> <file>
# @DESCRIPTION:
# 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: <file> <metaver>
# @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
2018-11-04 08:53:49 +00:00
die "Unsupported meta file format, run: ${SCRIPT} upgrade"
2018-10-30 03:16:08 +00:00
fi
unset mymetavar
}
2018-09-30 05:52:01 +00:00
# @FUNCTION: get_download_url
2018-10-30 03:16:08 +00:00
# @USAGE: <tool> <version>
2018-09-30 05:52:01 +00:00
# @DESCRIPTION:
2018-10-30 03:16:08 +00:00
# Gets the download url for the given tool and version
2018-09-30 05:52:01 +00:00
# and the current distro and architecture (which it tries to discover).
2018-10-30 03:16:08 +00:00
# This uses "${META_DOWNLOAD_URL}" for url discovery.
# @STDOUT: download url or nothing if no appropriate was found
2018-09-29 07:50:26 +00:00
get_download_url() {
2018-10-30 03:16:08 +00:00
{ [ -z "$1" ] || [ -z "$2" ] ;} && die "Internal error: not enough arguments given to get_download_url"
2018-09-30 05:52:40 +00:00
2018-10-30 03:16:08 +00:00
mytool=$1
myver=$2
2018-09-29 07:50:26 +00:00
myarch=$(get_arch)
2018-10-30 03:16:08 +00:00
mydistro=$(get_distro_alias "$(get_distro_name)")
2018-09-29 07:50:26 +00:00
mydistrover=$(get_distro_ver)
2018-11-06 05:32:55 +00:00
meta_file="$(get_meta_download_file)"
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
# 1st try with full distro=ver
url=$(try_download_url "${mytool}" "${myver}" "${myarch}" "${mydistro}=${mydistrover}" "${meta_file}")
if [ -n "${url}" ] ; then
printf "%s" "${url}"
exit 0
fi
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
# 2nd try with just distro
url=$(try_download_url "${mytool}" "${myver}" "${myarch}" "${mydistro}" "${meta_file}")
if [ -n "${url}" ] ; then
printf "%s" "${url}"
exit 0
fi
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
# 3rd try with unknown
url=$(try_download_url "${mytool}" "${myver}" "${myarch}" "unknown" "${meta_file}")
if [ -n "${url}" ] ; then
printf "%s" "${url}"
exit 0
fi
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
unset mytool myver myarch mydistro mydistrover meta_file
2018-10-30 03:16:08 +00:00
}
# @FUNCTION: get_tool_ver_from_tag
# @USAGE: <tool> <tag>
# @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
2018-11-06 05:32:55 +00:00
meta_file="$(get_meta_version_file)"
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
awk "
NF {
if (\$1 == \"${mytool}\") {
split(\$3,a,\",\");
for (i in a) if (a[i] == \"${mytag}\") {
print \$2
exit
2018-10-30 03:16:08 +00:00
}
2018-11-06 05:32:55 +00:00
}
}" "${meta_file}" || die "awk failed!"
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
unset mytool mytag meta_file
2018-09-29 07:50:26 +00:00
}
2018-09-29 18:57:03 +00:00
# @FUNCTION: ghc_already_installed
# @USAGE: <ghcversion>
# @DESCRIPTION:
# Checks whether the specified GHC version
# has been installed by ghcup already.
# @RETURN: 0 if GHC is already installed, 1 otherwise
ghc_already_installed() {
2018-09-30 05:52:40 +00:00
[ -z "$1" ] && die "Internal error: no argument given to ghc_already_installed"
2018-09-29 18:57:03 +00:00
if [ -e "$(get_ghc_location "$1")" ] ; then
return 0
else
return 1
fi
}
# @FUNCTION: get_ghc_location
# @USAGE: <ghcversion>
# @DESCRIPTION:
# Gets/prints the location where the specified GHC is or would be installed.
# Doesn't check whether that directory actually exist. Use
# 'ghc_already_installed' for that.
# @STDOUT: ghc location
get_ghc_location() {
2018-09-30 05:52:40 +00:00
[ -z "$1" ] && die "Internal error: no argument given to get_ghc_location"
2018-09-29 18:57:03 +00:00
myghcver=$1
inst_location=${GHC_LOCATION}/${myghcver}
printf "%s" "${inst_location}"
2018-09-30 05:21:09 +00:00
unset myghcver inst_location
2018-09-29 18:57:03 +00:00
}
2018-09-30 05:25:17 +00:00
# @FUNCTION: download
# @USAGE: <url>
# @DESCRIPTION:
# Downloads the given url as a file into the current directory.
download() {
2018-09-30 05:52:40 +00:00
[ -z "$1" ] && die "Internal error: no argument given to download"
2018-09-30 05:25:17 +00:00
# shellcheck disable=SC2086
2018-10-17 09:04:39 +00:00
edo ${DOWNLOADER} ${DOWNLOADER_OPTS} "$1"
2018-09-30 05:25:17 +00:00
}
2018-10-30 03:16:08 +00:00
# @FUNCTION: download_silent
# @USAGE: <url>
# @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
}
2018-10-17 07:39:51 +00:00
# @FUNCTION: unpack
# @USAGE: <tarball>
# @DESCRIPTION:
# Uncompresses and unpacks the given tarball if needed by discovering the
# file extension.
unpack() {
[ -z "$1" ] && die "Internal error: no argument given to unpack"
filename=$1
file_ext=${filename##*.}
# this is for portability, since not all
# distros have tar with compression detection
# capability
case "${file_ext}" in
xz)
debug_message "xz -cd \"${filename}\" | tar -xf -"
( xz -cd "${filename}" | tar -xf - ; ) || die "unpacking failed!"
;;
gz)
debug_message "gzip -cd \"${filename}\" | tar -xf -"
( gzip -cd "${filename}" | tar -xf - ; ) || die "unpacking failed!"
;;
tar)
edo tar -xf "${filename}"
;;
*)
die "Unknown file extension: \"${file_ext}\""
esac
unset filename file_ext
}
2018-10-27 08:18:14 +00:00
# @FUNCTION: ask_for_confirmation
# @USAGE: [confirmation-msg]
# @DESCRIPTION:
# Asks the user for confirmation and returns 0 for yes, 1 for no.
# @RETURN: 0 if user confirmed, 1 otherwise
ask_for_confirmation() {
confirmation_msg=$1
if [ -n "${confirmation_msg}" ] ; then
printf "%s\\n(y/n and press Enter)\\n" "${confirmation_msg}"
else
printf "Confirm action: (y/n and press Enter)\\n"
fi
read -r answer
if [ "${answer}" != "${answer#[Yy]}" ] ;then
return 0
else
return 1
fi
unset confirmation_msg answer
}
2018-10-30 03:16:08 +00:00
# @FUNCTION: get_distro_alias
# @USAGE: <distro name>
# @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
;;
2018-11-11 18:12:44 +00:00
"Darwin")
distro_alias=darwin
;;
2018-10-30 03:16:08 +00:00
esac
printf "%s" "${distro_alias}"
unset distro_name distro_alias
}
2018-10-17 07:39:51 +00:00
2018-10-30 10:06:12 +00:00
# @FUNCTION: posix_realpath
# @USAGE: <file>
# @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 )"
2018-10-30 14:19:20 +00:00
# TODO: better distinguish between "does not exist" and "permission denied"
2018-10-30 10:06:12 +00:00
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
}
2018-11-04 10:20:50 +00:00
# @FUNCTION: is_sourced
# @DESCRIPTION:
# Tries to figure out if we are being sourced. Based on
# https://stackoverflow.com/a/28776166
# @RETURNS: 0 if we are being sourced, 1 otherwise
is_sourced() {
if [ -n "$ZSH_EVAL_CONTEXT" ]; then
case $ZSH_EVAL_CONTEXT in
*:file)
return 0 ;;
esac
elif [ -n "$KSH_VERSION" ]; then
# shellcheck disable=SC2154
[ "$(cd "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && return 0
elif [ -n "$BASH_VERSION" ]; then
# shellcheck disable=SC2128
[ "$0" != "$BASH_SOURCE" ] && return 0
else # All other shells: examine $0 for known shell binary filenames
# Detects `sh` and `dash`; add additional shell filenames as needed.
case ${0##*/} in
sh|dash)
return 0 ;;
esac
fi
# assume we are not sourced, if our above checks didn't find it
return 1
}
2018-10-17 07:39:51 +00:00
2018-11-06 05:32:55 +00:00
# @FUNCTION: get_meta_version_file
# @DESCRIPTION:
# Downloads the META_VERSION_URL
# in case it hasn't been downloaded
# during the execution of this script yet
# and checks the format version matches
# the expected one.
# @STDOUT: file location
get_meta_version_file() {
meta_file_name="$(basename "${META_VERSION_URL}")"
meta_filepath="${CACHE_LOCATION}/${meta_file_name}"
if [ ! -e "${meta_filepath}" ] ; then
(
edo cd "${CACHE_LOCATION}"
download_silent "${META_VERSION_URL}"
) || die "downloading failed"
fi
2018-11-06 08:27:27 +00:00
check_meta_file_version "${meta_filepath}" "${META_VERSION_FORMAT}"
2018-11-06 05:32:55 +00:00
printf "%s" "${meta_filepath}"
unset meta_file_name meta_filepath
}
# @FUNCTION: get_meta_download_file
# @DESCRIPTION:
# Downloads the META_DOWNLOAD_URL
# in case it hasn't been downloaded
# during the execution of this script yet
# and checks the format version matches
# the expected one.
# @STDOUT: file location
get_meta_download_file() {
meta_file_name="$(basename "${META_DOWNLOAD_URL}")"
meta_filepath="${CACHE_LOCATION}/${meta_file_name}"
if [ ! -e "${meta_filepath}" ] ; then
(
edo cd "${CACHE_LOCATION}"
download_silent "${META_DOWNLOAD_URL}"
) || die "downloading failed!"
fi
2018-11-06 08:27:27 +00:00
check_meta_file_version "${meta_filepath}" "${META_DOWNLOAD_FORMAT}"
2018-11-06 05:32:55 +00:00
printf "%s" "${CACHE_LOCATION}/${meta_file_name}"
unset meta_file_name meta_filepath
}
# @FUNCTION: known_tool_versions
# @USAGE: <tool>
# @DESCRIPTION:
# Prints the known tool versions from
# META_VERSION_URL.
# @STDOUT: known ghc versions
known_tool_versions() {
[ -z "$1" ] && die "Internal error: no argument given to posix_realpath"
mytool=$1
meta_file="$(get_meta_version_file)"
awk "
NF {
if (\$1 == \"${mytool}\") {
print \$2
}
}" "${meta_file}" || die "awk failed!"
unset mytool meta_file
}
# @FUNCTION: known_tool_tags
# @USAGE: <tool>
# @DESCRIPTION:
# Prints the known tool tags from
# META_VERSION_URL.
# @STDOUT: known tool tags
known_tool_tags() {
[ -z "$1" ] && die "Internal error: no argument given to known_tool_tags"
mytool=$1
meta_file="$(get_meta_version_file)"
awk "
NF {
if (\$1 == \"${mytool}\") {
split(\$3,a,\",\");
for (i in a) {
print a[i]
}
}
}" "${meta_file}" | sort -u || die "awk failed!"
unset mytool meta_file
}
# @FUNCTION: array_contains
# @USAGE: <element> <array>
# @DESCRIPTION:
# Checks whether the given elements
# is in the array.
# @RETURNS: returns 0 if the element is in the array, 1 otherwise
array_contains() {
{ [ -z "$1" ] || [ -z "$2" ] ;} && die "Internal error: not enough arguments given to array_contains"
element=$1
array=$2
for e in ${array} ; do
if [ "${e}" = "${element}" ] ; then
unset e element array
return 0
fi
done
unset e element array
return 1
}
2018-09-29 07:50:26 +00:00
2018-09-30 06:26:59 +00:00
############################
#--[ Subcommand install ]--#
############################
2018-09-29 07:50:26 +00:00
2018-09-30 05:52:01 +00:00
# @FUNCTION: install_ghc
# @USAGE: <ghcversion>
# @DESCRIPTION:
# Installs the given ghc version with a lot of side effects.
2018-09-29 07:50:26 +00:00
install_ghc() {
2018-09-30 05:52:40 +00:00
[ -z "$1" ] && die "Internal error: no argument given to install_ghc"
2018-09-29 07:50:26 +00:00
myghcver=$1
2018-09-29 18:57:03 +00:00
inst_location=$(get_ghc_location "$1")
2018-10-30 03:16:08 +00:00
download_url=$(get_download_url "ghc" "${myghcver}")
2018-09-29 15:12:13 +00:00
download_tarball_name=$(basename "${download_url}")
2018-10-17 07:42:38 +00:00
first_install=true
2018-09-29 09:27:01 +00:00
2018-10-30 03:16:08 +00:00
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)"
2018-09-29 18:57:03 +00:00
if ghc_already_installed "${myghcver}" ; then
2018-09-29 08:51:36 +00:00
if ${FORCE} ; then
echo "GHC already installed in ${inst_location}, overwriting!"
else
die "GHC already installed in ${inst_location}, use --force to overwrite"
fi
2018-10-17 07:42:38 +00:00
first_install=false
2018-09-29 08:51:36 +00:00
fi
2018-09-29 07:50:26 +00:00
2018-09-29 14:31:08 +00:00
tmp_dir=$(mktemp -d)
[ -z "${tmp_dir}" ] && die "Failed to create temporary directory"
2018-09-29 08:51:36 +00:00
(
2018-10-28 09:34:19 +00:00
if ${CACHING} ; then
if [ ! -e "${CACHE_LOCATION}/${download_tarball_name}" ] ; then
edo cd "${CACHE_LOCATION}"
download "${download_url}"
fi
edo cd "${tmp_dir}"
unpack "${CACHE_LOCATION}/${download_tarball_name}"
else
edo cd "${tmp_dir}"
download "${download_url}"
unpack "${download_tarball_name}"
fi
2018-09-29 07:50:26 +00:00
2018-09-29 14:31:08 +00:00
edo cd "ghc-${myghcver}"
2018-09-29 07:50:26 +00:00
2018-09-29 18:57:03 +00:00
debug_message "Installing GHC into ${inst_location}"
2018-09-29 08:51:36 +00:00
2018-09-29 14:31:08 +00:00
edo ./configure --prefix="${inst_location}"
2018-10-01 06:18:53 +00:00
edo make install
2018-09-29 09:35:39 +00:00
# clean up
2018-09-29 14:31:08 +00:00
edo cd ..
2018-09-29 15:12:13 +00:00
[ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
[ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
2018-09-29 14:31:08 +00:00
) || {
2018-09-29 15:12:13 +00:00
[ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
[ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
2018-10-17 07:42:38 +00:00
if ${first_install} ; then
[ -e "${inst_location}" ] && rm -r "${inst_location}"
else
warning_message "GHC force installation failed. The install might be broken."
warning_message "Consider running: ghcup rm ${myghcver}"
fi
2018-11-04 08:53:49 +00:00
die "Failed to install, consider updating this script via: ${SCRIPT} upgrade"
2018-09-29 14:31:08 +00:00
}
2018-09-29 08:51:36 +00:00
2018-09-29 13:46:39 +00:00
for f in "${inst_location}"/bin/*-"${myghcver}" ; do
2018-09-29 15:02:00 +00:00
[ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
2018-09-29 13:46:39 +00:00
fn=$(basename "${f}")
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC2046
2018-09-29 18:57:03 +00:00
edo ln $(optionv "-v") -sf ../ghc/"${myghcver}/bin/${fn}" "${BIN_LOCATION}/${fn}"
2018-09-29 09:27:01 +00:00
unset fn
done
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC2046
2018-09-29 18:57:03 +00:00
edo ln $(optionv "-v") -sf ../ghc/"${myghcver}"/bin/runhaskell "${BIN_LOCATION}/runhaskell-${myghcver}"
2018-09-29 09:27:01 +00:00
2018-09-30 06:33:42 +00:00
status_message "Done installing, run \"ghci-${myghcver}\" or set up your current GHC via: ${SCRIPT} set ${myghcver}"
2018-09-29 08:51:36 +00:00
2018-10-28 09:34:19 +00:00
unset myghcver inst_location f download_url download_tarball_name first_install tmp_dir
2018-09-29 08:51:36 +00:00
}
2018-09-30 06:26:59 +00:00
2018-09-30 06:33:42 +00:00
########################
#--[ Subcommand set ]--#
########################
2018-09-30 06:26:59 +00:00
2018-09-29 08:51:36 +00:00
2018-09-30 05:52:01 +00:00
# @FUNCTION: set_ghc
# @USAGE: <ghcversion>
# @DESCRIPTION:
# Sets the current ghc version by creating symlinks.
2018-09-29 08:51:36 +00:00
set_ghc() {
2018-09-30 05:52:40 +00:00
[ -z "$1" ] && die "Internal error: no argument given to set_ghc"
2018-09-29 08:51:36 +00:00
myghcver=$1
2018-09-29 18:57:03 +00:00
inst_location=$(get_ghc_location "$1")
2018-09-29 08:51:36 +00:00
[ -e "${inst_location}" ] || die "GHC ${myghcver} not installed yet, use: ${SCRIPT} install ${myghcver}"
2018-09-29 18:57:03 +00:00
status_message "Setting GHC to ${myghcver}"
2018-09-29 08:51:36 +00:00
2018-09-29 13:46:39 +00:00
for f in "${inst_location}"/bin/*-"${myghcver}" ; do
2018-09-29 15:02:00 +00:00
[ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
2018-09-29 13:46:39 +00:00
source_fn=$(basename "${f}")
target_fn=$(echo "${source_fn}" | sed "s#-${myghcver}##")
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC2046
2018-09-29 18:57:03 +00:00
edo ln $(optionv "-v") -sf ../ghc/"${myghcver}/bin/${source_fn}" "${BIN_LOCATION}/${target_fn}"
2018-09-29 08:51:36 +00:00
unset source_fn target_fn
done
2018-09-29 14:22:54 +00:00
# shellcheck disable=SC2046
2018-09-29 18:57:03 +00:00
edo ln $(optionv "-v") -sf runghc "${BIN_LOCATION}"/runhaskell
2018-09-29 07:50:26 +00:00
2018-09-29 18:57:03 +00:00
status_message "Done, make sure \"${BIN_LOCATION}\" is in your PATH!"
2018-09-29 07:50:26 +00:00
2018-09-29 18:57:03 +00:00
unset myghcver inst_location f
2018-09-29 07:50:26 +00:00
}
2018-09-30 06:26:59 +00:00
2018-11-04 08:53:49 +00:00
############################
#--[ Subcommand upgrade ]--#
############################
2018-09-30 06:26:59 +00:00
2018-09-29 08:51:36 +00:00
2018-11-04 08:53:49 +00:00
# @FUNCTION: upgrade
2018-09-30 05:52:01 +00:00
# @USAGE: <install-location>
# @DESCRIPTION:
# Downloads the latest version of this script and places it into
# the given directory.
2018-11-04 08:53:49 +00:00
upgrade() {
2018-09-29 08:51:36 +00:00
target_location=$1
[ -e "${target_location}" ] || die "Destination \"${target_location}\" does not exist, cannot update script"
2018-09-29 18:57:03 +00:00
status_message "Updating ${SCRIPT}"
2018-09-29 08:51:36 +00:00
(
2018-10-08 14:42:04 +00:00
edo cd "$(mktemp -d)"
2018-10-17 09:04:39 +00:00
download "${SCRIPT_UPDATE_URL}"
2018-10-08 14:42:04 +00:00
edo chmod +x ghcup
edo mv -f ghcup "${target_location}"/ghcup
2018-09-29 17:33:08 +00:00
) || die "failed to install"
2018-09-29 08:51:36 +00:00
2018-09-29 18:57:03 +00:00
status_message "Done, make sure \"${target_location}\" is in your PATH!"
2018-09-29 08:51:36 +00:00
2018-09-30 05:33:04 +00:00
unset target_location
2018-09-29 08:51:36 +00:00
}
2018-09-30 06:26:59 +00:00
#########################
#--[ Subcommand show ]--#
#########################
2018-09-29 15:01:39 +00:00
2018-09-30 05:52:01 +00:00
# @FUNCTION: show_ghc
# @DESCRIPTION:
# Prints the currently installed and selected GHC, in human-friendly
# format.
2018-09-29 15:01:39 +00:00
show_ghc() {
current_ghc=$(show_ghc_installed)
echo "Installed GHCs:"
2018-09-29 18:57:03 +00:00
for i in "${GHC_LOCATION}"/* ; do
2018-10-01 04:28:24 +00:00
if [ -e "${i}" ] ; then
echo " $(basename "${i}")"
else # directory is empty
echo " None"
exit 0
fi
2018-09-29 15:01:39 +00:00
done
if [ -n "${current_ghc}" ] ; then
echo
echo "Current GHC"
echo " ${current_ghc}"
fi
2018-09-29 18:57:03 +00:00
unset current_ghc i
2018-09-29 15:01:39 +00:00
}
2018-09-30 05:52:01 +00:00
# @FUNCTION: show_ghc_installed
# @DESCRIPTION:
# Prints the currently selected GHC only as version string.
# @STDOUT: current GHC version
2018-09-29 15:01:39 +00:00
show_ghc_installed() {
2018-09-29 19:14:56 +00:00
current_ghc="${BIN_LOCATION}/ghc"
2018-10-30 10:06:12 +00:00
real_ghc=$(posix_realpath "${current_ghc}")
2018-09-29 19:14:56 +00:00
if [ -L "${current_ghc}" ] ; then # is symlink
2018-10-30 10:06:12 +00:00
if [ -e "${real_ghc}" ] ; then # exists (posix_realpath was called)
2018-09-29 19:14:56 +00:00
real_ghc="$(basename "${real_ghc}" | sed 's#ghc-##')"
printf "%s" "${real_ghc}"
else # is a broken symlink
red_message "broken symlink"
fi
2018-09-29 15:01:39 +00:00
fi
2018-09-29 19:14:56 +00:00
unset real_ghc current_ghc
2018-09-29 15:01:39 +00:00
}
2018-09-30 06:26:59 +00:00
#######################
#--[ Subcommand rm ]--#
#######################
2018-09-29 18:58:55 +00:00
2018-09-30 05:52:01 +00:00
# @FUNCTION: rm_ghc
# @USAGE: <ghcversion>
# @DESCRIPTION:
# Removes the given GHC version installed by ghcup.
2018-09-29 18:58:55 +00:00
rm_ghc() {
2018-09-30 05:52:40 +00:00
[ -z "$1" ] && die "Internal error: no argument given to rm_ghc"
2018-09-29 18:58:55 +00:00
myghcver=$1
inst_location=$(get_ghc_location "${myghcver}")
[ -z "${myghcver}" ] && die "We are paranoid, ghcver not set"
if ghc_already_installed "${myghcver}" ; then
2018-10-27 08:18:14 +00:00
[ -z "${inst_location}" ] && die "internal error: inst_location empty!"
if ! ${FORCE} ; then
if ! ask_for_confirmation "Really removing ${myghcver}? This will also recursively remove the following directory (please double-check): \"${inst_location}\"" ; then
warning_message "Not removing GHC..."
return 0
fi
fi
2018-09-29 18:58:55 +00:00
for f in "${BIN_LOCATION}"/*-"${myghcver}" ; do
# https://tanguy.ortolo.eu/blog/article113/test-symlink
2018-10-27 06:45:14 +00:00
[ ! -e "${f}" ] && [ ! -h "${f}" ] && {
warning_message "No existing symlinks for ${myghcver} in ${BIN_LOCATION}, skipping"
break
}
2018-09-29 18:58:55 +00:00
edo rm "${f}"
done
2018-10-27 06:45:14 +00:00
2018-09-29 18:58:55 +00:00
edo rm -r "${inst_location}"
2018-09-30 06:41:18 +00:00
2018-09-30 08:11:56 +00:00
status_message "Successfully removed GHC ${myghcver}."
2018-10-27 08:41:23 +00:00
if [ -h "${BIN_LOCATION}/ghc" ] && [ ! -e "${BIN_LOCATION}/ghc" ] ; then
2018-10-27 07:16:57 +00:00
warning_message "Currently active GHC is a dangling symlink, removing..."
# TODO: known_tools is not very robust, but we want to avoid accidentially deleting
# unrelated things (even if those are dangling symlinks)
known_tools="ghc ghci ghc-pkg haddock-ghc runghc runhaskell"
# remove dangling symlinks for ghc, ghci, ...
for t in ${known_tools} ; do
if [ -h "${BIN_LOCATION}/${t}" ] && [ ! -e "${BIN_LOCATION}/${t}" ] ; then
edo rm "${BIN_LOCATION}/${t}"
fi
done
unset t known_tools
warning_message "Done."
warning_message "You may now want to set currently active GHC to a different version via:"
2018-09-30 08:11:56 +00:00
warning_message " ghcup set <ghcver>"
fi
2018-09-29 18:58:55 +00:00
else
warning_message "${myghcver} doesn't appear to be installed, skipping"
fi
unset myghcver inst_location f
}
2018-09-30 08:46:50 +00:00
############################
#--[ Subcommand install ]--#
############################
# @FUNCTION: install_cabal
# @USAGE: <cabalversion>
# @DESCRIPTION:
# Installs the given cabal version.
install_cabal() {
[ -z "$1" ] && die "Internal error: no argument given to install_cabal"
mycabalver=$1
myarch=$(get_arch)
2018-10-05 02:44:23 +00:00
inst_location=$BIN_LOCATION
2018-10-30 03:16:08 +00:00
download_url=$(get_download_url "cabal-install" "${mycabalver}")
2018-10-28 09:34:19 +00:00
download_tarball_name=$(basename "${download_url}")
2018-09-30 08:46:50 +00:00
2018-10-30 03:16:08 +00:00
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}\""
2018-10-31 02:30:38 +00:00
edo mkdir -p "${inst_location}"
2018-09-30 08:46:50 +00:00
2018-10-28 09:34:19 +00:00
tmp_dir=$(mktemp -d)
[ -z "${tmp_dir}" ] && die "Failed to create temporary directory"
2018-09-30 08:46:50 +00:00
(
2018-10-28 09:34:19 +00:00
if ${CACHING} ; then
if [ ! -e "${CACHE_LOCATION}/${download_tarball_name}" ] ; then
edo cd "${CACHE_LOCATION}"
download "${download_url}"
fi
edo cd "${tmp_dir}"
unpack "${CACHE_LOCATION}/${download_tarball_name}"
else
edo cd "${tmp_dir}"
download "${download_url}"
unpack "${download_tarball_name}"
fi
2018-10-05 02:44:23 +00:00
edo mv -f cabal "${inst_location}"/cabal
2018-10-28 09:34:19 +00:00
if [ -e "${tmp_dir}/${download_tarball_name}" ] ; then
rm "${tmp_dir}/${download_tarball_name}"
fi
2018-09-30 08:46:50 +00:00
) || die "Failed to install cabal-install"
2018-10-05 02:44:23 +00:00
status_message "Successfully installed cabal-install into"
status_message " ${BIN_LOCATION}"
status_message ""
status_message "You may want to run the following to get the really latest version:"
2018-09-30 08:46:50 +00:00
status_message " cabal new-install cabal-install"
2018-10-05 02:44:23 +00:00
status_message ""
status_message "And make sure that \"~/.cabal/bin\" comes *before* \"${BIN_LOCATION}\""
status_message "in your PATH!"
2018-09-30 08:46:50 +00:00
2018-10-28 09:34:19 +00:00
unset mycabalver myarch inst_location download_url download_tarball_name tmp_dir
2018-09-30 08:46:50 +00:00
}
2018-09-30 11:39:40 +00:00
# @FUNCTION: compile_ghc
2018-10-16 08:18:08 +00:00
# @USAGE: <ghcversion> <bootstrap-ghc> [build.mk]
2018-09-30 11:39:40 +00:00
# @DESCRIPTION:
# Compile and installs the given GHC version with the
# specified GHC bootstrap version.
2018-10-16 08:18:08 +00:00
# Can additionally take a custom file that will be used
# as build configuration.
2018-09-30 11:39:40 +00:00
compile_ghc() {
{ [ -z "$1" ] || [ -z "$2" ] ;} && die "Internal error: not enough arguments given to compile_ghc"
myghcver=$1
bootstrap_ghc=$2
inst_location=$(get_ghc_location "$1")
download_url="https://downloads.haskell.org/~ghc/${myghcver}/ghc-${myghcver}-src.tar.xz"
download_tarball_name=$(basename "${download_url}")
2018-10-16 08:18:08 +00:00
if [ -n "$3" ] ; then
case "$3" in
/*) build_config=$3 ;;
*) build_config="$(pwd)/$3" ;;
esac
[ -e "${build_config}" ] || die "specified build config \"${build_config}\" file does not exist!"
fi
2018-09-30 11:39:40 +00:00
if ghc_already_installed "${myghcver}" ; then
if ${FORCE} ; then
echo "GHC already installed in ${inst_location}, overwriting!"
else
die "GHC already installed in ${inst_location}, use --force to overwrite"
fi
fi
status_message "Compiling GHC for version ${myghcver} from source"
tmp_dir=$(mktemp -d)
[ -z "${tmp_dir}" ] && die "Failed to create temporary directory"
(
2018-10-28 09:34:19 +00:00
if ${CACHING} ; then
if [ ! -e "${CACHE_LOCATION}/${download_tarball_name}" ] ; then
edo cd "${CACHE_LOCATION}"
download "${download_url}"
fi
edo cd "${tmp_dir}"
unpack "${CACHE_LOCATION}/${download_tarball_name}"
else
edo cd "${tmp_dir}"
download "${download_url}"
unpack "${download_tarball_name}"
fi
2018-09-30 11:39:40 +00:00
edo cd "ghc-${myghcver}"
2018-10-16 08:18:08 +00:00
if [ -n "${build_config}" ] ; then
edo cat "${build_config}" > mk/build.mk
else
cat <<-EOF > mk/build.mk || die
V=0
BUILD_MAN = NO
BUILD_SPHINX_HTML = NO
BUILD_SPHINX_PDF = NO
HADDOCK_DOCS = YES
GhcWithLlvmCodeGen = YES
EOF
fi
2018-09-30 11:39:40 +00:00
edo ./boot
edo ./configure --prefix="${inst_location}" --with-ghc="${bootstrap_ghc}"
edo make -j${JOBS}
edo make install
# clean up
edo cd ..
[ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
[ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
) || {
[ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
[ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
die "Failed to install, consider updating this script via:
2018-11-04 08:53:49 +00:00
${SCRIPT} upgrade
2018-09-30 11:39:40 +00:00
Also check https://ghc.haskell.org/trac/ghc/wiki/Building/Preparation/Linux for build requirements and follow the instructions."
}
for f in "${inst_location}"/bin/*-"${myghcver}" ; do
[ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
fn=$(basename "${f}")
# shellcheck disable=SC2046
edo ln $(optionv "-v") -sf ../ghc/"${myghcver}/bin/${fn}" "${BIN_LOCATION}/${fn}"
unset fn
done
# shellcheck disable=SC2046
edo ln $(optionv "-v") -sf ../ghc/"${myghcver}"/bin/runhaskell "${BIN_LOCATION}/runhaskell-${myghcver}"
status_message "Done installing, run \"ghci-${myghcver}\" or set up your current GHC via: ${SCRIPT} set ${myghcver}"
2018-10-28 09:34:19 +00:00
unset myghcver bootstrap_ghc inst_location f download_url download_tarball_name tmp_dir
2018-09-30 11:39:40 +00:00
}
2018-09-30 08:46:50 +00:00
2018-10-17 10:19:43 +00:00
###############################
#--[ Subcommand debug-info ]--#
###############################
# @FUNCTION: print_debug_info
# @DESCRIPTION:
# Print debug info (e.g. detected system/distro).
print_debug_info() {
echo "Script version: ${VERSION}"
echo
echo "Script variables:"
echo " GHC install location: ${GHC_LOCATION}"
echo " Binary install location: ${BIN_LOCATION}"
2018-10-28 09:36:10 +00:00
echo " Tarball cache location: ${CACHE_LOCATION}"
2018-10-17 10:19:43 +00:00
echo " Downloader: ${DOWNLOADER} ${DOWNLOADER_OPTS} <url>"
echo " Script update url: ${SCRIPT_UPDATE_URL}"
echo " GHC download baseurl: ${GHC_DOWNLOAD_BASEURL}"
2018-10-30 03:16:08 +00:00
echo " Meta download url ${META_DOWNLOAD_URL}"
2018-11-06 05:32:55 +00:00
echo " Meta download format ${META_DOWNLOAD_FORMAT}"
2018-10-30 03:16:08 +00:00
echo " Meta version url ${META_VERSION_URL}"
echo " Meta version format ${META_VERSION_FORMAT}"
2018-10-17 10:19:43 +00:00
echo
echo "Detected system information:"
echo " Architecture: $(get_arch)"
echo " Distribution: $(get_distro_name)"
echo " Distro version: $(get_distro_ver)"
}
2018-09-29 18:58:55 +00:00
2018-09-29 07:50:26 +00:00
2018-10-30 03:16:08 +00:00
#########################
#--[ Subcommand list ]--#
#########################
# @FUNCTION: list
# @USAGE: [tool]
# @DESCRIPTION:
# List available tools and their versions from upstream.
list() {
mytool=$1
2018-11-06 05:32:55 +00:00
meta_file="$(get_meta_version_file)"
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
echo "Available upstream versions:"
echo
if [ -z "${mytool}" ] ; then
awk "
NF {
if (\$1 != \"#\") {
2018-11-06 14:01:02 +00:00
if (\$1 == \"cabal-install\") {
print \$1 \"\\t\" \$2 \"\\t\" \$3
} else {
print \$1 \"\\t\\t\" \$2 \"\\t\" \$3
}
2018-11-06 05:32:55 +00:00
}
}" "${meta_file}" || die "awk failed!"
else
awk "
NF {
if (\$1 == \"${mytool}\") {
2018-11-06 14:01:02 +00:00
print \$1 \"\\t\" \$2 \"\\t\" \$3
2018-11-06 05:32:55 +00:00
}
}" "${meta_file}" || die "awk failed!"
fi
2018-10-30 03:16:08 +00:00
2018-11-06 05:32:55 +00:00
unset mytool meta_file
2018-10-30 03:16:08 +00:00
}
2018-09-30 06:26:59 +00:00
#######################
#--[ Sanity checks ]--#
#######################
2018-09-29 07:50:26 +00:00
2018-09-29 08:51:36 +00:00
if [ -z "$HOME" ] ; then
die "HOME env not set, cannot operate"
fi
2018-10-30 03:16:08 +00:00
2018-09-30 06:26:59 +00:00
2018-10-18 15:56:55 +00:00
2018-09-30 06:26:59 +00:00
##############################################
#--[ Command line parsing and entry point ]--#
##############################################
2018-11-04 10:20:50 +00:00
if ! is_sourced ; then
2018-09-30 06:26:59 +00:00
2018-11-04 10:20:50 +00:00
[ $# -lt 1 ] && usage
2018-09-29 08:51:36 +00:00
2018-11-04 10:20:50 +00:00
while [ $# -gt 0 ] ; do
case $1 in
-v|--verbose)
VERBOSE=true
shift 1
if [ $# -lt 1 ] ; then
usage
fi
;;
-V|--version)
printf "%s" "${VERSION}"
exit 0;;
-h|--help)
usage;;
-w|--wget)
DOWNLOADER="wget"
DOWNLOADER_OPTS=""
shift 1
if [ $# -lt 1 ] ; then
usage
fi
;;
-c|--cache)
CACHING=true
shift 1
if [ $# -lt 1 ] ; then
usage
fi
;;
2018-11-06 05:32:55 +00:00
*) ## startup tasks ##
edo mkdir -p "${INSTALL_BASE}"
edo mkdir -p "${BIN_LOCATION}"
edo mkdir -p "${CACHE_LOCATION}"
# clean up old meta files
if [ -e "${CACHE_LOCATION}/$(basename "${META_VERSION_URL}")" ] ; then
edo rm "${CACHE_LOCATION}/$(basename "${META_VERSION_URL}")"
fi
if [ -e "${CACHE_LOCATION}/$(basename "${META_DOWNLOAD_URL}")" ] ; then
edo rm "${CACHE_LOCATION}/$(basename "${META_DOWNLOAD_URL}")"
fi
2018-11-04 10:20:50 +00:00
# check for available commands
2018-11-04 10:21:23 +00:00
missing_commands="$(check_required_commands ${DOWNLOADER})"
if [ -n "${missing_commands}" ] ; then
die "Following commands are required, but missing, please install: ${missing_commands}"
fi
unset missing_commands
2018-11-04 10:20:50 +00:00
case $1 in
install)
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) install_usage;;
-f|--force) FORCE=true
shift 1;;
*) GHC_VER=$1
break;;
esac
done
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
2018-11-06 05:32:55 +00:00
# could be a version or a tag, let's check
if array_contains "${GHC_VER}" "$(known_tool_versions "ghc")" ; then
install_ghc "${GHC_VER}"
elif array_contains "${GHC_VER}" "$(known_tool_tags "ghc")" ; then
install_ghc "$(get_tool_ver_from_tag "ghc" "${GHC_VER}")"
else
die "\"${GHC_VER}\" is not a known version or tag!"
fi
2018-10-30 03:16:08 +00:00
fi
2018-11-04 10:20:50 +00:00
break;;
set)
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) set_usage;;
*) GHC_VER=$1
break;;
esac
done
[ "${GHC_VER}" ] || set_usage
set_ghc "${GHC_VER}"
break;;
2018-11-04 08:53:49 +00:00
upgrade)
2018-11-04 10:20:50 +00:00
shift 1
while [ $# -gt 0 ] ; do
case $1 in
2018-11-04 08:53:49 +00:00
-h|--help) upgrade_usage;;
2018-11-04 10:20:50 +00:00
*) TARGET_LOCATION=$1
break;;
esac
done
if [ "${TARGET_LOCATION}" ] ; then
2018-11-04 08:53:49 +00:00
upgrade "${TARGET_LOCATION}"
2018-11-04 10:20:50 +00:00
else
2018-11-04 08:53:49 +00:00
upgrade "$(dirname "$(posix_realpath "${SOURCE}")")"
2018-10-30 03:16:08 +00:00
fi
2018-11-04 10:20:50 +00:00
break;;
show)
SHOW_INSTALLED=false
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) show_usage;;
-i|--installed) SHOW_INSTALLED=true
break;;
*) show_usage;;
esac
done
if ${SHOW_INSTALLED} ; then
show_ghc_installed
else
show_ghc
fi
break;;
rm)
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) rm_usage;;
-f|--force) FORCE=true
shift 1;;
*) GHC_VER=$1
break;;
esac
done
[ "${GHC_VER}" ] || rm_usage
rm_ghc "${GHC_VER}"
break;;
install-cabal)
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) install_cabal_usage;;
-f|--force) FORCE=true
shift 1;;
*) CABAL_VER=$1
break;;
esac
done
if [ -n "${CABAL_VER}" ] ; then
2018-11-06 05:32:55 +00:00
# could be a version or a tag, let's check
if array_contains "${CABAL_VER}" "$(known_tool_versions "cabal-install")" ; then
install_cabal "${CABAL_VER}"
elif array_contains "${CABAL_VER}" "$(known_tool_tags "cabal-install")" ; then
install_cabal "$(get_tool_ver_from_tag "cabal-install" "${CABAL_VER}")"
else
die "\"${CABAL_VER}\" is not a known version or tag!"
fi
2018-11-04 10:20:50 +00:00
else
_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)
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) compile_usage;;
-f|--force) FORCE=true
shift 1;;
-j|--jobs) JOBS=$2
shift 2;;
-c|--build-config) BUILD_CONFIG=$2
shift 2;;
*) GHC_VER=$1
BOOTSTRAP_GHC=$2
break;;
esac
done
[ "${GHC_VER}" ] || compile_usage
[ "${BOOTSTRAP_GHC}" ] || compile_usage
compile_ghc "${GHC_VER}" "${BOOTSTRAP_GHC}" "${BUILD_CONFIG}"
break;;
debug-info)
shift 1
while [ $# -gt 0 ] ; do
case $1 in
-h|--help) debug_info_usage;;
*) debug_info_usage;;
esac
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
2018-10-30 03:16:08 +00:00
break;;
2018-11-04 10:20:50 +00:00
esac
done
fi # is_sourced
2018-09-29 07:50:26 +00:00
2018-10-02 17:41:24 +00:00
# vim: tabstop=4 shiftwidth=4 expandtab