1#!/bin/sh
2
3# Copyright (c) 2016-2018 Jade Allen
4# Copyright (c) 2011, 2012 Spawngrid, Inc
5# Copyright (c) 2011 Evax Software <contact(at)evax(dot)org>
6#
7# Permission is hereby granted, free of charge, to any person obtaining a copy
8# of this software and associated documentation files (the "Software"), to deal
9# in the Software without restriction, including without limitation the rights
10# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11# copies of the Software, and to permit persons to whom the Software is
12# furnished to do so, subject to the following conditions:
13#
14# The above copyright notice and this permission notice shall be included in
15# all copies or substantial portions of the Software.
16#
17# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23# THE SOFTWARE.
24
25unset ERL_TOP
26
27# Make sure CDPATH doesn't affect cd in case path is relative.
28unset CDPATH
29
30KERL_VERSION='2.2.2'
31
32DOCSH_GITHUB_URL='https://github.com/erszcz/docsh.git'
33ERLANG_DOWNLOAD_URL='https://erlang.org/download'
34KERL_CONFIG_STORAGE_FILENAME='.kerl_config'
35
36TMP_DIR=${TMP_DIR:-'/tmp'}
37if [ -z "$HOME" ]; then
38    # shellcheck disable=SC2016
39    echo 'Error: $HOME is empty or not set.' 1>&2
40    exit 1
41fi
42
43# Default values
44OTP_GITHUB_URL=${OTP_GITHUB_URL:='https://github.com/erlang/otp'}
45KERL_BASE_DIR=${KERL_BASE_DIR:="$HOME"/.kerl}
46KERL_CONFIG=${KERL_CONFIG:="$HOME"/.kerlrc}
47KERL_DOWNLOAD_DIR=${KERL_DOWNLOAD_DIR:="${KERL_BASE_DIR:?}"/archives}
48KERL_BUILD_DIR=${KERL_BUILD_DIR:="${KERL_BASE_DIR:?}"/builds}
49KERL_GIT_DIR=${KERL_GIT_DIR:="${KERL_BASE_DIR:?}"/gits}
50KERL_DOC_TARGETS=${KERL_DOC_TARGETS:="chunks"}
51
52if [ -n "$OTP_GITHUB_URL" ]; then
53    _OGU="$OTP_GITHUB_URL"
54fi
55if [ -n "$KERL_CONFIGURE_OPTIONS" ]; then
56    _KCO="$KERL_CONFIGURE_OPTIONS"
57fi
58if [ -n "$KERL_CONFIGURE_APPLICATIONS" ]; then
59    _KCA="$KERL_CONFIGURE_APPLICATIONS"
60fi
61if [ -n "$KERL_CONFIGURE_DISABLE_APPLICATIONS" ]; then
62    _KCDA="$KERL_CONFIGURE_DISABLE_APPLICATIONS"
63fi
64if [ -n "$KERL_SASL_STARTUP" ]; then
65    _KSS="$KERL_SASL_STARTUP"
66fi
67if [ -n "$KERL_DEPLOY_SSH_OPTIONS" ]; then
68    _KDSSH="$KERL_DEPLOY_SSH_OPTIONS"
69fi
70if [ -n "$KERL_DEPLOY_RSYNC_OPTIONS" ]; then
71    _KDRSYNC="$KERL_DEPLOY_RSYNC_OPTIONS"
72fi
73if [ -n "$KERL_INSTALL_MANPAGES" ]; then
74    _KIM="$KERL_INSTALL_MANPAGES"
75fi
76if [ -n "$KERL_INSTALL_HTMLDOCS" ]; then
77    _KIHD="$KERL_INSTALL_HTMLDOCS"
78fi
79if [ -n "$KERL_BUILD_PLT" ]; then
80    _KBPLT="$KERL_BUILD_PLT"
81fi
82if [ -n "$KERL_BUILD_DOCS" ]; then
83    _KBD="$KERL_BUILD_DOCS"
84fi
85if [ -n "$KERL_DOC_TARGETS" ]; then
86    _KDT="$KERL_DOC_TARGETS"
87fi
88if [ -n "$KERL_BUILD_BACKEND" ]; then
89    _KBB="$KERL_BUILD_BACKEND"
90fi
91
92OTP_GITHUB_URL=
93KERL_CONFIGURE_OPTIONS=
94KERL_CONFIGURE_APPLICATIONS=
95KERL_CONFIGURE_DISABLE_APPLICATIONS=
96KERL_SASL_STARTUP=
97KERL_DEPLOY_SSH_OPTIONS=
98KERL_DEPLOY_RSYNC_OPTIONS=
99KERL_INSTALL_MANPAGES=
100KERL_INSTALL_HTMLDOCS=
101KERL_BUILD_PLT=
102KERL_BUILD_DOCS=
103KERL_DOC_TARGETS=
104KERL_BUILD_BACKEND=
105
106# ensure the base dir exists
107mkdir -p "$KERL_BASE_DIR" || exit 1
108
109# source the config file if available
110if [ -f "$KERL_CONFIG" ]; then
111    # shellcheck source=/dev/null
112    . "$KERL_CONFIG"
113fi
114
115if [ -n "$_OGU" ]; then
116    OTP_GITHUB_URL="$_OGU"
117fi
118if [ -n "$_KCO" ]; then
119    KERL_CONFIGURE_OPTIONS="$_KCO"
120fi
121if [ -n "$_KCA" ]; then
122    KERL_CONFIGURE_APPLICATIONS="$_KCA"
123fi
124if [ -n "$_KCDA" ]; then
125    KERL_CONFIGURE_DISABLE_APPLICATIONS="$_KCDA"
126fi
127if [ -n "$_KSS" ]; then
128    KERL_SASL_STARTUP="$_KSS"
129fi
130if [ -n "$_KDSSH" ]; then
131    KERL_DEPLOY_SSH_OPTIONS="$_KDSSH"
132fi
133if [ -n "$_KDRSYNC" ]; then
134    KERL_DEPLOY_RSYNC_OPTIONS="$_KDRSYNC"
135fi
136if [ -n "$_KIM" ]; then
137    KERL_INSTALL_MANPAGES="$_KIM"
138fi
139if [ -n "$_KIHD" ]; then
140    KERL_INSTALL_HTMLDOCS="$_KIHD"
141fi
142if [ -n "$_KBPLT" ]; then
143    KERL_BUILD_PLT="$_KBPLT"
144fi
145if [ -n "$_KBD" ]; then
146    KERL_BUILD_DOCS="$_KBD"
147fi
148if [ -n "$_KDT" ]; then
149    KERL_DOC_TARGETS="$_KDT"
150fi
151if [ -n "$_KBB" ]; then
152    KERL_BUILD_BACKEND="$_KBB"
153fi
154
155if [ -z "$KERL_SASL_STARTUP" ]; then
156    INSTALL_OPT='-minimal'
157else
158    INSTALL_OPT='-sasl'
159fi
160
161if [ -z "$KERL_BUILD_BACKEND" ]; then
162    KERL_BUILD_BACKEND='git'
163fi
164if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
165    KERL_USE_AUTOCONF=1
166elif [ "$KERL_BUILD_BACKEND" != 'tarball' ]; then
167    echo "Unhandled value KERL_BUILD_BACKEND='${KERL_BUILD_BACKEND}'"
168    echo "KERL_BUILD_BACKEND must be one of 'git' (default) or 'tarball'"
169    exit 1
170fi
171
172KERL_SYSTEM=$(uname -s)
173case "$KERL_SYSTEM" in
174    Darwin|FreeBSD|OpenBSD)
175        MD5SUM='openssl md5'
176        MD5SUM_FIELD=2
177        SED_OPT=-E
178        CP_OPT=-a
179        ;;
180    *)
181        MD5SUM=md5sum
182        MD5SUM_FIELD=1
183        SED_OPT=-r
184        CP_OPT=-pr
185        ;;
186esac
187
188
189usage() {
190    echo 'kerl: build and install Erlang/OTP'
191    echo "usage: $0 <command> [options ...]"
192    printf '\n  <command>       Command to be executed\n\n'
193    echo 'Valid commands are:'
194    echo '  build           Build specified release or git repository'
195    echo '  install         Install the specified release at the given location'
196    echo '  deploy          Deploy the specified installation to the given host and location'
197    echo '  update          Update the list of available releases from your source provider'
198    echo '  list            List releases, builds and installations'
199    echo '  delete          Delete builds and installations'
200    echo '  install-docsh   Install erl shell documentation access extension - docsh'
201    echo '  path            Print the path of a given installation'
202    echo '  active          Print the path of the active installation'
203    echo '  plt             Print Dialyzer PLT path for the active installation'
204    echo '  status          Print available builds and installations'
205    echo '  prompt          Print a string suitable for insertion in prompt'
206    echo '  cleanup         Remove compilation artifacts (use after installation)'
207    echo "  version         Print current version (current: $KERL_VERSION)"
208    exit 1
209}
210
211if [ $# -eq 0 ]; then usage; fi
212
213get_releases() {
214    if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
215        get_git_releases
216    else
217        get_tarball_releases
218    fi
219}
220
221get_git_releases() {
222    git ls-remote --tags --refs "$OTP_GITHUB_URL" \
223    | cut -f2 \
224    | cut -d'/' -f3- \
225    | sed $SED_OPT \
226        -e '# Delete all tags starting with ":" as to not mix' \
227        -e '# them with the prefixed lines we`re generating next.' \
228        -e '/^:/d' \
229        \
230        -e '# Prefix "OTP*" release lines with the crux of their versions.' \
231        -e '#  - "OTP_R16B01_RC1"  =>  ":16B01_RC1 OTP_R16B01_RC1"' \
232        -e '#  - "OTP_R16B03"      =>  ":16B03 OTP_R16B03"' \
233        -e '#  - "OTP-17.0"        =>  ":17.0 OTP-17.0"' \
234        -e '#  - "OTP-17.3.1"      =>  ":17.3.1 OTP-17.3.1"' \
235        -e '#  - "OTP-19.0-rc1"    =>  ":19.0-rc1 OTP-19.0-rc1"' \
236        -e '#  - "OTP-19.0-rc2"    =>  ":19.0-rc2 OTP-19.0-rc2"' \
237        -e 's/^(OTP[-_](R?([0-9][^ :]*).*))/:\3 \2/' \
238        \
239        -e '# Delete all lines that didn`t get prefixed above.' \
240        -e '/^[^:]/d' \
241        \
242        -e '# Move the colon markers preceding each version prefix' \
243        -e '# as to preceed the tag suffix instead, which will make' \
244        -e '# throwing the version prefixes easier later on.' \
245        -e '#  - ":16B01_RC1 OTP_R16B03"  =>  "16B01_RC1 :OTP_R16B01_RC1"' \
246        -e '#  -     ":16B03 OTP_R16B03"  =>  "16B03 :OTP_R16B03"' \
247        -e '#  -      ":17.0 OTP_R16B03"  =>  "17.0 :OTP-17.0"' \
248        -e '#  -    ":17.3.1 OTP_R16B03"  =>  "17.3.1 :OTP-17.3.1"' \
249        -e '#  -  ":19.0-rc1 OTP_R16B03"  =>  "19.0-rc1 :OTP-19.0-rc1"' \
250        -e '#  -  ":19.0-rc2 OTP_R16B03"  =>  "19.0-rc2 :OTP-19.0-rc2"' \
251        -e 's/^:([^ ]+) /\1 :/' \
252        \
253        -e '# Repeatedly replace sequences of one or more dots, dashes' \
254        -e '# or underscores, within each version prefix, with single' \
255        -e '# space characters.' \
256        -e '#  - "16B01_RC1 :OTP_R16B01_RC1"  =>  "16B01 RC1 :OTP_R16B01_RC1"' \
257        -e '#  -     "16B03 :OTP_R16B03"      =>  "16B03 :OTP_R16B03"' \
258        -e '#  -      "17.0 :OTP-17.0"        =>  "17 0 :OTP-17.0"' \
259        -e '#  -    "17.3.1 :OTP-17.3.1"      =>  "17 3 1 :OTP-17.3.1"' \
260        -e '#  -  "19.0-rc1 :OTP-19.0-rc1"    =>  "19 0 rc1 :OTP-19.0-rc1"' \
261        -e '#  -  "19.0-rc2 :OTP-19.0-rc2"    =>  "19 0 rc2 :OTP-19.0-rc2"' \
262        -e ':loop' \
263           -e 's/^([^:]*)[.-]+([^:]*) :/\1 \2 :/' \
264        -e 't loop' \
265        \
266        -e '# Repeatedly replace "A", "B", or "C" separators, within each' \
267        -e '# version prefix, with " 0 ", " 1 " and " 2 ", respectively.' \
268        -e '#  - "16B01 RC1 :OTP_R16B01_RC1"  =>  "16 1 01 RC1 :OTP_R16B01_RC1"' \
269        -e '#  -     "16B03 :OTP_R16B03"      =>  "16 1 03 :OTP_R16B03"' \
270        -e ':loop2' \
271            -e 's/^(.*[0-9]+)A([^:]*) :/\1 0 \2 :/' \
272            -e 's/^(.*[0-9]+)B([^:]*) :/\1 1 \2 :/' \
273            -e 's/^(.*[0-9]+)C([^:]*) :/\1 2 \2 :/' \
274        -e 't loop2' \
275        \
276        -e '# Repeatedly replace space-release candidate infixes, within' \
277        -e '# each version prefix, with a leading zero followed by' \
278        -e '# the candidate number.' \
279        -e '# - "16 1 01 RC1 :OTP_R16B01_RC1"  =>  "16 1 01 0 1 :OTP_R16B01_RC1"' \
280        -e '# -      "19 0 rc1 :OTP-19.0-rc1"  =>  "19 0 0 1 :OTP-19.0-rc1"' \
281        -e '# -      "19 0 rc2 :OTP-19.0-rc2"  =>  "19 0 0 2 :OTP-19.0-rc2"' \
282        -e ':loop3' \
283            -e 's/^([^:]* )(rc|RC)([0-9]+)(( [^:]*)?) :/\10 \3\4 :/' \
284        -e 't loop3' \
285        \
286        -e '# Repeatedly prefix single digits, within each version prefix,' \
287        -e '# with leading zeroes.' \
288        -e '#  - "16 1 01 0 1 :OTP_R16B01_RC1"  =>  "16 01 01 00 01 :OTP_R16B01_RC1"' \
289        -e '#  -     "16 1 03 :OTP_R16B03"      =>  "16 01 03 :OTP_R16B03"' \
290        -e '#  -        "17 0 :OTP-17.0"        =>  "17 00 :OTP-17.0"' \
291        -e '#  -      "17 3 1 :OTP-17.3.1"      =>  "17 03 01 :OTP-17.3.1"' \
292        -e '#  -    "19 0 0 1 :OTP-19.0-rc1"    =>  "19 00 00 01 :OTP-19.0-rc.1"' \
293        -e '#  -    "19 0 0 2 :OTP-19.0-rc2"    =>  "19 00 00 02 :OTP-19.0-rc.2"' \
294        -e ':loop4' \
295            -e 's/^([^:]*[^0-9:])([0-9])(([^0-9][^:]*)?) :/\10\2\3 :/' \
296        -e 't loop4' \
297        \
298        -e '# Suffix each version prefix with 00 as to not compare ':' with a number.' \
299        -e '#  - "16 01 01 00 01 :OTP_R16B01_RC1"  =>  "16 01 01 00 01 00 :OTP_R16B01_RC1"' \
300        -e '#  -       "16 01 03 :OTP_R16B03"      =>  "16 01 03 00 :OTP_R16B03"' \
301        -e '#  -          "17 00 :OTP-17.0""       =>  "17 00 00 :OTP-17.0"' \
302        -e '#  -       "17 03 01 :OTP-17.3.1"      =>  "17 03 01 00 :OTP-17.3.1"' \
303        -e '#  -    "19 00 00 01 :OTP-19.0-rc.1"   =>  "19 00 00 01 00 :OTP-19.0-rc.1"' \
304        -e '#  -    "19 00 00 02 :OTP-19.0-rc.2"   =>  "19 00 00 02 00 :OTP-19.0-rc.2"' \
305        -e 's/^([^:]+) :/\1 00 :/' \
306        \
307     | LC_ALL=C sort -n \
308     | cut -d':' -f2-
309}
310
311get_tarball_releases() {
312    tmp="$(mktemp "$TMP_DIR"/kerl.XXXXXX)"
313    if [ 200 = "$(\curl -qsL --output "$tmp" --write-out '%{http_code}' $ERLANG_DOWNLOAD_URL/)" ]; then
314        sed $SED_OPT \
315            -e 's/^.*<[aA] [hH][rR][eE][fF]=\"otp_src_([-0-9A-Za-z_.]+)\.tar\.gz\">.*$/\1/' \
316            -e '/^R1|^[0-9]/!d' "$tmp" \
317        | sed -e 's/^R\(.*\)/\1:R\1/' \
318        | sed -e 's/^\([^\:]*\)$/\1-z:\1/' \
319        | sort | cut -d: -f2
320        rm "$tmp"
321        return 0
322    fi
323    rm "$tmp"
324    exit 1
325}
326
327update_checksum_file() {
328    if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
329        return 0
330    else
331        echo 'Getting checksum file from erlang.org...'
332        curl -f -L -o "$KERL_DOWNLOAD_DIR"/MD5 "$ERLANG_DOWNLOAD_URL"/MD5 || exit 1
333    fi
334}
335
336ensure_checksum_file() {
337    if [ ! -s "$KERL_DOWNLOAD_DIR"/MD5 ]; then
338        update_checksum_file
339    fi
340}
341
342check_releases() {
343    if [ ! -f "$KERL_BASE_DIR"/otp_releases ]; then
344        get_releases >"$KERL_BASE_DIR"/otp_releases
345    fi
346}
347
348is_valid_release() {
349    check_releases
350    while read -r rel; do
351        if [ "$1" = "$rel" ]; then
352            return 0
353        fi
354    done <"$KERL_BASE_DIR"/otp_releases
355    return 1
356}
357
358assert_valid_release() {
359    if ! is_valid_release "$1"; then
360        echo "$1 is not a valid Erlang/OTP release"
361        exit 1
362    fi
363    return 0
364}
365
366get_release_from_name() {
367    if [ -f "$KERL_BASE_DIR"/otp_builds ]; then
368        while read -r l; do
369            rel=$(echo "$l" | cut -d, -f1)
370            name=$(echo "$l" | cut -d, -f2)
371            if [ "$name" = "$1" ]; then
372                echo "$rel"
373                return 0
374            fi
375        done <"$KERL_BASE_DIR"/otp_builds
376    fi
377    return 1
378}
379
380get_newest_valid_release() {
381    check_releases
382
383    rel=$(tail -1 "$KERL_BASE_DIR"/otp_releases)
384
385    if [ -n "$rel" ]; then
386        echo "$rel"
387        return 0
388    fi
389
390    return 1
391}
392
393is_valid_installation() {
394    if [ -f "$KERL_BASE_DIR"/otp_installations ]; then
395        while read -r l; do
396            name=$(echo "$l" | cut -d' ' -f1)
397            path=$(echo "$l" | cut -d' ' -f2)
398            if [ "$name" = "$1" ] || [ "$path" = "$1" ]; then
399                if [ -f "$path"/activate ]; then
400                    return 0
401                fi
402            fi
403        done <"$KERL_BASE_DIR"/otp_installations
404    fi
405    return 1
406}
407
408assert_valid_installation() {
409    if ! is_valid_installation "$1"; then
410        echo "$1 is not a kerl-managed Erlang/OTP installation"
411        exit 1
412    fi
413    return 0
414}
415
416assert_build_name_unused() {
417    if [ -f "$KERL_BASE_DIR"/otp_builds ]; then
418        while read -r l; do
419            name=$(echo "$l" | cut -d, -f2)
420            if [ "$name" = "$1" ]; then
421                echo "There's already a build named $1"
422                exit 1
423            fi
424        done <"$KERL_BASE_DIR"/otp_builds
425    fi
426}
427
428_check_required_pkgs() {
429    has_dpkg=$(command -v dpkg)
430    has_rpm=$(command -v rpm)
431    if [ -n "$has_dpkg" ] || [ -n "$has_rpm" ]; then
432        # found either dpkg or rpm (or maybe even both!)
433        if [ -n "$has_dpkg" ] && [ -n "$has_rpm" ]; then
434            echo 'WARNING: You appear to have BOTH rpm and dpkg. This is very strange. No package checks done.'
435        elif [ -n "$has_dpkg" ]; then
436            _check_dpkg
437        elif [ -n "$has_rpm" ]; then
438            _check_rpm
439        fi
440    fi
441}
442
443_dpkg_is_installed() {
444    # gratefully stolen from
445    # https://superuser.com/questions/427318/test-if-a-package-is-installed-in-apt
446    # returns 0 (true) if found, 1 otherwise
447    dpkg-query -Wf'${db:Status-abbrev}' "$1" 2>/dev/null | \grep -q '^i'
448}
449
450_check_dpkg() {
451    required='
452libssl-dev
453make
454automake
455autoconf
456libncurses5-dev
457gcc
458'
459    for pkg in $required; do
460        if ! _dpkg_is_installed "$pkg"; then
461            echo "WARNING: It appears that a required development package '$pkg' is not installed."
462        fi
463    done
464}
465
466_rpm_is_installed() {
467    rpm --quiet -q "$1" >/dev/null 2>&1
468}
469
470_check_rpm() {
471    required='
472openssl-devel
473make
474automake
475autoconf
476ncurses-devel
477gcc
478'
479    for pkg in $required; do
480        if ! _rpm_is_installed "$pkg"; then
481            echo "WARNING: It appears a required development package '$pkg' is not installed."
482        fi
483    done
484}
485
486do_git_build() {
487    assert_build_name_unused "$3"
488
489    GIT=$(printf '%s' "$1" | $MD5SUM | cut -d ' ' -f $MD5SUM_FIELD)
490    mkdir -p "$KERL_GIT_DIR" || exit 1
491    cd "$KERL_GIT_DIR" || exit 1
492    echo "Checking out Erlang/OTP git repository from $1..."
493    if [ ! -d "$GIT" ]; then
494        if ! git clone -q --mirror "$1" "$GIT" >/dev/null 2>&1; then
495            echo 'Error mirroring remote git repository'
496            exit 1
497        fi
498    fi
499    cd "$GIT" || exit 1
500    if ! git remote update --prune >/dev/null 2>&1; then
501        echo 'Error updating remote git repository'
502        exit 1
503    fi
504
505    rm -Rf "${KERL_BUILD_DIR:?}/$3"
506    mkdir -p "$KERL_BUILD_DIR/$3" || exit 1
507    cd "$KERL_BUILD_DIR/$3" || exit 1
508    if ! git clone -l "$KERL_GIT_DIR/$GIT" otp_src_git >/dev/null 2>&1; then
509        echo 'Error cloning local git repository'
510        exit 1
511    fi
512    cd otp_src_git || exit 1
513    if ! git checkout "$2" >/dev/null 2>&1; then
514        if ! git checkout -b "$2" "$2" >/dev/null 2>&1; then
515            echo 'Could not checkout specified version'
516            rm -Rf "${KERL_BUILD_DIR:?}/$3"
517            exit 1
518        fi
519    fi
520    if [ ! -x otp_build ]; then
521        echo 'Not a valid Erlang/OTP repository'
522        rm -Rf "${KERL_BUILD_DIR:?}/$3"
523        exit 1
524    fi
525    echo "Building Erlang/OTP $3 from git, please wait..."
526    if [ -z "$KERL_BUILD_AUTOCONF" ]; then
527        KERL_USE_AUTOCONF=1
528    fi
529    _do_build 'git' "$3"
530    echo "Erlang/OTP $3 from git has been successfully built"
531    list_add builds git,"$3"
532}
533
534get_otp_version() {
535    echo "$1" | sed $SED_OPT -e 's/R?([0-9]{1,2}).+/\1/'
536}
537
538get_perl_version() {
539    if assert_perl; then
540        # This is really evil but it's portable and it works. Don't @ me bro
541        perl -e 'print int(($] - 5)*1000)'
542    else
543        echo 'FATAL: could not find perl which is required to compile Erlang.'
544        exit 1
545    fi
546}
547
548assert_perl() {
549    perl_loc=$(command -v perl)
550    if [ -z "$perl_loc" ]; then
551        return 1
552    else
553        # 0 to bash is "true" because of Unix exit code conventions
554        return 0
555    fi
556}
557
558get_javac_version() {
559    java_loc=$(command -v javac)
560    if [ -z "$java_loc" ]; then
561        # Java's not installed, so just return 0
562        0
563    else
564        javaout=$(javac -version 2>&1)
565        echo "$javaout" | cut -d' ' -f2 | cut -d. -f2
566    fi
567}
568
569show_configuration_warnings() {
570    # $1 is logfile
571    # $2 is section header (E.g. "APPLICATIONS DISABLED")
572    # Find the row number for the section we are looking for
573    INDEX=$(\grep -n -m1 "$2" "$1" | cut -d: -f1)
574
575    # If there are no warnings, the section won't appear in the log
576    if [ -n "$INDEX" ]; then
577        # Skip the section header, find the end line and skip it
578        # then print the results indented
579        tail -n +$((INDEX+3)) "$1" | \
580            sed -n '1,/\*/p' | \
581            awk -F: -v logfile="$1" -v section="$2" \
582                'BEGIN { printf "%s (See: %s)\n", section, logfile }
583                 /^[^\*]/ { print " *", $0 }
584                 END { print "" } '
585    fi
586}
587
588show_logfile() {
589    echo "$1"
590    tail "$2"
591    echo
592    echo "Please see $2 for full details."
593}
594
595maybe_patch() {
596    # $1 = OS platform e.g., Darwin, etc
597    # $2 = OTP release
598
599    release=$(get_otp_version "$2")
600    case "$1" in
601        Darwin)
602            maybe_patch_darwin "$release"
603            # Catalina and clang require a "no-weaks-import" flag during build
604            maybe_patch_catalina "$release" "$2"
605            maybe_patch_bigsur "$release"
606            ;;
607        SunOS)
608            maybe_patch_sunos "$release"
609            ;;
610        *)
611            ;;
612    esac
613
614    maybe_patch_all "$release"
615}
616
617maybe_patch_all() {
618    perlver=$(get_perl_version)
619    if [ "$perlver" -ge 22 ]; then
620        case "$1" in
621            14)
622                apply_r14_beam_makeops_patch >>"$LOGFILE"
623                ;;
624            15)
625                apply_r15_beam_makeops_patch >>"$LOGFILE"
626                ;;
627            *)
628                ;;
629        esac
630    fi
631
632    # Are we building docs?
633    if [ -n "$KERL_BUILD_DOCS" ]; then
634        if [ "$1" -le 16 ]; then
635            javaver=$(get_javac_version)
636            if [ "$javaver" -ge 8 ]; then
637                apply_javadoc_linting_patch >>"$LOGFILE"
638            fi
639        fi
640    fi
641
642    # Maybe apply zlib patch
643    if [ "$1" -ge 17 ] && [ "$1" -le 19 ]; then
644        apply_zlib_patch >> "$LOGFILE"
645    fi
646}
647
648maybe_patch_darwin() {
649    # Reminder: $1 = OTP release version
650    if [ "$1" -le 14 ]; then
651        CFLAGS='-DERTS_DO_INCL_GLB_INLINE_FUNC_DEF'
652        apply_darwin_compiler_patch >>"$LOGFILE"
653    elif [ "$1" -eq 16 ]; then
654        apply_r16_wx_ptr_patch >>"$LOGFILE"
655    elif [ "$1" -ge 17 ] && [ "$1" -le 19 ]; then
656        apply_wx_ptr_patch >>"$LOGFILE"
657    elif [ "$1" -ge 21 ] && [ "$1" -le 23 ]; then
658        apply_in6addr_test_patch >> "$LOGFILE"
659        KERL_USE_AUTOCONF=1
660    fi
661}
662
663maybe_patch_catalina() {
664    clang_version=$(/usr/bin/clang --version  | head -1  | awk '{print $4}')
665    command_line_tools_version=$(xcode-select -v | awk '{print $3}' | sed 's/.$//')
666    release="$1"
667    otp_version="$2"
668
669    if is_osx_catalina && \
670        [[ "$release" -lt 23 ]] && \
671        compare_sem_version $otp_version "<" "22.3.1" && \
672        compare_sem_version $clang_version ">" "11.4" && \
673        [[ "$command_line_tools_version" -le 2373 ]]; then
674        apply_catalina_no_weak_imports_patch >>"$LOGFILE"
675        KERL_USE_AUTOCONF=1
676    fi
677}
678
679is_osx_catalina() {
680    [[ $(uname -r) == "19"* ]]
681}
682
683compare_sem_version() {
684    version=$1
685    operator=$2
686    baseline=$3
687
688    case $operator in
689        '<')
690            [[ $(printf "$version\n$baseline" | sort --version-sort | head -n 1) != "$baseline" ]]
691            ;;
692        ('>' | "==")
693            [[ $(printf "$version\n$baseline" | sort --version-sort | head -n 1) == "$baseline" ]]
694            ;;
695    esac
696}
697
698apply_catalina_no_weak_imports_patch() {
699    patch -p1 <<'_END_PATCH'
700diff --git a/erts/configure.in b/erts/configure.in
701index 3ba8216a19..d7cebc5ebc 100644
702--- a/erts/configure.in
703+++ b/erts/configure.in
704@@ -926,20 +926,16 @@ dnl for now that is the way we do it.
705 USER_LD=$LD
706 USER_LDFLAGS="$LDFLAGS"
707 LD='$(CC)'
708+
709 case $host_os in
710-     darwin*)
711-	saved_LDFLAGS="$LDFLAGS"
712-	LDFLAGS="$LDFLAGS -Wl,-no_weak_imports"
713-	AC_TRY_LINK([],[],
714-		[
715-			LD_MAY_BE_WEAK=no
716-		],
717-		[
718-			LD_MAY_BE_WEAK=yes
719-			LDFLAGS="$saved_LDFLAGS"
720-		]);;
721-    *)
722-	LD_MAY_BE_WEAK=no;;
723+        darwin19*)
724+	    # Disable stack checking to avoid crashing with a segment fault
725+	    # in macOS Catalina.
726+	    AC_MSG_NOTICE([Turning off stack check on macOS 10.15 (Catalina)])
727+	    CFLAGS="-Wno-error=implicit-function-declaration -fno-stack-check $CFLAGS"
728+	    ;;
729+        *)
730+	    ;;
731 esac
732
733 AC_SUBST(LD)
734_END_PATCH
735}
736
737# https://github.com/erlang/otp/commit/f1044ef9e35da26f276b8127640e177d67aade6a.diff
738maybe_patch_bigsur() {
739    release="$1"
740    if is_osx_bigsur && \
741        [[ "$release" -lt 24 ]]; then
742        apply_bigsur_bypass_version_check >>"$LOGFILE"
743        KERL_USE_AUTOCONF=1
744    fi
745}
746
747is_osx_bigsur() {
748    [[ $(uname -r) == "20"* ]]
749}
750
751apply_bigsur_bypass_version_check() {
752    if [ -f make/configure.in ]; then
753        echo "Bypassing version check in make/configure.in for Big Sur"
754        sed -i '' 's/\(#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > $int_macosx_version\)/\1 \&\& false/' make/configure.in
755    elif [ -f configure.in ]; then
756        echo "Bypassing version check in configure.in for Big Sur"
757        sed -i '' 's/\(#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > $int_macosx_version\)/\1 \&\& false/' configure.in
758        echo "Removng no_weak_imports from LDFLAGS in erts/configure.in for Big Sur"
759        sed -i '' 's/LDFLAGS="$LDFLAGS -Wl,-no_weak_imports"//' erts/configure.in
760    fi
761}
762
763maybe_patch_sunos() {
764    if [ "$1" -le 14 ]; then
765        apply_solaris_networking_patch >>"$LOGFILE"
766    fi
767}
768
769do_normal_build() {
770    assert_valid_release "$1"
771    assert_build_name_unused "$2"
772    FILENAME=""
773    download "$1"
774    mkdir -p "$KERL_BUILD_DIR/$2" || exit 1
775    if [ ! -d "$KERL_BUILD_DIR/$2/$FILENAME" ]; then
776        echo 'Extracting source code'
777        UNTARDIRNAME="$KERL_BUILD_DIR/$2/$FILENAME-kerluntar-$$"
778        rm -rf "$UNTARDIRNAME"
779        mkdir -p "$UNTARDIRNAME" || exit 1
780        # github tarballs have a directory in the form of "otp[_-]TAGNAME"
781        # Ericsson tarballs have the classic otp_src_RELEASE pattern
782        # Standardize on Ericsson format because that's what the rest of the script expects
783        (cd "$UNTARDIRNAME" && tar xzf "$KERL_DOWNLOAD_DIR/$FILENAME".tar.gz && cp -rfp ./* "$KERL_BUILD_DIR/$2/otp_src_$1")
784        rm -rf "$UNTARDIRNAME"
785    fi
786
787    echo "Building Erlang/OTP $1 ($2), please wait..."
788    _do_build "$1" "$2"
789    echo "Erlang/OTP $1 ($2) has been successfully built"
790    list_add builds "$1,$2"
791}
792
793_flags() {
794    # We used to munge the LD and DED flags for clang 9/10 shipped with
795    # High Sierra (macOS 10.13), Mojave (macOS 10.14) and Catalina
796    # (macOS 10.15)
797    #
798    # As of OTP 20.1 that is (apparently) no longer necessary and
799    # in OTP 24 breaks stuff. See thread and comment here:
800    # https://github.com/erlang/otp/issues/4821#issuecomment-845914942
801    case "$KERL_SYSTEM" in
802        Darwin)
803            # Make sure we don't overwrite stuff that someone who
804            # knows better than us set.
805            if [ -z "$CC" ]; then
806                CC='clang'
807            fi
808
809            CFLAGS="$CFLAGS" CC="$CC" "$@"
810            ;;
811        *)
812            CFLAGS="$CFLAGS" "$@"
813            ;;
814    esac
815}
816
817_do_build() {
818    case "$KERL_SYSTEM" in
819        Darwin)
820            # Ensure that the --enable-darwin-64bit flag is set on all macOS
821            # That way even on older Erlangs we get 64 bit Erlang builds
822            # macOS has been mandatory 64 bit for a while
823            if ! echo "$KERL_CONFIGURE_OPTIONS" | \grep 'darwin-64bit' >/dev/null 2>&1; then
824                KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS "--enable-darwin-64bit
825            fi
826
827            # Attempt to use brew to discover if and where openssl has been
828            # installed unless the user has already explictly set it.
829
830            if ! echo "$KERL_CONFIGURE_OPTIONS" | \grep 'with-ssl' >/dev/null 2>&1; then
831                whichbrew=$(command -v brew)
832                if [ -n "$whichbrew" ] && [ -x "$whichbrew" ]; then
833                    brew_prefix=$(brew --prefix openssl@1.1)
834                    if [ -n "$brew_prefix" ] && [ -d "$brew_prefix" ]; then
835                        KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS "--with-ssl=$brew_prefix
836                    fi
837                fi
838            fi
839        ;;
840        Linux)
841            # we are going to check here to see if the Linux uses dpkg or rpms
842            #
843            # this is a "best effort" attempt to discover if a Linux has the
844            # packages needed to build Erlang. We will always assume the user
845            # knows better than us and are going to go ahead and try to build
846            # Erlang anyway. But at least it will be a clear warning to the
847            # user if a build fails.
848            _check_required_pkgs
849            ;;
850        *)
851        ;;
852    esac
853
854    if [ -n "$KERL_BUILD_DOCS" ]; then
855        KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS --prefix=$KERL_BUILD_DIR/$2/release_$1"
856    fi
857
858    ERL_TOP="$KERL_BUILD_DIR/$2/otp_src_$1"
859    cd "$ERL_TOP" || exit 1
860    LOGFILE="$KERL_BUILD_DIR/$2/otp_build_$1.log"
861
862    # Set configuation flags given applications white/black lists
863    if [ -n "$KERL_CONFIGURE_APPLICATIONS" ]; then
864        for app in $KERL_CONFIGURE_APPLICATIONS; do
865            case "$KERL_CONFIGURE_OPTIONS" in
866                *"--with-$app"*)
867                    echo "Option '--with-$app' in KERL_CONFIGURE_OPTIONS is superfluous" ;;
868                *)
869                    KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS --with-$app" ;;
870            esac
871        done
872    fi
873    if [ -n "$KERL_CONFIGURE_DISABLE_APPLICATIONS" ]; then
874        for app in $KERL_CONFIGURE_DISABLE_APPLICATIONS; do
875            case "$KERL_CONFIGURE_OPTIONS" in
876                *"--without-$app"*)
877                    echo "Option '--without-$app' in KERL_CONFIGURE_OPTIONS is superfluous" ;;
878                *)
879                    KERL_CONFIGURE_OPTIONS="$KERL_CONFIGURE_OPTIONS --without-$app" ;;
880            esac
881        done
882    fi
883
884    # Check to see if configuration options need to be stored or have changed
885    TMPOPT="${TMP_DIR}/kerloptions.$$"
886    echo "$CFLAGS" >"$TMPOPT"
887    echo "$KERL_CONFIGURE_OPTIONS" >>"$TMPOPT"
888    SUM=$($MD5SUM "$TMPOPT" | cut -d ' ' -f $MD5SUM_FIELD)
889    # Check for a .kerl_config.md5 file
890    if [ -e ./"$KERL_CONFIG_STORAGE_FILENAME".md5 ]; then
891        # Compare our current options to the saved ones
892        read -r OLD_SUM <./"$KERL_CONFIG_STORAGE_FILENAME".md5
893        if [ "$SUM" != "$OLD_SUM" ]; then
894            echo 'Configure options have changed. Reconfiguring...'
895            rm -f configure
896            mv "$TMPOPT" ./"$KERL_CONFIG_STORAGE_FILENAME"
897            echo "$SUM" >./"$KERL_CONFIG_STORAGE_FILENAME".md5
898        else
899            # configure options are the same
900            rm -f "$TMPOPT"
901        fi
902    else
903	# no file exists, so write one
904	mv "$TMPOPT" .kerl_config
905	echo "$SUM" >.kerl_config.md5
906    fi
907
908    # Don't apply patches to "custom" git builds. We have no idea if they will apply
909    # cleanly or not.
910    if [ "$1" != 'git' ]; then
911        maybe_patch "$KERL_SYSTEM" "$1"
912    fi
913    if [ -n "$KERL_USE_AUTOCONF" ]; then
914        # shellcheck disable=SC2086
915        ./otp_build autoconf $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1 && \
916           _flags ./otp_build configure $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1
917    else
918        # shellcheck disable=SC2086
919        _flags ./otp_build configure $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1
920
921    fi
922    if echo "$KERL_CONFIGURE_OPTIONS" | \grep -- '--enable-native-libs' >/dev/null 2>&1; then
923        make clean >>"$LOGFILE" 2>&1
924        # shellcheck disable=SC2086
925        if ! _flags ./otp_build configure $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1; then
926            show_logfile 'Configure failed.' "$LOGFILE"
927            list_remove builds "$1 $2"
928            exit 1
929        fi
930    fi
931
932    for SECTION in 'APPLICATIONS DISABLED' \
933                   'APPLICATIONS INFORMATION' \
934                   'DOCUMENTATION INFORMATION'; do
935        show_configuration_warnings "$LOGFILE" "$SECTION"
936    done
937
938    if [ -n "$KERL_CONFIGURE_APPLICATIONS" ]; then
939        \find ./lib -maxdepth 1 -type d -exec touch -f {}/SKIP \;
940        for app in $KERL_CONFIGURE_APPLICATIONS; do
941            if ! rm ./lib/"$app"/SKIP; then
942                echo "Couldn't prepare '$app' application for building"
943                list_remove builds "$1 $2"
944                exit 1
945            fi
946        done
947    fi
948    if [ -n "$KERL_CONFIGURE_DISABLE_APPLICATIONS" ]; then
949        for app in $KERL_CONFIGURE_DISABLE_APPLICATIONS; do
950            if ! touch -f ./lib/"$app"/SKIP; then
951                echo "Couldn't disable '$app' application for building"
952                exit 1
953            fi
954        done
955    fi
956
957    # shellcheck disable=SC2086
958    if ! _flags ./otp_build boot -a $KERL_CONFIGURE_OPTIONS >>"$LOGFILE" 2>&1; then
959        show_logfile 'Build failed.' "$LOGFILE"
960        list_remove builds "$1 $2"
961        exit 1
962    fi
963    if [ -n "$KERL_BUILD_DOCS" ]; then
964        echo 'Building docs...'
965        release=$(get_otp_version "$2")
966        if ! make docs "DOC_TARGETS=$KERL_DOC_TARGETS" >>"$LOGFILE" 2>&1; then
967            show_logfile 'Building docs failed.' "$LOGFILE"
968            list_remove builds "$1 $2"
969            exit 1
970        fi
971        if ! make release_docs "DOC_TARGETS=$KERL_DOC_TARGETS" "RELEASE_ROOT=$KERL_BUILD_DIR/$2/release_$1" >>"$LOGFILE" 2>&1; then
972            show_logfile 'Release of docs failed.' "$LOGFILE"
973            list_remove builds "$1 $2"
974            exit 1
975        fi
976    fi
977    rm -f "$LOGFILE"
978    ERL_TOP="$ERL_TOP" ./otp_build release -a "$KERL_BUILD_DIR/$2/release_$1" >/dev/null 2>&1
979    cd "$KERL_BUILD_DIR/$2/release_$1" || exit 1
980    ./Install $INSTALL_OPT "$KERL_BUILD_DIR/$2/release_$1" >/dev/null 2>&1
981    if [ -n "$KERL_BUILD_DEBUG_VM" ]; then
982        echo "Also building Erlang/OTP $1 ($2) debug VM, please wait..."
983        cd $ERL_TOP/erts/emulator || exit 1
984        make debug ERL_TOP=$ERL_TOP >>"$LOGFILE" 2>&1
985    fi
986}
987
988do_install() {
989    if ! rel=$(get_release_from_name "$1"); then
990        echo "No build named $1"
991        exit 1
992    fi
993    if ! is_valid_install_path "$2"; then
994        exit 1
995    fi
996    mkdir -p "$2" || exit 1
997    absdir=$(cd "$2" && pwd)
998    echo "Installing Erlang/OTP $rel ($1) in $absdir..."
999    ERL_TOP="$KERL_BUILD_DIR/$1/otp_src_$rel"
1000    cd "$ERL_TOP" || exit 1
1001    if ! (ERL_TOP="$ERL_TOP" ./otp_build release -a "$absdir" >/dev/null 2>&1 &&
1002              cd "$absdir" && ./Install $INSTALL_OPT "$absdir" >/dev/null 2>&1); then
1003        echo "Couldn't install Erlang/OTP $rel ($1) in $absdir"
1004        exit 1
1005    fi
1006    BEAM_DEBUG_SMP=$(\find . -name beam.debug.smp)
1007    if [ "$BEAM_DEBUG_SMP" != "" ]; then
1008        ERL_CHILD_SETUP_DEBUG=$(\find . -name erl_child_setup.debug)
1009        echo "Also installing Erlang/OTP $rel ($1) debug VM in $absdir..."
1010        cp $BEAM_DEBUG_SMP $absdir/erts-*/bin/
1011        cp $ERL_CHILD_SETUP_DEBUG $absdir/erts-*/bin/
1012        cp bin/cerl $absdir/bin
1013    fi
1014    list_add installations "$1 $absdir";
1015    cat <<ACTIVATE >"$absdir"/activate
1016#!/bin/sh
1017# credits to virtualenv
1018kerl_deactivate() {
1019    if [ -n "\$_KERL_SAVED_ERL_AFLAGS" ]; then
1020        ERL_AFLAGS="\$_KERL_SAVED_ERL_AFLAGS"
1021        export ERL_AFLAGS
1022        unset _KERL_SAVED_ERL_AFLAGS
1023    fi
1024    if [ -n "\$_KERL_PATH_REMOVABLE" ]; then
1025        # shellcheck disable=SC2001
1026        PATH="\$(echo "\$PATH" | sed -e "s#\$_KERL_PATH_REMOVABLE:##")"
1027        export PATH
1028        unset _KERL_PATH_REMOVABLE
1029    fi
1030    if [ -n "\$_KERL_MANPATH_REMOVABLE" ]; then
1031        # shellcheck disable=SC2001
1032        MANPATH="\$(echo "\$MANPATH" | sed -e "s#\$_KERL_MANPATH_REMOVABLE:##")"
1033        export MANPATH
1034        unset _KERL_MANPATH_REMOVABLE
1035    fi
1036    if [ -n "\$_KERL_SAVED_REBAR_PLT_DIR" ]; then
1037        REBAR_PLT_DIR="\$_KERL_SAVED_REBAR_PLT_DIR"
1038        export REBAR_PLT_DIR
1039        unset _KERL_SAVED_REBAR_PLT_DIR
1040    fi
1041    if [ -n "\$_KERL_ACTIVE_DIR" ]; then
1042        unset _KERL_ACTIVE_DIR
1043    fi
1044    if [ -n "\$_KERL_SAVED_PS1" ]; then
1045        PS1="\$_KERL_SAVED_PS1"
1046        export PS1
1047        unset _KERL_SAVED_PS1
1048    fi
1049    if [ -n "\$_KERL_DOCSH_DOT_ERLANG" ]; then
1050        rm "\$HOME/.erlang"
1051        unset _KERL_DOCSH_DOT_ERLANG
1052    fi
1053    if [ -n "\$_KERL_DOCSH_USER_DEFAULT" ]; then
1054        unset DOCSH_USER_DEFAULT
1055        unset _KERL_DOCSH_USER_DEFAULT
1056    fi
1057    if [ -n "\$_KERL_ERL_CALL_REMOVABLE" ]; then
1058        # shellcheck disable=SC2001
1059        PATH="\$(echo "\$PATH" | sed -e "s#\$_KERL_ERL_CALL_REMOVABLE:##")"
1060        export PATH
1061        unset _KERL_ERL_CALL_REMOVABLE
1062    fi
1063    if [ -n "\$BASH" ] || [ -n "\$ZSH_VERSION" ]; then
1064        hash -r
1065    fi
1066    if [ ! "\$1" = "nondestructive" ]; then
1067        unset -f kerl_deactivate
1068    fi
1069    unset KERL_ENABLE_PROMPT
1070    unset KERL_PROMPT_FORMAT
1071}
1072kerl_deactivate nondestructive
1073
1074_KERL_SAVED_REBAR_PLT_DIR="\$REBAR_PLT_DIR"
1075export _KERL_SAVED_REBAR_PLT_DIR
1076_KERL_PATH_REMOVABLE="$absdir/bin"
1077PATH="\${_KERL_PATH_REMOVABLE}:\$PATH"
1078export PATH _KERL_PATH_REMOVABLE
1079_KERL_MANPATH_REMOVABLE="$absdir/lib/erlang/man:$absdir/man"
1080MANPATH="\${_KERL_MANPATH_REMOVABLE}:\$MANPATH"
1081export MANPATH _KERL_MANPATH_REMOVABLE
1082REBAR_PLT_DIR="$absdir"
1083export REBAR_PLT_DIR
1084_KERL_ACTIVE_DIR="$absdir"
1085export _KERL_ACTIVE_DIR
1086_KERL_ERL_CALL_REMOVABLE=\$(\\find $absdir -type d -wholename "*erl_interface*/bin")
1087PATH="\${_KERL_ERL_CALL_REMOVABLE}:\$PATH"
1088export PATH _KERL_ERL_CALL_REMOVABLE
1089# https://twitter.com/mononcqc/status/877544929496629248
1090export _KERL_SAVED_ERL_AFLAGS=" \$ERL_AFLAGS"
1091kernel_history=\$(echo "\$ERL_AFLAGS" | \\grep 'kernel shell_history' || true)
1092if [ -z "\$kernel_history" ]; then
1093    export ERL_AFLAGS="-kernel shell_history enabled \$ERL_AFLAGS"
1094fi
1095# shellcheck source=/dev/null
1096if [ -f "$KERL_CONFIG" ]; then . "$KERL_CONFIG"; fi
1097if [ -n "\$KERL_ENABLE_PROMPT" ]; then
1098    _KERL_SAVED_PS1="\$PS1"
1099    export _KERL_SAVED_PS1
1100    if [ -n "\$KERL_PROMPT_FORMAT" ]; then
1101        FRMT="\$KERL_PROMPT_FORMAT"
1102    else
1103        FRMT="(%BUILDNAME%)"
1104    fi
1105    PRMPT=\$(echo "\$FRMT" | sed 's^%RELEASE%^$rel^;s^%BUILDNAME%^$1^')
1106    PS1="\$PRMPT\$PS1"
1107    export PS1
1108fi
1109if [ -d "$absdir/lib/docsh" ]; then
1110    export DOCSH_USER_DEFAULT="$absdir/lib/docsh/user_default"
1111    export _KERL_DOCSH_USER_DEFAULT=yes
1112    if [ -f "\$HOME/.erlang" ]; then
1113        # shellcheck disable=SC2153
1114        if [ ! x"\$KERL_DOCSH_DOT_ERLANG" = x'exists' ]; then
1115            echo "Couldn't symlink correct \$HOME/.erlang - file exists - docsh might not work."
1116            echo "Please make sure \$HOME/.erlang contains code"
1117            echo "from $absdir/lib/docsh/dot.erlang"
1118            echo 'and export KERL_DOCSH_DOT_ERLANG=exists to suppress this warning.'
1119        fi
1120    else
1121        ln -s "$absdir/lib/docsh/dot.erlang" "\$HOME/.erlang"
1122        export _KERL_DOCSH_DOT_ERLANG=yes
1123    fi
1124fi
1125if [ -n "\$BASH" ] || [ -n "\$ZSH_VERSION" ]; then
1126    hash -r
1127fi
1128ACTIVATE
1129
1130    cat <<ACTIVATE_FISH >"$absdir"/activate.fish
1131# credits to virtualenv
1132function _kerl_remove_el --description 'remove element from array'
1133    set -l new_array
1134    for el in \$\$argv[1]
1135        if test \$el != \$argv[2]
1136            set new_array \$new_array \$el
1137        end
1138    end
1139    set -x \$argv[1] \$new_array
1140end
1141
1142function kerl_deactivate --description "deactivate erlang environment"
1143    if set --query _KERL_PATH_REMOVABLE
1144        _kerl_remove_el PATH "\$_KERL_PATH_REMOVABLE"
1145        set --erase _KERL_PATH_REMOVABLE
1146    end
1147    if set --query _KERL_MANPATH_REMOVABLE
1148        _kerl_remove_el MANPATH "\$_KERL_MANPATH_REMOVABLE"
1149        set --erase _KERL_MANPATH_REMOVABLE
1150    end
1151    if set --query _KERL_SAVED_REBAR_PLT_DIR
1152        set -x REBAR_PLT_DIR "\$_KERL_SAVED_REBAR_PLT_DIR"
1153        set --erase _KERL_SAVED_REBAR_PLT_DIR
1154    end
1155    if set --query _KERL_ACTIVE_DIR
1156        set --erase _KERL_ACTIVE_DIR
1157    end
1158    if functions --query _kerl_saved_prompt
1159        functions --erase fish_prompt
1160        # functions --copy complains about about fish_prompt already being defined
1161        # so we take a page from virtualenv's book
1162        . ( begin
1163                printf "function fish_prompt\\n\\t#"
1164                functions _kerl_saved_prompt
1165            end | psub )
1166        functions --erase _kerl_saved_prompt
1167    end
1168    if set --query _KERL_DOCSH_DOT_ERLANG
1169        rm "\$HOME/.erlang"
1170        set --erase _KERL_DOCSH_DOT_ERLANG
1171    end
1172    if set --query _KERL_DOCSH_USER_DEFAULT
1173        set --erase DOCSH_USER_DEFAULT
1174        set --erase _KERL_DOCSH_USER_DEFAULT
1175    end
1176    if set --query _KERL_ERL_CALL_REMOVABLE
1177        _kerl_remove_el PATH "\$_KERL_ERL_CALL_REMOVABLE"
1178        set --erase _KERL_ERL_CALL_REMOVABLE
1179    end
1180    if test "\$argv[1]" != "nondestructive"
1181        functions --erase kerl_deactivate
1182        functions --erase _kerl_remove_el
1183    end
1184end
1185kerl_deactivate nondestructive
1186
1187set -x _KERL_SAVED_REBAR_PLT_DIR "\$REBAR_PLT_DIR"
1188set -x _KERL_PATH_REMOVABLE "$absdir/bin"
1189set -x PATH "\$_KERL_PATH_REMOVABLE" \$PATH
1190set -x _KERL_MANPATH_REMOVABLE "$absdir/lib/erlang/man" "$absdir/man"
1191set -x MANPATH \$MANPATH "\$_KERL_MANPATH_REMOVABLE"
1192set -x REBAR_PLT_DIR "$absdir"
1193set -x _KERL_ACTIVE_DIR "$absdir"
1194set -x _KERL_ERL_CALL_REMOVABLE (\\find "$absdir" -type d -path "*erl_interface*/bin")
1195set -x PATH "\$_KERL_ERL_CALL_REMOVABLE" \$PATH
1196
1197if test -f "$KERL_CONFIG.fish"
1198    source "$KERL_CONFIG.fish"
1199end
1200if set --query KERL_ENABLE_PROMPT
1201    functions --copy fish_prompt _kerl_saved_prompt
1202    function fish_prompt
1203        echo -n "($1)"
1204        _kerl_saved_prompt
1205    end
1206end
1207if test -d "$absdir/lib/docsh"
1208    set -x DOCSH_USER_DEFAULT "$absdir/lib/docsh/user_default"
1209    set -x _KERL_DOCSH_USER_DEFAULT yes
1210    if test -f "\$HOME/.erlang"
1211        if test ! x"\$KERL_DOCSH_DOT_ERLANG" = x"exists"
1212            echo "Couldn't symlink correct \$HOME/.erlang - file exists - docsh might not work."
1213            echo "Please make sure \$HOME/.erlang contains code"
1214            echo "from $absdir/lib/docsh/dot.erlang"
1215            echo "and export KERL_DOCSH_DOT_ERLANG=exists to suppress this warning."
1216        end
1217    else
1218        ln -s "$absdir/lib/docsh/dot.erlang" "\$HOME/.erlang"
1219        set -x _KERL_DOCSH_DOT_ERLANG yes
1220    end
1221end
1222ACTIVATE_FISH
1223
1224    cat <<ACTIVATE_CSH >"$absdir"/activate.csh
1225# This file must be used with "source bin/activate.csh" *from csh*.
1226# You cannot run it directly.
1227
1228alias kerl_deactivate 'test \$?_KERL_SAVED_PATH != 0 && setenv PATH "\$_KERL_SAVED_PATH" && unset _KERL_SAVED_PATH; rehash; test \$?_KERL_SAVED_MANPATH != 0 && setenv MANPATH "\$_KERL_SAVED_MANPATH" && unset _KERL_SAVED_MANPATH; test \$?_KERL_SAVED_REBAR_PLT_DIR != 0 && setenv REBAR_PLT_DIR "\$_KERL_SAVED_REBAR_PLT_DIR" && unset _KERL_SAVED_REBAR_PLT_DIR; test \$?_KERL_ACTIVE_DIR != 0 && unset _KERL_ACTIVE_DIR; test \$?_KERL_DOCSH_USER_DEFAULT != 0 && unsetenv DOCSH_USER_DEFAULT && unset _KERL_DOCSH_USER_DEFAULT; test \$?_KERL_ERL_CALL_REMOVABLE != 0 && unset _KERL_ERL_CALL_REMOVABLE; test \$?_KERL_DOCSH_DOT_ERLANG != 0 && rm "\$HOME/.erlang" && unset _KERL_DOCSH_DOT_ERLANG; test \$?_KERL_SAVED_PROMPT != 0 && set prompt="\$_KERL_SAVED_PROMPT" && unset _KERL_SAVED_PROMPT; test "!:*" != "nondestructive" && unalias deactivate'
1229
1230# Unset irrelevant variables.
1231kerl_deactivate nondestructive
1232
1233if ( \$?REBAR_PLT_DIR ) then
1234    set _KERL_SAVED_REBAR_PLT_DIR = "\$REBAR_PLT_DIR"
1235else
1236    set _KERL_SAVED_REBAR_PLT_DIR=""
1237endif
1238
1239set _KERL_PATH_REMOVABLE = "$absdir/bin"
1240set _KERL_SAVED_PATH = "\$PATH"
1241setenv PATH "\${_KERL_PATH_REMOVABLE}:\$PATH"
1242
1243if ( ! \$?MANPATH ) then
1244    set MANPATH = ""
1245endif
1246set _KERL_MANPATH_REMOVABLE = "$absdir/lib/erlang/man:$absdir/man"
1247set _KERL_SAVED_MANPATH = "\$MANPATH"
1248setenv MANPATH "\${_KERL_MANPATH_REMOVABLE}:\$MANPATH"
1249
1250setenv REBAR_PLT_DIR "$absdir"
1251
1252set _KERL_ACTIVE_DIR = "$absdir"
1253
1254set _KERL_ERL_CALL_REMOVABLE = $(\find "$absdir" -type d -wholename "*erl_interface*/bin")
1255setenv PATH "\${_KERL_ERL_CALL_REMOVABLE}:\$PATH"
1256
1257if ( -f "$KERL_CONFIG.csh" ) then
1258    source "$KERL_CONFIG.csh"
1259endif
1260
1261if ( \$?KERL_ENABLE_PROMPT ) then
1262    set _KERL_SAVED_PROMPT = "\$prompt"
1263
1264    if ( \$?KERL_PROMPT_FORMAT ) then
1265        set FRMT = "\$KERL_PROMPT_FORMAT"
1266    else
1267        set FRMT = "(%BUILDNAME%)"
1268    endif
1269
1270    set PROMPT = \$(echo "\$FRMT" | sed 's^%RELEASE%^$rel^;s^%BUILDNAME%^$1^')
1271    set prompt = "\$PROMPT\$prompt"
1272endif
1273
1274if ( -d "$absdir/lib/docsh" ) then
1275    setenv DOCSH_USER_DEFAULT "$absdir/lib/docsh/user_default"
1276    set _KERL_DOCSH_USER_DEFAULT = "yes"
1277    if ( -f "\$HOME/.erlang" ) then
1278        if ( \$?KERL_DOCSH_DOT_ERLANG == 0 ) then
1279            echo "Couldn't symlink correct \$HOME/.erlang - file exists - docsh might not work."
1280            echo "Please make sure \$HOME/.erlang contains code"
1281            echo "from $absdir/lib/docsh/dot.erlang"
1282            echo "and export KERL_DOCSH_DOT_ERLANG=exists to suppress this warning."
1283        endif
1284    else
1285        ln -s "$absdir/lib/docsh/dot.erlang" "\$HOME/.erlang"
1286        set _KERL_DOCSH_DOT_ERLANG = "yes"
1287    endif
1288endif
1289
1290rehash
1291ACTIVATE_CSH
1292
1293    if [ -n "$KERL_BUILD_DOCS" ]; then
1294        if ! (ERL_TOP="$ERL_TOP" make release_docs "DOC_TARGETS=$KERL_DOC_TARGETS" "RELEASE_ROOT=$absdir" >/dev/null 2>&1); then
1295            echo "Couldn't install docs for Erlang/OTP $rel ($1) in $absdir"
1296            exit 1
1297        fi
1298    else
1299        if [ "$KERL_BUILD_BACKEND" = 'tarball' ]; then
1300            if [ "$rel" != 'git' ]; then
1301                if [ -n "$KERL_INSTALL_MANPAGES" ]; then
1302                    echo 'Fetching and installing manpages...'
1303                    download_manpages "$rel"
1304                fi
1305
1306                if [ -n "$KERL_INSTALL_HTMLDOCS" ]; then
1307                    echo 'Fetching and installing HTML docs...'
1308                    download_htmldocs "$rel"
1309                fi
1310            fi
1311        fi
1312    fi
1313
1314    KERL_CONFIG_STORAGE_PATH="$KERL_BUILD_DIR/$1/otp_src_$rel/$KERL_CONFIG_STORAGE_FILENAME"
1315    [ -e "$KERL_CONFIG_STORAGE_PATH" ] && cp "$KERL_CONFIG_STORAGE_PATH" "$absdir/$KERL_CONFIG_STORAGE_FILENAME"
1316
1317    if [ -n "$KERL_BUILD_PLT" ]; then
1318        echo 'Building Dialyzer PLT...'
1319        build_plt "$absdir"
1320    fi
1321
1322    if command -v apk >/dev/null 2>&1; then
1323        # Running on Alpine Linux, assuming non-exotic shell
1324        SHELL_SUFFIX=''
1325    else
1326        PID=$$
1327        PARENT_PID=$(\ps -p $PID -o ppid=) || exit 1
1328        # shellcheck disable=SC2086
1329        PARENT_CMD=$(\ps -p $PARENT_PID -o ucomm | tail -n 1)
1330        case "$PARENT_CMD" in
1331            fish)
1332                SHELL_SUFFIX='.fish'
1333                ;;
1334            csh)
1335                SHELL_SUFFIX='.csh'
1336                ;;
1337            *)
1338                SHELL_SUFFIX=''
1339                ;;
1340        esac
1341    fi
1342
1343    echo 'You can activate this installation running the following command:'
1344    echo ". $absdir/activate$SHELL_SUFFIX"
1345    echo 'Later on, you can leave the installation typing:'
1346    echo 'kerl_deactivate'
1347}
1348
1349install_docsh() {
1350    REPO_URL=$DOCSH_GITHUB_URL
1351    GIT=$(printf '%s' $REPO_URL | $MD5SUM | cut -d' ' -f $MD5SUM_FIELD)
1352    BUILDNAME="$1"
1353    DOCSH_DIR="$KERL_BUILD_DIR/$BUILDNAME/docsh"
1354    DOCSH_REF='0.7.1'
1355    ACTIVE_PATH="$2"
1356
1357    OTP_VERSION=$(get_otp_version "$1")
1358    # This has to be updated with docsh updates
1359    DOCSH_SUPPORTED='^1[9]\|2[01]$'
1360    if ! echo "$OTP_VERSION" | \grep "$DOCSH_SUPPORTED" >/dev/null 2>&1; then
1361        echo "Erlang/OTP version $OTP_VERSION not supported by docsh (does not match regex $DOCSH_SUPPORTED)"
1362        exit 1
1363    fi
1364
1365    mkdir -p "$KERL_GIT_DIR" || exit 1
1366    cd "$KERL_GIT_DIR" || exit 1
1367    echo "Checking out docsh git repository from $REPO_URL..."
1368    if [ ! -d "$GIT" ]; then
1369        if ! git clone -q --mirror "$REPO_URL" "$GIT" >/dev/null 2>&1; then
1370            echo 'Error mirroring remote git repository'
1371            exit 1
1372        fi
1373    fi
1374    cd "$GIT" || exit 1
1375    if ! git remote update --prune >/dev/null 2>&1; then
1376        echo 'Error updating remote git repository'
1377        exit 1
1378    fi
1379
1380    rm -Rf "$DOCSH_DIR"
1381    mkdir -p "$DOCSH_DIR" || exit 1
1382    cd "$DOCSH_DIR" || exit 1
1383    if ! git clone -l "$KERL_GIT_DIR/$GIT" "$DOCSH_DIR" >/dev/null 2>&1; then
1384        echo 'Error cloning local git repository'
1385        exit 1
1386    fi
1387    cd "$DOCSH_DIR" || exit 1
1388    if ! git checkout "$DOCSH_REF" >/dev/null 2>&1; then
1389        if ! git checkout -b "$DOCSH_REF" "$DOCSH_REF" >/dev/null 2>&1; then
1390            echo 'Could not checkout specified version'
1391            rm -Rf "$DOCSH_DIR"
1392            exit 1
1393        fi
1394    fi
1395
1396    if ! ./rebar3 compile; then
1397        echo 'Could not compile docsh'
1398        rm -Rf "$DOCSH_DIR"
1399        exit 1
1400    fi
1401
1402    ## Install docsh
1403    if [ -f "$ACTIVE_PATH"/lib/docsh ]; then
1404        echo "Couldn't install $ACTIVE_PATH/lib/docsh - the directory already exists"
1405        rm -Rf "$DOCSH_DIR"
1406        exit 1
1407    else
1408        cp -R "$DOCSH_DIR"/_build/default/lib/docsh "$ACTIVE_PATH"/lib/
1409    fi
1410    ## Prepare dot.erlang for linking as $HOME/.erlang
1411    if [ -f "$ACTIVE_PATH"/lib/docsh/dot.erlang ]; then
1412        echo "Couldn't install $ACTIVE_PATH/lib/docsh/dot.erlang - the file already exists"
1413        rm -Rf "$DOCSH_DIR"
1414        exit 1
1415    else
1416        cat "$DOCSH_DIR"/templates/dot.erlang >"$ACTIVE_PATH"/lib/docsh/dot.erlang
1417    fi
1418    ## Warn if $HOME/.erlang exists
1419    if [ -f "$HOME"/.erlang ]; then
1420        echo "$HOME/.erlang exists - kerl won't be able to symlink a docsh-compatible version."
1421        echo "Please make sure your $HOME/.erlang contains code"
1422        echo "from $ACTIVE_PATH/lib/docsh/dot.erlang"
1423        echo 'and export KERL_DOCSH_DOT_ERLANG=exists to suppress further warnings'
1424    fi
1425    ## Install docsh user_default
1426    if [ -f "$ACTIVE_PATH"/lib/docsh/user_default.beam ]; then
1427        echo "Couldn't install $ACTIVE_PATH/lib/docsh/user_default.beam - the file already exists"
1428        rm -Rf "$DOCSH_DIR"
1429        exit 1
1430    else
1431        erlc -I "$DOCSH_DIR"/include -o "$ACTIVE_PATH"/lib/docsh/ "$DOCSH_DIR"/templates/user_default.erl
1432    fi
1433}
1434
1435download_manpages() {
1436    FILENAME=otp_doc_man_$1.tar.gz
1437    tarball_download "$FILENAME"
1438    echo 'Extracting manpages'
1439    cd "$absdir" && tar xzf "$KERL_DOWNLOAD_DIR/$FILENAME"
1440}
1441
1442download_htmldocs() {
1443    FILENAME=otp_doc_html_"$1".tar.gz
1444    tarball_download "$FILENAME"
1445    echo 'Extracting HTML docs'
1446    (cd "$absdir" && mkdir -p html && tar -C "$absdir"/html -xzf "$KERL_DOWNLOAD_DIR/$FILENAME")
1447}
1448
1449build_plt() {
1450    dialyzerd="$1"/dialyzer
1451    mkdir -p "$dialyzerd" || exit 1
1452    plt="$dialyzerd"/plt
1453    build_log="$dialyzerd"/build.log
1454    dirs=$(\find "$1"/lib -maxdepth 2 -name ebin -type d -exec dirname {} \;)
1455    apps=$(for app in $dirs; do basename "$app" | cut -d- -f1 ; done | \grep -Ev 'erl_interface|jinterface' | xargs echo)
1456    # shellcheck disable=SC2086
1457    "$1"/bin/dialyzer --output_plt "$plt" --build_plt --apps $apps >>"$build_log" 2>&1
1458    status=$?
1459    if [ $status -eq 0 ] || [ $status -eq 2 ]; then
1460        echo "Done building $plt"
1461        return 0
1462    else
1463        echo "Error building PLT, see $build_log for details"
1464        return 1
1465    fi
1466}
1467
1468do_plt() {
1469    ACTIVE_PATH="$1"
1470    if [ -n "$ACTIVE_PATH" ]; then
1471        plt="$ACTIVE_PATH"/dialyzer/plt
1472        if [ -f "$plt" ]; then
1473            echo 'Dialyzer PLT for the active installation is:'
1474            echo "$plt"
1475            return 0
1476        else
1477            echo 'There is no Dialyzer PLT for the active installation'
1478            return 1
1479        fi
1480    else
1481        echo 'No Erlang/OTP installation is currently active'
1482        return 2
1483    fi
1484}
1485
1486print_buildopts() {
1487    buildopts="$1/$KERL_CONFIG_STORAGE_FILENAME"
1488    if [ -f "$buildopts" ]; then
1489        echo 'The build options for the active installation are:'
1490        cat "$buildopts"
1491        return 0
1492    else
1493        echo 'The build options for the active installation are not available.'
1494        return 1
1495    fi
1496}
1497
1498do_deploy() {
1499    if [ -z "$1" ]; then
1500        echo 'No host given'
1501        exit 1
1502    fi
1503    host="$1"
1504
1505    assert_valid_installation "$2"
1506    rel="$(get_name_from_install_path "$2")"
1507    path="$2"
1508    remotepath="$path"
1509
1510    if [ -n "$3" ]; then
1511        remotepath="$3"
1512    fi
1513
1514    # shellcheck disable=SC2086
1515    if ! ssh $KERL_DEPLOY_SSH_OPTIONS "$host" true >/dev/null 2>&1; then
1516        echo "Couldn't ssh to $host"
1517        exit 1
1518    fi
1519
1520    echo "Cloning Erlang/OTP $rel ($path) to $host ($remotepath) ..."
1521
1522    # shellcheck disable=SC2086
1523    if ! rsync -aqz -e "ssh $KERL_DEPLOY_SSH_OPTIONS" $KERL_DEPLOY_RSYNC_OPTIONS "$path/" "$host:$remotepath/"; then
1524        echo "Couldn't rsync Erlang/OTP $rel ($path) to $host ($remotepath)"
1525        exit 1
1526    fi
1527
1528    # shellcheck disable=SC2086,SC2029
1529    if ! ssh $KERL_DEPLOY_SSH_OPTIONS "$host" "cd \"$remotepath\" && env ERL_TOP=\"\$(pwd)\" ./Install $INSTALL_OPT \"\$(pwd)\" >/dev/null 2>&1"; then
1530        echo "Couldn't install Erlang/OTP $rel to $host ($remotepath)"
1531        exit 1
1532    fi
1533
1534    # shellcheck disable=SC2086,SC2029
1535    if ! ssh $KERL_DEPLOY_SSH_OPTIONS "$host" "cd \"$remotepath\" && sed -i -e \"s#$path#\"\$(pwd)\"#g\" activate"; then
1536        echo "Couldn't completely install Erlang/OTP $rel to $host ($remotepath)"
1537        exit 1
1538    fi
1539
1540    echo "On $host, you can activate this installation running the following command:"
1541    echo ". $remotepath/activate"
1542    echo 'Later on, you can leave the installation typing:'
1543    echo 'kerl_deactivate'
1544}
1545
1546
1547# Quoted from https://github.com/mkropat/sh-realpath
1548# LICENSE: MIT
1549
1550realpath() {
1551    canonicalize_path "$(resolve_symlinks "$1")"
1552}
1553
1554resolve_symlinks() {
1555    _resolve_symlinks "$1"
1556}
1557
1558_resolve_symlinks() {
1559    _assert_no_path_cycles "$@" || return
1560
1561    if path=$(readlink -- "$1"); then
1562        dir_context=$(dirname -- "$1")
1563        _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@"
1564    else
1565        printf '%s\n' "$1"
1566    fi
1567}
1568
1569_prepend_dir_context_if_necessary() {
1570    if [ "$1" = . ]; then
1571        printf '%s\n' "$2"
1572    else
1573        _prepend_path_if_relative "$1" "$2"
1574    fi
1575}
1576
1577_prepend_path_if_relative() {
1578    case "$2" in
1579        /* ) printf '%s\n' "$2" ;;
1580         * ) printf '%s\n' "$1/$2" ;;
1581    esac
1582}
1583
1584_assert_no_path_cycles() {
1585    target=$1
1586    shift
1587
1588    for path in "$@"; do
1589        if [ "$path" = "$target" ]; then
1590            return 1
1591        fi
1592    done
1593}
1594
1595canonicalize_path() {
1596    if [ -d "$1" ]; then
1597        _canonicalize_dir_path "$1"
1598    else
1599        _canonicalize_file_path "$1"
1600    fi
1601}
1602
1603_canonicalize_dir_path() {
1604    (cd "$1" 2>/dev/null && pwd -P)
1605}
1606
1607_canonicalize_file_path() {
1608    dir=$(dirname -- "$1")
1609    file=$(basename -- "$1")
1610    (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
1611}
1612
1613# END QUOTE
1614
1615is_valid_install_path() {
1616    # don't allow installs into .erlang because
1617    # it's a special configuration file location for OTP
1618    if [ "$(basename -- "$1")" = '.erlang' ]; then
1619        echo 'ERROR: You cannot install a build into .erlang. (It is a special configuration file location for OTP.)'
1620        return 1
1621    fi
1622
1623    candidate=$(realpath "$1")
1624    canonical_home=$(realpath "$HOME")
1625    canonical_base_dir=$(realpath "$KERL_BASE_DIR")
1626
1627    # don't allow installs into home directory
1628    if [ "$candidate" = "$canonical_home" ]; then
1629        echo "ERROR: You cannot install a build into $HOME. It's a really bad idea."
1630        return 1
1631    fi
1632
1633    # don't install into our base directory either.
1634    if [ "$candidate" = "$canonical_base_dir" ]; then
1635        echo "ERROR: You cannot install a build into $KERL_BASE_DIR."
1636        return 1
1637    fi
1638
1639    INSTALLED_NAME=$(get_name_from_install_path "$candidate")
1640    if [ -n "$INSTALLED_NAME" ]; then
1641        echo "ERROR: Installation ($INSTALLED_NAME) already registered for this location ($1)"
1642        return 1
1643    fi
1644
1645    # if the install directory exists,
1646    # do not allow installs into a directory that is not empty
1647    if [ -e "$1" ]; then
1648        if [ ! -d "$1" ]; then
1649            echo "ERROR: $1 is not a directory."
1650            return 1
1651        else
1652            count=$(\find "$1" | wc -l)
1653            if [ "$count" -ne 1 ]; then
1654                echo "ERROR: $1 does not appear to be an empty directory."
1655                return 1
1656            fi
1657        fi
1658    fi
1659
1660    return 0
1661}
1662
1663maybe_remove() {
1664    candidate=$(realpath "$1")
1665    canonical_home=$(realpath "$HOME")
1666
1667    if [ "$candidate" = "$canonical_home" ]; then
1668        echo "WARNING: You cannot remove an install from $HOME; it's your home directory."
1669        return 0
1670    fi
1671
1672    ACTIVE_PATH="$(get_active_path)"
1673    if [ "$candidate" = "$ACTIVE_PATH" ]; then
1674	echo 'ERROR: You cannot delete the active installation. Deactivate it first.'
1675	exit 1
1676    fi
1677
1678    rm -Rf "$1"
1679}
1680
1681list_print() {
1682    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
1683        if [ "$(\wc -l "$KERL_BASE_DIR/otp_$1")" != '0' ]; then
1684            cat "$KERL_BASE_DIR/otp_$1"
1685            return 0
1686        fi
1687    fi
1688    echo "There are no $1 available"
1689}
1690
1691list_add() {
1692    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
1693        while read -r l; do
1694            if [ "$l" = "$2" ]; then
1695                return 1
1696            fi
1697        done <"$KERL_BASE_DIR/otp_$1"
1698        echo "$2" >>"$KERL_BASE_DIR/otp_$1" || exit 1
1699    else
1700        echo "$2" >"$KERL_BASE_DIR/otp_$1" || exit 1
1701    fi
1702}
1703
1704list_remove() {
1705    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
1706        sed $SED_OPT -i -e "/^.*$2$/d" "$KERL_BASE_DIR/otp_$1" || exit 1
1707    fi
1708}
1709
1710list_has() {
1711    if [ -f "$KERL_BASE_DIR/otp_$1" ]; then
1712        \grep "$2" "$KERL_BASE_DIR/otp_$1" >/dev/null 2>&1 && return 0
1713    fi
1714    return 1
1715}
1716
1717path_usage() {
1718    echo "usage: $0 path [<install_name>]"
1719}
1720
1721list_usage() {
1722    echo "usage: $0 list <releases|builds|installations>"
1723}
1724
1725delete_usage() {
1726    echo "usage: $0 delete <build|installation> <build_name or path>"
1727}
1728
1729cleanup_usage() {
1730    echo "usage: $0 cleanup <build_name|all>"
1731}
1732
1733update_usage() {
1734    echo "usage: $0 update releases"
1735}
1736
1737get_active_path() {
1738    if [ -n "$_KERL_ACTIVE_DIR" ]; then
1739        echo "$_KERL_ACTIVE_DIR"
1740    fi
1741    return 0
1742}
1743
1744get_name_from_install_path() {
1745    if [ -f "$KERL_BASE_DIR"/otp_installations ]; then
1746        \grep -m1 -E "$1$" "$KERL_BASE_DIR"/otp_installations | cut -d' ' -f1
1747    fi
1748    return 0
1749}
1750
1751get_install_path_from_name() {
1752    if [ -f "$KERL_BASE_DIR"/otp_installations ]; then
1753        \grep -m1 -E "$1$" "$KERL_BASE_DIR"/otp_installations | cut -d' ' -f2
1754    fi
1755    return 0
1756}
1757
1758do_active() {
1759    ACTIVE_PATH="$(get_active_path)"
1760    if [ -n "$ACTIVE_PATH" ]; then
1761        echo 'The current active installation is:'
1762        echo "$ACTIVE_PATH"
1763        return 0
1764    else
1765        echo 'No Erlang/OTP installation is currently active'
1766        return 1
1767    fi
1768}
1769
1770make_filename() {
1771    release=$(get_otp_version "$1")
1772    if [ "$release" -ge 17 ]; then
1773        echo "OTP-$1"
1774    else
1775        echo "OTP_$1"
1776    fi
1777}
1778
1779download() {
1780    mkdir -p "$KERL_DOWNLOAD_DIR" || exit 1
1781    if [ "$KERL_BUILD_BACKEND" = 'git' ]; then
1782        FILENAME=$(make_filename "$1")
1783        github_download "$1" "$FILENAME".tar.gz
1784    else
1785        FILENAME="otp_src_$1"
1786        tarball_download "$FILENAME".tar.gz
1787    fi
1788}
1789
1790github_download() {
1791    tarball_file="$KERL_DOWNLOAD_DIR/$2"
1792    tarball_url="$OTP_GITHUB_URL/archive/$2"
1793    prebuilt_url="$OTP_GITHUB_URL/releases/download/OTP-$1/otp_src_$1.tar.gz"
1794    if curl --silent --location --fail -I $prebuilt_url > /dev/null; then
1795        tarball_url=$prebuilt_url
1796        unset KERL_USE_AUTOCONF
1797    fi
1798    # if the file doesn't exist or the file has no size
1799    if [ ! -s $tarball_file ]; then
1800        echo "Downloading $1 to $KERL_DOWNLOAD_DIR..."
1801        curl -f -L -o $tarball_file $tarball_url || exit 1
1802    else
1803        # If the downloaded tarball was corrupted due to interruption while
1804        # downloading.
1805        if ! gunzip -t $tarball_file 2>/dev/null; then
1806            echo "$tarball_file corrupted and redownloading..."
1807            rm -rf $tarball_file
1808            curl -f -L -o $tarball_file $tarball_url || exit 1
1809        fi
1810    fi
1811}
1812
1813tarball_download() {
1814    if [ ! -s "$KERL_DOWNLOAD_DIR/$1" ]; then
1815        echo "Downloading $1 to $KERL_DOWNLOAD_DIR"
1816        curl -f -L -o "$KERL_DOWNLOAD_DIR/$1" "$ERLANG_DOWNLOAD_URL/$1" || exit 1
1817        update_checksum_file
1818    fi
1819    ensure_checksum_file
1820    echo 'Verifying archive checksum...'
1821    SUM="$($MD5SUM "$KERL_DOWNLOAD_DIR/$1" | cut -d' ' -f $MD5SUM_FIELD)"
1822    ORIG_SUM="$(\grep -F "$1" "$KERL_DOWNLOAD_DIR"/MD5 | cut -d' ' -f2)"
1823    if [ "$SUM" != "$ORIG_SUM" ]; then
1824        echo "Checksum error, check the files in $KERL_DOWNLOAD_DIR"
1825        exit 1
1826    fi
1827    echo "Checksum verified ($SUM)"
1828}
1829
1830apply_solaris_networking_patch() {
1831    patch -p1 <<_END_PATCH
1832--- otp-a/erts/emulator/drivers/common/inet_drv.c
1833+++ otp-b/erts/emulator/drivers/common/inet_drv.c
1834@@ -4166,16 +4166,7 @@
1835 	    break;
1836
1837 	case INET_IFOPT_HWADDR: {
1838-#ifdef SIOCGIFHWADDR
1839-	    if (ioctl(desc->s, SIOCGIFHWADDR, (char *)&ifreq) < 0)
1840-		break;
1841-	    buf_check(sptr, s_end, 1+2+IFHWADDRLEN);
1842-	    *sptr++ = INET_IFOPT_HWADDR;
1843-	    put_int16(IFHWADDRLEN, sptr); sptr += 2;
1844-	    /* raw memcpy (fix include autoconf later) */
1845-	    sys_memcpy(sptr, (char*)(&ifreq.ifr_hwaddr.sa_data), IFHWADDRLEN);
1846-	    sptr += IFHWADDRLEN;
1847-#elif defined(SIOCGENADDR)
1848+#if defined(SIOCGENADDR)
1849 	    if (ioctl(desc->s, SIOCGENADDR, (char *)&ifreq) < 0)
1850 		break;
1851 	    buf_check(sptr, s_end, 1+2+sizeof(ifreq.ifr_enaddr));
1852_END_PATCH
1853}
1854
1855apply_darwin_compiler_patch() {
1856    patch -p0 <<_END_PATCH
1857--- erts/emulator/beam/beam_bp.c.orig	2011-10-03 13:12:07.000000000 -0500
1858+++ erts/emulator/beam/beam_bp.c	2013-10-04 13:42:03.000000000 -0500
1859@@ -496,7 +496,8 @@
1860 }
1861
1862 /* bp_hash */
1863-ERTS_INLINE Uint bp_sched2ix() {
1864+#ifndef ERTS_DO_INCL_GLB_INLINE_FUNC_DEF
1865+ERTS_GLB_INLINE Uint bp_sched2ix() {
1866 #ifdef ERTS_SMP
1867     ErtsSchedulerData *esdp;
1868     esdp = erts_get_scheduler_data();
1869@@ -505,6 +506,7 @@
1870     return 0;
1871 #endif
1872 }
1873+#endif
1874 static void bp_hash_init(bp_time_hash_t *hash, Uint n) {
1875     Uint size = sizeof(bp_data_time_item_t)*n;
1876     Uint i;
1877--- erts/emulator/beam/beam_bp.h.orig	2011-10-03 13:12:07.000000000 -0500
1878+++ erts/emulator/beam/beam_bp.h	2013-10-04 13:42:08.000000000 -0500
1879@@ -144,7 +144,19 @@
1880 #define ErtsSmpBPUnlock(BDC)
1881 #endif
1882
1883-ERTS_INLINE Uint bp_sched2ix(void);
1884+ERTS_GLB_INLINE Uint bp_sched2ix(void);
1885+
1886+#ifdef ERTS_DO_INCL_GLB_INLINE_FUNC_DEF
1887+ERTS_GLB_INLINE Uint bp_sched2ix() {
1888+#ifdef ERTS_SMP
1889+    ErtsSchedulerData *esdp;
1890+    esdp = erts_get_scheduler_data();
1891+    return esdp->no - 1;
1892+#else
1893+    return 0;
1894+#endif
1895+}
1896+#endif
1897
1898 #ifdef ERTS_SMP
1899 #define bp_sched2ix_proc(p) ((p)->scheduler_data->no - 1)
1900_END_PATCH
1901}
1902
1903# javadoc 8 includes always-enabled document linting which causes
1904# documentation builds to fail on older OTP releases.
1905apply_javadoc_linting_patch() {
1906    # The _END_PATCH token is quoted below to disable parameter substitution
1907    patch -p0 <<'_END_PATCH'
1908--- lib/jinterface/doc/src/Makefile.orig	2016-05-23 14:34:48.000000000 -0500
1909+++ lib/jinterface/doc/src/Makefile	2016-05-23 14:35:48.000000000 -0500
1910@@ -142,7 +142,7 @@
1911 	rm -f errs core *~
1912
1913 jdoc:$(JAVA_SRC_FILES)
1914-	(cd ../../java_src;$(JAVADOC) -sourcepath . -d $(JAVADOC_DEST) \
1915+	(cd ../../java_src;$(JAVADOC) -Xdoclint:none -sourcepath . -d $(JAVADOC_DEST) \
1916 		-windowtitle $(JAVADOC_TITLE) $(JAVADOC_PKGS))
1917
1918 man:
1919_END_PATCH
1920}
1921
1922# perl 5.24 fatalizes the warning this causes
1923apply_r14_beam_makeops_patch() {
1924    patch -p0 <<'_END_PATCH'
1925--- erts/emulator/utils/beam_makeops.orig	2016-05-23 21:40:42.000000000 -0500
1926+++ erts/emulator/utils/beam_makeops	2016-05-23 21:41:08.000000000 -0500
1927@@ -1576,7 +1576,7 @@
1928 	if $min_window{$key} > $min_window;
1929
1930     pop(@{$gen_transform{$key}})
1931-	if defined @{$gen_transform{$key}}; # Fail
1932+	if defined $gen_transform{$key}; # Fail
1933     my(@prefix) = (&make_op($comment), &make_op('', 'try_me_else', &tr_code_len(@code)));
1934     unshift(@code, @prefix);
1935     push(@{$gen_transform{$key}}, @code, &make_op('', 'fail'));
1936_END_PATCH
1937}
1938
1939# https://github.com/erlang/otp/commit/21ca6d3a137034f19862db769a5b7f1c5528dbc4.diff
1940apply_r15_beam_makeops_patch() {
1941    patch -p1 <<'_END_PATCH'
1942--- a/erts/emulator/utils/beam_makeops
1943+++ b/erts/emulator/utils/beam_makeops
1944@@ -1711,7 +1711,7 @@ sub tr_gen_to {
1945     my $prev_last;
1946     $prev_last = pop(@{$gen_transform{$key}})
1947-	if defined @{$gen_transform{$key}}; # Fail
1948+	if defined $gen_transform{$key}; # Fail
1949
1950     if ($prev_last && !is_instr($prev_last, 'fail')) {
1951 	error("Line $line: A previous transformation shadows '$orig_transform'");
1952_END_PATCH
1953}
1954
1955#https://github.com/erlang/otp/commit/a64c4d806fa54848c35632114585ad82b98712e8.diff
1956apply_wx_ptr_patch() {
1957    patch -p1 <<'_END_PATCH'
1958diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
1959index 0d2da5d4a79..8118136d30e 100644
1960--- a/lib/wx/c_src/wxe_impl.cpp
1961+++ b/lib/wx/c_src/wxe_impl.cpp
1962@@ -666,7 +666,7 @@ void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) {
1963     throw wxe_badarg(index);
1964   }
1965   void * temp = memenv->ref2ptr[index];
1966-  if((index < memenv->next) && ((index == 0) || (temp > NULL)))
1967+  if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL)))
1968     return temp;
1969   else {
1970     throw wxe_badarg(index);
1971@@ -678,7 +678,7 @@ void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) {
1972   if(!memenv)
1973     throw wxe_badarg(index);
1974   void * temp = memenv->ref2ptr[index];
1975-  if((index < memenv->next) && ((index == 0) || (temp > NULL))) {
1976+  if((index < memenv->next) && ((index == 0) || (temp != (void *) NULL))) {
1977     ptrMap::iterator it;
1978     it = ptr2ref.find(temp);
1979     if(it != ptr2ref.end()) {
1980_END_PATCH
1981}
1982
1983apply_r16_wx_ptr_patch() {
1984    patch -p1 <<'_END_PATCH'
1985diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp
1986index cc9bcc995..1b1912630 100644
1987--- a/lib/wx/c_src/wxe_impl.cpp
1988+++ b/lib/wx/c_src/wxe_impl.cpp
1989@@ -757,7 +757,7 @@ void * WxeApp::getPtr(char * bp, wxeMemEnv *memenv) {
1990     throw wxe_badarg(index);
1991   }
1992   void * temp = memenv->ref2ptr[index];
1993-  if((index < memenv->next) && ((index == 0) || (temp > NULL)))
1994+  if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL)))
1995     return temp;
1996   else {
1997     throw wxe_badarg(index);
1998@@ -769,7 +769,7 @@ void WxeApp::registerPid(char * bp, ErlDrvTermData pid, wxeMemEnv * memenv) {
1999   if(!memenv)
2000     throw wxe_badarg(index);
2001   void * temp = memenv->ref2ptr[index];
2002-  if((index < memenv->next) && ((index == 0) || (temp > NULL))) {
2003+  if((index < memenv->next) && ((index == 0) || (temp != (void *)NULL))) {
2004     ptrMap::iterator it;
2005     it = ptr2ref.find(temp);
2006     if(it != ptr2ref.end()) {
2007_END_PATCH
2008}
2009
2010# https://github.com/erlang/otp/commit/e27119948fc6ab28bea81019720bddaac5b655a7.patch
2011apply_zlib_patch()
2012{
2013
2014    patch -p1 <<'_END_PATCH'
2015diff --git a/erts/emulator/beam/external.c b/erts/emulator/beam/external.c
2016index 656de7c49ad..4491d486837 100644
2017--- a/erts/emulator/beam/external.c
2018+++ b/erts/emulator/beam/external.c
2019@@ -1193,6 +1193,7 @@ typedef struct B2TContext_t {
2020     } u;
2021 } B2TContext;
2022
2023+static B2TContext* b2t_export_context(Process*, B2TContext* src);
2024
2025 static uLongf binary2term_uncomp_size(byte* data, Sint size)
2026 {
2027@@ -1225,7 +1226,7 @@ static uLongf binary2term_uncomp_size(byte* data, Sint size)
2028
2029 static ERTS_INLINE int
2030 binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size,
2031-		    B2TContext* ctx)
2032+		    B2TContext** ctxp, Process* p)
2033 {
2034     byte *bytes = data;
2035     Sint size = data_size;
2036@@ -1239,8 +1240,8 @@ binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size,
2037     size--;
2038     if (size < 5 || *bytes != COMPRESSED) {
2039 	state->extp = bytes;
2040-        if (ctx)
2041-	    ctx->state = B2TSizeInit;
2042+        if (ctxp)
2043+	    (*ctxp)->state = B2TSizeInit;
2044     }
2045     else  {
2046 	uLongf dest_len = (Uint32) get_int32(bytes+1);
2047@@ -1257,16 +1258,26 @@ binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size,
2048                 return -1;
2049 	    }
2050 	    state->extp = erts_alloc(ERTS_ALC_T_EXT_TERM_DATA, dest_len);
2051-            ctx->reds -= dest_len;
2052+            if (ctxp)
2053+                (*ctxp)->reds -= dest_len;
2054 	}
2055 	state->exttmp = 1;
2056-        if (ctx) {
2057+        if (ctxp) {
2058+            /*
2059+             * Start decompression by exporting trap context
2060+             * so we don't have to deal with deep-copying z_stream.
2061+             */
2062+            B2TContext* ctx = b2t_export_context(p, *ctxp);
2063+            ASSERT(state = &(*ctxp)->b2ts);
2064+            state = &ctx->b2ts;
2065+
2066 	    if (erl_zlib_inflate_start(&ctx->u.uc.stream, bytes, size) != Z_OK)
2067 		return -1;
2068
2069 	    ctx->u.uc.dbytes = state->extp;
2070 	    ctx->u.uc.dleft = dest_len;
2071 	    ctx->state = B2TUncompressChunk;
2072+            *ctxp = ctx;
2073         }
2074 	else {
2075 	    uLongf dlen = dest_len;
2076@@ -1308,7 +1319,7 @@ erts_binary2term_prepare(ErtsBinary2TermState *state, byte *data, Sint data_size
2077 {
2078     Sint res;
2079
2080-    if (binary2term_prepare(state, data, data_size, NULL) < 0 ||
2081+    if (binary2term_prepare(state, data, data_size, NULL, NULL) < 0 ||
2082         (res=decoded_size(state->extp, state->extp + state->extsize, 0, NULL)) < 0) {
2083
2084         if (state->exttmp)
2085@@ -1435,7 +1446,7 @@ static Eterm binary_to_term_int(Process* p, Uint32 flags, Eterm bin, Binary* con
2086             if (ctx->aligned_alloc) {
2087                 ctx->reds -= bin_size / 8;
2088             }
2089-            if (binary2term_prepare(&ctx->b2ts, bytes, bin_size, ctx) < 0) {
2090+            if (binary2term_prepare(&ctx->b2ts, bytes, bin_size, &ctx, p) < 0) {
2091 		ctx->state = B2TBadArg;
2092 	    }
2093             break;
2094_END_PATCH
2095}
2096
2097# https://github.com/erlang/otp/commit/4b0467c.patch
2098apply_in6addr_test_patch()
2099{
2100
2101    patch -p1 <<'_END_PATCH'
2102diff --git a/erts/configure.in b/erts/configure.in
2103index caa1ce568b..6ebb3d3a25 100644
2104--- a/erts/configure.in
2105+++ b/erts/configure.in
2106@@ -2191,6 +2191,7 @@ AC_CACHE_CHECK(
2107 		#include <sys/types.h>
2108 		#include <sys/socket.h>
2109 		#include <netinet/in.h>
2110+		#include <stdio.h>
2111 	    ]],
2112 	    [[printf("%d", in6addr_any.s6_addr[16]);]]
2113 	)],
2114@@ -2214,6 +2215,7 @@ AC_CACHE_CHECK(
2115 		#include <sys/types.h>
2116 		#include <sys/socket.h>
2117 		#include <netinet/in.h>
2118+		#include <stdio.h>
2119 	    ]],
2120 	    [[printf("%d", in6addr_loopback.s6_addr[16]);]]
2121 	)],
2122_END_PATCH
2123}
2124
2125case "$1" in
2126    version)
2127        echo "$KERL_VERSION"
2128        exit 0
2129        ;;
2130    build)
2131        if [ "$2" = 'git' ]; then
2132            if [ $# -ne 5 ]; then
2133                echo "usage: $0 $1 $2 <git_url> <git_version> <build_name>"
2134                exit 1
2135            fi
2136            do_git_build "$3" "$4" "$5"
2137        else
2138            if [ $# -eq 2 ]; then
2139                do_normal_build "$2" "$2"
2140            elif [ $# -eq 3 ]; then
2141                do_normal_build "$2" "$3"
2142            else
2143                echo "usage: $0 $1 <release> <build_name>"
2144                exit 1
2145            fi
2146        fi
2147        ;;
2148    install)
2149        if [ $# -lt 2 ]; then
2150            echo "usage: $0 $1 <build_name> [directory]"
2151            exit 1
2152        fi
2153        if [ $# -eq 3 ]; then
2154            do_install "$2" "$3"
2155        else
2156            if [ -z "$KERL_DEFAULT_INSTALL_DIR" ]; then
2157                do_install "$2" "$PWD"
2158            else
2159                do_install "$2" "$KERL_DEFAULT_INSTALL_DIR/$2"
2160            fi
2161        fi
2162        ;;
2163    install-docsh)
2164        ACTIVE_PATH="$(get_active_path)"
2165        if [ -n "$ACTIVE_PATH" ]; then
2166            ACTIVE_NAME="$(get_name_from_install_path "$ACTIVE_PATH")"
2167            if [ -z "$ACTIVE_NAME" ]; then
2168                ## TODO: Are git builds installed the usual way
2169                ##       or do we need this clause to provide a fallback?
2170                #BUILDNAME="$(basename "$ACTIVE_PATH")"
2171                echo "$ACTIVE_PATH is not a kerl installation"
2172                exit 1
2173            else
2174                BUILDNAME="$ACTIVE_NAME"
2175            fi
2176            install_docsh "$BUILDNAME" "$ACTIVE_PATH"
2177            echo 'Please kerl_deactivate and activate again to enable docsh'
2178        else
2179            echo 'No Erlang/OTP installation is currently active - cannot install docsh'
2180            exit 1
2181        fi
2182        ;;
2183    deploy)
2184        if [ $# -lt 2 ]; then
2185            echo "usage: $0 $1 <[user@]host> [directory] [remote_directory]"
2186            exit 1
2187        fi
2188        if [ $# -eq 4 ]; then
2189            do_deploy "$2" "$3" "$4"
2190        else
2191            if [ $# -eq 3 ]; then
2192                do_deploy "$2" "$3"
2193            else
2194                do_deploy "$2" .
2195            fi
2196        fi
2197        ;;
2198    update)
2199        if [ $# -lt 2 ]; then
2200            update_usage
2201            exit 1
2202        fi
2203        case "$2" in
2204            releases)
2205                rm -f "${KERL_BASE_DIR:?}"/otp_releases
2206                check_releases
2207                echo 'The available releases are:'
2208                list_print releases
2209                ;;
2210            *)
2211                update_usage
2212                exit 1
2213                ;;
2214        esac
2215        ;;
2216    list)
2217        if [ $# -ne 2 ]; then
2218            list_usage
2219            exit 1
2220        fi
2221        case "$2" in
2222            releases)
2223                check_releases
2224                list_print "$2"
2225                echo "Run '$0 update releases' to update this list from erlang.org"
2226                ;;
2227            builds)
2228                list_print "$2"
2229                ;;
2230            installations)
2231                list_print "$2"
2232                ;;
2233            *)
2234                echo "Cannot list $2"
2235                list_usage
2236                exit 1
2237                ;;
2238        esac
2239        ;;
2240    path)
2241        # Usage:
2242        # kerl path
2243        # # Print currently active installation path, else non-zero exit
2244        # kerl path <install>
2245        # Print path to installation with name <install>, else non-zero exit
2246        if [ -z "$2" ]; then
2247            activepath=$(get_active_path)
2248            if [ -z "$activepath" ]; then
2249                echo 'No active kerl-managed erlang installation'
2250                exit 1
2251            fi
2252            echo "$activepath"
2253        else
2254            # There are some possible extensions to this we could
2255            # consider, such as:
2256            # - if 2+ matches: prefer one in a subdir from $PWD
2257            # - prefer $KERL_DEFAULT_INSTALL_DIR
2258            match=
2259            for ins in $(list_print installations | cut -d' ' -f2); do
2260                if [ "$(basename "$ins")" = "$2" ]; then
2261                    if [ -z "$match" ]; then
2262                        match="$ins"
2263                    else
2264                        echo 'Error: too many matching installations' >&2
2265                        exit 2
2266                    fi
2267                fi
2268            done
2269            [ -n "$match" ] && echo "$match" && exit 0
2270            echo 'Error: no matching installation found' >&2 && exit 1
2271        fi
2272        ;;
2273    delete)
2274        if [ $# -ne 3 ]; then
2275            delete_usage
2276            exit 1
2277        fi
2278        case "$2" in
2279            build)
2280                rel="$(get_release_from_name "$3")"
2281                if [ -d "${KERL_BUILD_DIR:?}/$3" ]; then
2282                    maybe_remove "${KERL_BUILD_DIR:?}/$3"
2283                else
2284                    if [ -z "$rel" ]; then
2285                      echo "No build named $3"
2286                      exit 1
2287                    fi
2288                fi
2289                list_remove "$2"s "$rel,$3"
2290                echo "The $3 build has been deleted"
2291                ;;
2292            installation)
2293                assert_valid_installation "$3"
2294                if [ -d "$3" ]; then
2295                    maybe_remove "$3"
2296                else
2297                    maybe_remove "$(get_install_path_from_name "$3")"
2298                fi
2299                escaped="$(echo "$3" | \sed $SED_OPT -e 's#/$##' -e 's#\/#\\\/#g')"
2300                list_remove "$2"s "$escaped"
2301                echo "The installation \"$3\" has been deleted"
2302                ;;
2303            *)
2304                echo "Cannot delete $2"
2305                delete_usage
2306                exit 1
2307                ;;
2308        esac
2309        ;;
2310    active)
2311        if ! do_active; then
2312            exit 1;
2313        fi
2314        ;;
2315    plt)
2316        ACTIVE_PATH=$(get_active_path)
2317        if ! do_plt "$ACTIVE_PATH"; then
2318            exit 1;
2319        fi
2320        ;;
2321    status)
2322        echo 'Available builds:'
2323        list_print builds
2324        echo '----------'
2325        echo 'Available installations:'
2326        list_print installations
2327        echo '----------'
2328        if do_active; then
2329            ACTIVE_PATH=$(get_active_path)
2330            if [ -n "$ACTIVE_PATH" ]; then
2331                do_plt "$ACTIVE_PATH"
2332                print_buildopts "$ACTIVE_PATH"
2333            else
2334                echo 'No Erlang/OTP installation is currently active'
2335                exit 1
2336            fi
2337        fi
2338        exit 0
2339        ;;
2340    prompt)
2341        FMT=' (%s)'
2342        if [ -n "$2" ]; then
2343            FMT="$2"
2344        fi
2345        ACTIVE_PATH="$(get_active_path)"
2346        if [ -n "$ACTIVE_PATH" ]; then
2347            ACTIVE_NAME="$(get_name_from_install_path "$ACTIVE_PATH")"
2348            if [ -z "$ACTIVE_NAME" ]; then
2349                VALUE="$(basename "$ACTIVE_PATH")*"
2350            else
2351                VALUE="$ACTIVE_NAME"
2352            fi
2353            # shellcheck disable=SC2059
2354            printf "$FMT" "$VALUE"
2355        fi
2356        exit 0
2357        ;;
2358    cleanup)
2359        if [ $# -ne 2 ]; then
2360            cleanup_usage
2361            exit 1
2362        fi
2363        case "$2" in
2364            all)
2365                echo 'Cleaning up compilation products for ALL builds'
2366                rm -rf "${KERL_BUILD_DIR:?}"/*
2367                rm -rf "${KERL_DOWNLOAD_DIR:?}"/*
2368                rm -rf "${KERL_GIT_DIR:?}"/*
2369                echo "Cleaned up all compilation products under $KERL_BUILD_DIR"
2370                ;;
2371            *)
2372                echo "Cleaning up compilation products for $3"
2373                rm -rf "${KERL_BUILD_DIR:?}/$3"
2374                echo "Cleaned up compilation products for $3 under $KERL_BUILD_DIR"
2375                ;;
2376        esac
2377        ;;
2378    *)
2379        echo "unknown command: $1"; usage; exit 1
2380        ;;
2381esac
2382