#! /bin/sh
# Copyright (c) 2011 Evax Software <contact(at)evax(dot)org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
ERLANG_DOWNLOAD_URL=http://www.erlang.org/download
KERL_BASE_DIR=$HOME/.kerl
KERL_CONFIG=$HOME/.kerlrc
KERL_DOWNLOAD_DIR=$KERL_BASE_DIR/archives
KERL_BUILD_DIR=$KERL_BASE_DIR/builds
if [ -n "$KERL_CONFIGURE_OPTIONS" ]; then
_KCO="$KERL_CONFIGURE_OPTIONS"
fi
KERL_CONFIGURE_OPTIONS=
KERL_DISABLE_AGNER=
KERL_SASL_STARTUP=
# ensure the base dir exsists
mkdir -p $KERL_BASE_DIR
# source the config file if available
if [ -f "$KERL_CONFIG" ]; then . "$KERL_CONFIG"; fi
if [ -n "$_KCO" ]; then
KERL_CONFIGURE_OPTIONS="$_KCO"
fi
if [ -z "$KERL_SASL_STARTUP" ]; then
INSTALL_OPT=-minimal
else
INSTALL_OPT=-sasl
fi
KERL_SYSTEM=`uname -s`
case "$KERL_SYSTEM" in
Darwin)
MD5SUM="openssl md5"
MD5SUM_FIELD=2
SED_OPT=-E
;;
*)
MD5SUM=md5sum
MD5SUM_FIELD=1
SED_OPT=-r
;;
esac
usage()
{
echo "kerl: build and install Erlang/OTP"
echo "usage: $0 <command> [options ...]"
echo "\n <command> Command to be executed\n"
echo "Valid commands are:"
echo " build Build specified release"
echo " install Install the specified release at the given location"
echo " update Update agner or the list of available releases from erlang.org"
echo " list List releases, builds and installations"
echo " delete Delete builds and installations"
echo " active Print the path of the active installation"
echo " status Print available builds and installations"
exit 1
}
if [ $# -eq 0 ]; then usage; fi
get_releases()
{
curl -s $ERLANG_DOWNLOAD_URL/ | \
sed $SED_OPT -e 's/^.*>otp_src_(R1[-1234567890ABCD]+)\.tar\.gz<.*$/\1/' \
-e '/^R/!d'
}
update_checksum_file()
{
echo "Getting the checksum file from erlang.org..."
curl $ERLANG_DOWNLOAD_URL/MD5 > "$KERL_DOWNLOAD_DIR/MD5" || exit 1
}
ensure_checksum_file()
{
if [ ! -f "$KERL_DOWNLOAD_DIR/MD5" ]; then
update_checksum_file
fi
}
check_releases()
{
if [ ! -f "$KERL_BASE_DIR/otp_releases" ]; then
echo "Getting the available releases from erlang.org..."
get_releases > "$KERL_BASE_DIR/otp_releases"
fi
}
# c.f. agner issue #98
# https://github.com/agner/agner/issues/#issue/98
KERL_NO_AGNER_SUPPORT="R10B-0 R10B-2 R10B-3 R10B-4 R10B-5 R10B-6 R10B-7
R10B-8 R10B-9 R11B-0 R11B-1 R11B-2 R11B-3 R11B-4 R11B-5 R12B-0 R12B-1
R12B-2 R12B-3 R12B-4 R12B-5 R13A R13B R13B01 R13B02 R13B03 R13B04"
agner_support()
{
if [ -z "$KERL_DISABLE_AGNER" ]; then
for v in $KERL_NO_AGNER_SUPPORT; do
if [ "$v" = "$1" ]; then
return 1
fi
done
return 0
fi
return 1
}
is_valid_release()
{
check_releases
for rel in `cat $KERL_BASE_DIR/otp_releases`; do
if [ "$1" = "$rel" ]; then
return 0
fi
done
return 1
}
assert_valid_release()
{
if ! is_valid_release $1; then
echo "$1 is not a valid Erlang/OTP release"
exit 1
fi
return 0
}
get_release_from_name()
{
if [ -f "$KERL_BASE_DIR/otp_builds" ]; then
for l in `cat "$KERL_BASE_DIR/otp_builds"`; do
rel=`echo $l | cut -d "," -f 1`
name=`echo $l | cut -d "," -f 2`
if [ "$name" = "$1" ]; then
echo "$rel"
return 0
fi
done
fi
return 1
}
is_valid_installation()
{
if [ -f "$1/activate" ]; then
return 0
fi
return 1
}
do_update_agner()
{
rel=`get_release_from_name $1`
if [ "$?" -eq 1 ]; then
echo "Unknown build name $1"
exit 1
fi
TARGET="$KERL_BUILD_DIR/$1/release_$rel"
cd "$KERL_BUILD_DIR/$1/agner_$rel" && \
AGNER_BIN="$TARGET/bin" AGNER_EXACT_PREFIX="$TARGET/lib" \
./agner install agner > /dev/null 2>&1
if [ "$?" -eq 1 ]; then
return 1
fi
return 0
}
do_build()
{
assert_valid_release $1
if [ -f "$KERL_BASE_DIR/otp_builds" ]; then
for l in `cat "$KERL_BASE_DIR/otp_builds"`; do
name=`echo $l | cut -d " " -f 1`
if [ "$name" = "$2" ]; then
echo "There's already a build named $2"
exit 1
fi
done
fi
FILENAME=otp_src_$1.tar.gz
if [ ! -f "$KERL_DOWNLOAD_DIR/$FILENAME" ]; then
echo "Downloading $FILENAME to $KERL_DOWNLOAD_DIR"
mkdir -p "$KERL_DOWNLOAD_DIR"
curl $ERLANG_DOWNLOAD_URL/$FILENAME > "$KERL_DOWNLOAD_DIR/$FILENAME"
update_checksum_file
fi
ensure_checksum_file
echo "Verifying archive checksum..."
SUM=`$MD5SUM "$KERL_DOWNLOAD_DIR/$FILENAME" | cut -d " " -f $MD5SUM_FIELD`
ORIG_SUM=`grep $FILENAME "$KERL_DOWNLOAD_DIR/MD5" | cut -d " " -f 2`
if [ "$SUM" != "$ORIG_SUM" ]; then
echo "Checksum error, check the files in $KERL_DOWNLOAD_DIR"
exit 1
fi
echo "Checksum verified ($SUM)"
mkdir -p "$KERL_BUILD_DIR/$2"
if [ ! -d "$KERL_BUILD_DIR/$2/otp_src_$1" ]; then
echo "Extracting source code"
cd "$KERL_BUILD_DIR/$2" && tar xfz "$KERL_DOWNLOAD_DIR/$FILENAME"
fi
echo "Building Erlang/OTP $1 ($2), please wait..."
cd "$KERL_BUILD_DIR/$2/otp_src_$1"
./otp_build setup -a $KERL_CONFIGURE_OPTIONS > "$KERL_BUILD_DIR/$2/otp_build_$1.log" 2>&1
if [ "$?" -eq 1 ]; then
echo "Build failed, see $KERL_BUILD_DIR/$2/otp_build_$1.log"
list_remove builds "$1 $2"
exit 1
fi
rm -f "$KERL_BUILD_DIR/$2/otp_build_$1.log"
./otp_build release -a "$KERL_BUILD_DIR/$2/release_$1" > /dev/null 2>&1
cd "$KERL_BUILD_DIR/$2/release_$1"
./Install $INSTALL_OPT "$KERL_BUILD_DIR/$2/release_$1" > /dev/null 2>&1
echo "Erlang/OTP $1 ($2) has been successfully built"
list_add builds "$1,$2"
if agner_support $1; then
echo "Fetching and building agner..."
cd "$KERL_BUILD_DIR/$2" && \
git clone https://github.com/agner/agner.git agner_$1 > /dev/null 2>&1 && \
cd agner_$1 && \
PATH="$KERL_BUILD_DIR/$2/otp_src_$1/bin:$PATH" make > /dev/null 2>&1 && \
do_update_agner $2
if [ "$?" -eq 1 ]; then
echo "Agner install failed"; exit 1
fi
echo "Agner has been successfully built"
fi
}
do_install()
{
rel=`get_release_from_name $1`
if [ "$?" -eq 1 ]; then
echo "No build named $1"
exit 1
fi
mkdir -p "$2"
if [ ! -d "$2" ]; then
echo "Destination is not a directory"
exit 1
fi
absdir=`cd "$2" && pwd`
echo "Installing Erlang/OTP $rel ($1) in $absdir..."
cd "$KERL_BUILD_DIR/$1/otp_src_$rel"
./otp_build release -a "$absdir" > /dev/null 2>&1 &&
cd "$absdir" && ./Install $INSTALL_OPT "$absdir" > /dev/null 2>&1
if [ $? -eq 1 ]; then
echo "Couldn't install Erlang/OTP $rel ($1) in $absdir"
exit 1
fi
list_add installations "$1 $absdir";
cat <<ACTIVATE > "$absdir/activate"
# credits to virtualenv
kerl_deactivate()
{
if [ -n "\$_KERL_SAVED_PATH" ]; then
PATH="\$_KERL_SAVED_PATH"
export PATH
unset _KERL_SAVED_PATH
fi
if [ -n "\$_KERL_SAVED_AGNER_BIN" ]; then
AGNER_BIN="\$_KERL_SAVED_AGNER_BIN"
export AGNER_BIN
unset _KERL_SAVED_AGNER_BIN
fi
if [ -n "\$_KERL_SAVED_AGNER_EXACT_PREFIX" ]; then
AGNER_EXACT_PREFIX="\$_KERL_SAVED_AGNER_EXACT_PREFIX"
export AGNER_EXACT_PREFIX
unset _KERL_SAVED_AGNER_EXACT_PREFIX
fi
if [ -n "\$BASH" -o -n "\$ZSH_VERSION" ]; then
hash -r
fi
if [ ! "\$1" = "nondestructive" ]; then
unset -f kerl_deactivate
fi
}
kerl_deactivate nondestructive
_KERL_SAVED_PATH="\$PATH"
export _KERL_SAVED_PATH
_KERL_SAVED_AGNER_BIN="\$AGNER_BIN"
export _KERL_SAVED_AGNER_BIN
_KERL_SAVED_AGNER_EXACT_PREFIX="\$AGNER_EXACT_PREFIX"
export _KERL_SAVED_AGNER_EXACT_PREFIX
PATH="$absdir/bin:\$PATH"
export PATH
AGNER_BIN="$absdir/bin"
export AGNER_BIN
AGNER_EXACT_PREFIX="$absdir/lib"
export AGNER_EXACT_PREFIX
if [ -n "\$BASH" -o -n "\$ZSH_VERSION" ]; then
hash -r
fi
ACTIVATE
if agner_support $1; then
echo "Installing agner in $absdir..."
cp -R "$KERL_BUILD_DIR/$1/release_$rel/lib/agner-@master" "$absdir/lib/"
cd "$absdir/bin" && ln -s "$absdir/lib/agner-@master/bin/agner" agner
if [ -n "$KERL_AGNER_AUTOINSTALL" ]; then
for i in $KERL_AGNER_AUTOINSTALL; do
echo "Autoinstalling $i"
agner install $i > /dev/null 2>&1
done
fi
fi
echo "You can activate this installation running the following command:"
echo ". $absdir/activate"
echo "Later on, you can leave the installation typing:"
echo "kerl_deactivate"
}
list_print()
{
if [ -f $KERL_BASE_DIR/otp_$1 ]; then
if [ "`cat "$KERL_BASE_DIR/otp_$1" | wc -l`" != "0" ]; then
if [ -z "$2" ]; then
cat $KERL_BASE_DIR/otp_$1
else
echo `cat $KERL_BASE_DIR/otp_$1`
fi
return 0
fi
fi
echo "There are no $1 available"
}
list_add()
{
if [ -f $KERL_BASE_DIR/otp_$1 ]; then
grep "$2" $KERL_BASE_DIR/otp_$1 > /dev/null 2>&1 || \
echo "$2" >> $KERL_BASE_DIR/otp_$1
else
echo "$2" > $KERL_BASE_DIR/otp_$1
fi
}
list_remove()
{
if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
sed $SED_OPT -i -e "/^.*$2$/d" "$KERL_BASE_DIR/otp_$1"
fi
}
list_has()
{
if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
grep $2 "$KERL_BASE_DIR/otp_$1" > /dev/null 2>&1 && return 0
fi
return 1
}
list_usage()
{
echo "usage: $0 list <releases|builds|installations>"
}
delete_usage()
{
echo "usage: $0 delete <build|installation> <build_name or path>"
}
update_usage()
{
echo "usage: $0 $1 <releases|agner>"
}
do_active()
{
if [ -n "$_KERL_SAVED_PATH" ]; then
echo "The current active installation is:"
echo `echo $PATH | cut -d ":" -f 1 | head -c -4`
return 0
else
echo "No Erlang/OTP kerl installation is currently active"
return 1
fi
}
case "$1" in
build)
if [ $# -ne 3 ]; then
echo "usage: $0 $1 <release> <build_name>"
exit 1
fi
do_build $2 $3
;;
install)
if [ $# -lt 2 ]; then
echo "usage: $0 $1 <build_name> [directory]"
exit 1
fi
if [ $# -eq 3 ]; then
do_install $2 "$3"
else
do_install $2 .
fi
;;
update)
if [ $# -lt 2 ]; then
update_usage
exit 1
fi
case "$2" in
releases)
rm -f "$KERL_BASE_DIR/otp_releases"
check_releases
echo "The available releases are:"
list_print releases spaces
;;
agner)
if [ $# -ne 3 ]; then
echo "usage: $0 $1 $2 <build_name>"
exit 1
fi
if agner_support $3; then
echo "Updating agner for build $3..."
if do_update_agner $3; then
echo "agner has been updated successfully"
else
echo "failed to update agner"
exit 1
fi
fi
;;
*)
update_usage
exit 1
;;
esac
;;
list)
if [ $# -ne 2 ]; then
list_usage
exit 1
fi
case "$2" in
releases)
check_releases
list_print $2 space
echo "Run \"$0 update releases\" to update this list from erlang.org"
;;
builds)
list_print $2
;;
installations)
list_print $2
;;
*)
echo "Cannot list $2"
list_usage
exit 1
;;
esac
;;
delete)
if [ $# -ne 3 ]; then
delete_usage
exit 1
fi
case "$2" in
build)
rel=`get_release_from_name $3`
if [ -d "$KERL_BUILD_DIR/$3" ]; then
rm -Rf "$KERL_BUILD_DIR/$3"
list_remove $2s "$rel,$3"
echo "The $3 build has been deleted"
else
echo "No build named $3"
exit 1
fi
;;
installation)
if is_valid_installation "$3"; then
rm -Rf "$3"
escaped=`echo "$3" | sed $SED_OPT -e 's#/$##' -e 's#\/#\\\/#g'`
list_remove $2s "$escaped"
echo "The installation in $3 has been deleted"
else
echo "$3 is not a kerl-managed Erlang/OTP installation"
exit 1
fi
;;
*)
echo "Cannot delete $2"
delete_usage
exit 1
;;
esac
;;
active)
if ! do_active; then
exit 1;
fi
;;
status)
echo "Available builds:"
list_print builds
echo "----------"
echo "Available installations:"
list_print installations
echo "----------"
echo "Currently active installation:"
do_active
exit 0
;;
*)
echo "unkwnown command: $1"; usage; exit 1
;;
esac