#!/bin/sh
#
# %CopyrightBegin%
#
# Copyright Ericsson AB 2014. All Rights Reserved.
#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
#
# %CopyrightEnd%
#

version="1.0.1"

force=
lib_path=
orig_dir=
sdir=
idir="/broken/path/here"
cleanup=no
install_docs=yes

invalid_src="does not seem to be a valid OTP source tree"
not_built="Source in has not been built"
doc_not_built="Documentation has not been built. Either build the
documentation and re-run 'otp_patch_apply', or re-run 'otp_patch_apply'
with the '-n' switch."

print_usage()
{
    cat <<EOF
otp_patch_apply -s <Dir> -i <Dir> [-l <Dir>] [-c] [-f] [-h] [-n] [-v] \\
                <App1> [... <AppN>]

  -s <Dir>  -- OTP source directory that contain build results.
  -i <Dir>  -- OTP installation directory to patch.
  -l <Dir>  -- Alternative OTP source library directory path(s) containing
               build results of OTP applications. Multiple paths should be
               colon separated.
  -c        -- Cleanup (remove) old versions of applications patched
               in the installation.
  -f        -- Force patch of application(s) even though dependencies are
               not fullfilled.
  -h        -- Print this help then exit.
  -n        -- Do not install documentation.
  -v        -- Print version then exit.
  <AppX>    -- Application to patch.

Environment Variable:
  ERL_LIBS  -- Alternative OTP source library directory path(s) containing
               build results of OTP applications. Multiple paths should be
               colon separated.

NOTE:
  * Complete build environment is required while running otp_patch_apply.
  * Before applying a patch you need to build all of OTP in the source
    directory.
  * All source directories identified by -s and -l should contain build
    results of OTP applications.

Version: $version

EOF

}

error()
{
    echo "ERROR:" "$@" 1>&2
    exit 1
}

usage_error()
{
    echo "ERROR:" "$@" 1>&2
    echo "" 1>&2
    print_usage 1>&2
    exit 1
}

usage()
{
    print_usage
    exit 0
}

alt_lib_path()
{
    app=$1
    save_ifs=$IFS
    IFS=:

    cd "$orig_dir" || error "Cannot change directory to $orig_dir"

    for lib in $lib_path; do
	# Want absolute path
	case "$lib" in
	    /*)
		;;
	    *)
		cd "$lib" || error "Cannot change directory to $lib"
		lib=`pwd`
		cd "$orig_dir" || error "Cannot change directory to $orig_dir"
	esac
	if [ -d "$lib/$app" ]; then
	    echo "$lib/$app"
	    IFS=$save_ifs
	    return 0
	fi
    done

    IFS=$save_ifs

    return 1
}

prog_in_mod_path()
{
    chk_path="/bin:$PATH"
    PROG=$1
    save_ifs=$IFS
    IFS=:
    if [ "X$TARGET" = "Xwin32" ]; then
	for p in $chk_path; do
	    if [ -f "$p/$PROG.exe" ]; then
		IFS=$save_ifs
		echo "$p/$PROG.exe"
		return 0
	    fi
	done
    else
	for p in $chk_path; do
	    if [ -x "$p/$PROG" ]; then
		IFS=$save_ifs
		echo "$p/$PROG"
		return 0
	    fi
	done
    fi
    IFS=$save_ifs
    return 1
}

find_prog()
{
    prog_in_mod_path "$1"
    if [ $? -ne 0 ]; then
	echo "$1"
    fi
    return 0
}

# Parse arguments

while [ $# -gt 0 ]; do
    case "$1" in
    	"-s")
	    shift
	    if [ ! $# -gt 0 ]; then
		usage_error "Missing OTP source directory"
	    fi
	    sdir="$1";;
    	"-i")
	    shift
	    if [ ! $# -gt 0 ]; then
		usage_error "Missing OTP install directory"
	    fi
	    idir="$1";;
    	"-l")
	    shift
	    if [ ! $# -gt 0 ]; then
		usage_error "Missing OTP library directory"
	    fi
	    if [ "x$lib_path" = "x" ]; then
		lib_path="$1"
	    else
		lib_path="$lib_path:$1"
	    fi;;
    	"-f")
	    force="-force";;
    	"-c")
	    cleanup=yes;;
	"-h")
	    usage;;
	"-n")
	    install_docs=no;;
	"-v")
	    echo "otp_patch_apply version $version"
	    exit 0;;
	*)
	    app="$1"
	    applications="$applications $app";;
    esac
    shift
done

# Check that we got mandatory arguments
test "x$sdir" != "x" || usage_error "Missing OTP source directory"
test "x$idir" != "x" || usage_error "Missing OTP install directory"
test "x$applications" != "x" || usage_error "Missing applications"

orig_dir=`pwd`

# Check that the source directory seems sane
cd "$sdir" 2>/dev/null || error "Cannot change directory to $sdir"

# Want absolute path
case "$sdir" in
    /*) ;;
    *) sdir=`pwd`;;
esac

export ERL_TOP="$sdir"
test -f "$sdir/otp_build" || error "$ERL_TOP" $invalid_src
test -f "$sdir/OTP_VERSION" || error "$ERL_TOP" $invalid_src
test -f "$sdir/otp_versions.table" || error "$ERL_TOP" $invalid_src
test -f "$sdir/erts/autoconf/config.guess" || error "$ERL_TOP" $invalid_src
test -f "$sdir/make/verify_runtime_dependencies" || error "$ERL_TOP" $invalid_src
test -x "$sdir/bootstrap/bin/erl" || error $not_built
test -x "$sdir/bootstrap/bin/erlc" || error $not_built
test -x "$sdir/bootstrap/bin/escript" || error $not_built
test -f "$sdir/make/otp_built" || error $not_built

if [ $install_docs = yes ]; then
    test -f "$sdir/make/otp_doc_built" || usage_error $doc_not_built
fi

otp_rel=`sed 's|\([0-9]*\).*|\1|' < $ERL_TOP/OTP_VERSION` || \
    error "Failed to read $ERL_TOP/OTP_VERSION"

case "$otp_rel" in
    1[7-9]|[2-9][0-9]) ;; # ok; release 17-99
    *) error "Invalid OTP release: $otp_rel";;
esac

export PATH="$ERL_TOP/bootstrap/bin:$PATH"
erlc="$ERL_TOP/bootstrap/bin/erlc"
erl="$ERL_TOP/bootstrap/bin/erl"

erl_otp_rel=`$erl -noshell -noinput -eval "io:format(\"~s~n\", [erlang:system_info(otp_release)]), erlang:halt(0)"` || \
    error "Failed to execute: $erl"

test "$otp_rel" = "$erl_otp_rel" || error "Inconsistent source: $sdir"

app_dirs=
for app in $applications; do
    case "$app" in
	"erts")
	    dir="$ERL_TOP/erts";;
	*)
	    dir="$ERL_TOP/lib/$app";;
    esac
    if [ ! -d "$dir" ]; then
	dir=`alt_lib_path "$app"`
	if [ $? -ne 0 ]; then
	    error "Application missing in source: $app"
	fi
    fi
    app_dirs="$app_dirs $dir"
done

cd "$orig_dir" 2>/dev/null || error "Cannot change directory to $orig_dir"

# Check that the install directory seems sane
cd "$idir" 2>/dev/null || error "Cannot change directory to $idir"

# Want absolute path
case "$idir" in
    /*) ;;
    *) idir=`pwd`;;
esac

test -d "$idir/releases/$otp_rel" || \
    error "No OTP-$otp_rel installation present in $idir"

cd "$ERL_TOP" 2>/dev/null || error "Cannot change directory to $ERL_TOP"

# Some tools we use
rm=`find_prog rm`
rmdir=`find_prog rmdir`
cp=`find_prog cp`
mv=`find_prog mv`
mkdir=`find_prog mkdir`

# Setup build stuff
if [ "x$TARGET" = "x" ]; then
    TARGET=`$ERL_TOP/erts/autoconf/config.guess`
fi
BUILDSYS=$TARGET
if [ -z "$MAKE" ]; then
    case $TARGET in
	win32)
	    MAKE=make;;
	*)
	    prog_in_mod_path gmake >/dev/null
	    if [ $? -eq 0 ]; then
		MAKE=gmake
	    else
		MAKE=make
	    fi;;
    esac
fi
if [ X`$MAKE is_cross_configured` = Xyes ]; then
    TARGET=`$MAKE target_configured`
elif [ "x$OVERRIDE_TARGET" != "x" -a "x$OVERRIDE_TARGET" != "xwin32" ]; then
    TARGET=$OVERRIDE_TARGET
fi

# Check for cleanup
inst_app_vers="$idir/releases/$otp_rel/installed_application_versions"
rm_app_vers=
if [ $cleanup = yes ]; then
    $mv "$inst_app_vers" "${inst_app_vers}.save" || \
	error "Failed to save $inst_app_vers"
    for app in $applications; do
	tmp=`grep "$app-*" "${inst_app_vers}.save"`
	rm_app_vers="$rm_app_vers $tmp"
    done
    $cp "${inst_app_vers}.save" "$inst_app_vers"
    for rm_app_ver in $rm_app_vers; do
	$cp "$inst_app_vers" "${inst_app_vers}.tmp"
	grep -v $rm_app_ver "${inst_app_vers}.tmp" > "$inst_app_vers"
    done
    $rm -f "${inst_app_vers}.tmp"
fi

# Verify runtime dependencies
$ERL_TOP/make/verify_runtime_dependencies -release "$otp_rel" \
    -source "$ERL_TOP" -target "$idir" $force $applications || {
    test ! -f  "${inst_app_vers}.save" || \
	$mv "${inst_app_vers}.save" "$inst_app_vers"
    exit 1
}

# Update OTP_VERSION in installation
otp_version=`cat "$idir/releases/$otp_rel/OTP_VERSION"` || {
    test ! -f  "${inst_app_vers}.save" || \
	$mv "${inst_app_vers}.save" "$inst_app_vers"
    error "Not able to read $idir/releases/$otp_rel/OTP_VERSION"
}

{
    echo "$otp_version" | sed "s|^\([^\*]*\)\**|\1\*\*|g" > \
	"$idir/releases/$otp_rel/OTP_VERSION"
} 2>/dev/null || {
    test ! -f  "${inst_app_vers}.save" || \
	$mv "${inst_app_vers}.save" "$inst_app_vers"
    error "Not able to update $idir/releases/$otp_rel/OTP_VERSION"
}

# Do actual cleanup
if [ "x$rm_app_vers" != "x" ]; then
    for app_ver in $rm_app_vers; do
	case x"$app_ver" in
	    x)
		;;
	    xerts-*)
		$rm -rf "$idir/$app_ver" ;;
	    x*)
		$rm -rf "$idir/lib/$app_ver" ;;
	esac
    done
    $rm -f "${inst_app_vers}.save"
fi

# Install application from built source
for app_dir in $app_dirs; do
    (cd "$app_dir" && \
	$MAKE MAKE="$MAKE" TARGET=$TARGET RELEASE_ROOT="$idir" \
	RELEASE_PATH="$idir" TESTROOT="$idir" release) || exit 1
done

if [ $install_docs = yes ]; then
# Documentation have been built and should be installed

    for app_dir in $app_dirs; do
	(cd "$app_dir" && \
	    $MAKE MAKE="$MAKE" RELEASE_ROOT="$idir" RELEASE_PATH="$idir" \
	    TESTROOT="$idir" release_docs) || exit 1
    done

    (cd "$sdir/system/doc/top" && $MAKE clean)

    (cd "$sdir/system/doc/top" && \
	$MAKE MAKE="$MAKE" RELEASE_ROOT="$idir" RELEASE_PATH="$idir" \
	TESTROOT="$idir" release_docs) || exit 1

    echo ""
    echo "*"
    echo "* NOTE! In order to update pre-formatted man pages you"
    echo "*       need to run the 'Install' script located in:"
    echo "*       $idir"
    echo "*"
fi 

# If erts, kernel, stdlib or sasl is included, find versions
for app in $applications; do
    case "$app" in
	erts)
	    erts_vsn=`grep '^VSN' erts/vsn.mk | sed "s|^VSN.*=[^0-9]*\([0-9].*\)$|\1|g"`
	    update_rel=true;;
	kernel)
	    kernel_vsn=`sed "s|^KERNEL_VSN[^=]*=[^0-9]*\([0-9].*\)$|\1|g" lib/kernel/vsn.mk`
	    update_rel=true;;
	stdlib)
	    stdlib_vsn=`sed "s|^STDLIB_VSN[^=]*=[^0-9]*\([0-9].*\)$|\1|g" lib/stdlib/vsn.mk`
	    update_rel=true;;
	sasl)
	    sasl_vsn=`sed "s|^SASL_VSN[^=]*=[^0-9]*\([0-9].*\)$|\1|g" lib/sasl/vsn.mk`
	    update_rel=true;;
	*)
	    ;;
    esac
done

# and find the old versions for those not included
if [ "X$update_rel" != "X" ]; then
    if [ "X$erts_vsn" = "X" ]; then
	erts_vsns=`ls -d "$idir"/erts-* | sed "s|$idir/erts-\([0-9\.].*\)|\1|g"`
	erts_vsn=`echo "$erts_vsns" | sort -t '.' -g | tail -n 1`
    fi
    if [ "X$kernel_vsn" = "X" ]; then
	kernel_vsns=`ls -d "$idir"/lib/kernel-* | sed "s|$idir/lib/kernel-\([0-9\.].*\)|\1|g"`
	kernel_vsn=`echo "$kernel_vsns" | sort -t '.' -g | tail -n 1`
    fi
    if [ "X$stdlib_vsn" = "X" ]; then
	stdlib_vsns=`ls -d "$idir"/lib/stdlib-* | sed "s|$idir/lib/stdlib-\([0-9\.].*\)|\1|g"`
	stdlib_vsn=`echo "$stdlib_vsns" | sort -t '.' -g | tail -n 1`
    fi
    if [ "X$sasl_vsn" = "X" ]; then
	sasl_vsns=`ls -d "$idir"/lib/sasl-* | sed "s|$idir/lib/sasl-\([0-9\.].*\)|\1|g"`
	sasl_vsn=`echo "$sasl_vsns" | sort -t '.' -g | tail -n 1`
    fi

    # Generate .rel, .script and .boot - to tmp dir
    start_clean="{release, {\"Erlang/OTP\",\"$otp_rel\"}, {erts, \"$erts_vsn\"}, [{kernel,\"$kernel_vsn\"},  {stdlib,\"$stdlib_vsn\"}]}."
    start_sasl="{release, {\"Erlang/OTP\",\"$otp_rel\"}, {erts, \"$erts_vsn\"}, [{kernel,\"$kernel_vsn\"},  {stdlib,\"$stdlib_vsn\"},  {sasl,\"$sasl_vsn\"}]}."

    tmp_dir="$idir/tmp";
    if [ ! -d "$tmp_dir" ]; then
	$mkdir "$tmp_dir"
    fi
    echo "$start_sasl" > "$tmp_dir/start_sasl.rel"
    echo "$start_clean" > "$tmp_dir/start_clean.rel"
    echo "$start_clean" > "$tmp_dir/no_dot_erlang.rel"

    $erlc -I"$idir"/lib/*/ebin -o"$tmp_dir" "$tmp_dir/start_sasl.rel" || exit 1
    $erlc -I"$idir"/lib/*/ebin -o"$tmp_dir" +no_warn_sasl "$tmp_dir/start_clean.rel" || exit 1
    $erlc -I"$idir"/lib/*/ebin -o"$tmp_dir" +no_warn_sasl +no_dot_erlang "$tmp_dir/no_dot_erlang.rel" || exit 1

    # Generate RELEASES file
    "$erl" -noinput +B -eval "release_handler:create_RELEASES(\"%ERL_ROOT%\", \"$tmp_dir\", \"$tmp_dir/start_sasl.rel\", []), halt()" || exit 1

    # If all good so far, move generated files into target area
    $mv "$tmp_dir/RELEASES" "$idir/releases/RELEASES.src"
    $mv "$tmp_dir"/* "$idir/releases/$otp_rel"
    $rmdir "$tmp_dir"

    # Remove old start scripts (forces a new run of Install)
    $rm -f "$idir"/releases/RELEASES
    $rm -f "$idir"/bin/*.script
    $rm -f "$idir"/bin/*.boot
    $rm -f "$idir"/bin/erl

    echo ""
    echo "*"
    echo "* NOTE! In order to get a runnable OTP system again you"
    echo "*       need to run the 'Install' script located in:"
    echo "*       $idir"
    echo "*"
fi