#!/bin/sh
#
# Type `build -h` for usage and see https://github.com/romkatv/zsh-bin
# for documentation.

set -ue

if [ -n "${BASH_VERSION:-}" ]; then
  if [ -z "${BASH_VERSION##[0-4].*}" ]; then
    >&2 echo "[error] bash version < 5 is not supported; try another interpreter"
    exit 1
  fi
fi

if [ -n "${ZSH_VERSION:-}" ]; then
  emulate sh -o err_exit -o no_unset
fi

usage="$(cat <<\END
Usage: build [-m ARCH] [-c CPU] [-i IMAGE] [-d CMD]

Creates an archive containing statically-linked, hermetic,
relocatable Zsh 5.8. Installation of Zsh from the archive
does not require libc, terminfo, ncurses or root access.
As long as the target machine has a compatible CPU and
kernel, it will work.

Options:

  -m ARCH   `uname -m` from the target machine; defaults to `uname -m`
            from the local machine
  -c CPU    generate machine instructions for CPU of this type; this
            value gets passed as `-march` (or `-mcpu` for ppc64le) to gcc;
            inferred from ARCH if not set explicitly
  -i IMAGE  docker image used for building zsh; inferred from ARCH
            if not set explicitly
  -d CMD    use this command instead of 'docker'; it must understand
            the same command line arguments
END
)"

build="$(cat <<\END
outdir="$(pwd)"

unset TERMINFO TERMINFO_DIRS

if command -v mktemp >/dev/null 2>&1; then
  workdir="$(mktemp -d "${TMPDIR:-/tmp}"/zsh-bin.XXXXXXXXXX)"
else
  workdir="${TMPDIR:-/tmp}/zsh-bin.$$"
  mkdir -- "$workdir"
fi

cd -- "$workdir"
workdir="$(pwd)"

narg() { echo $#; }

if [ "$(narg $workdir)" != 1 -o -z "${workdir##*:*}" ]; then
  >&2 echo "[error] cannot build in this directory: $workdir"
  exit 1
fi

cleanup() {
  trap - INT QUIT TERM EXIT ILL PIPE
  cd /
  if ! rm -rf -- "$workdir"; then
    sleep 5
    rm -rf -- "$workdir"
  fi
}
trap cleanup INT QUIT TERM EXIT ILL PIPE

libiconv_version=1.16
icmake_version=9.03.01
yodl_version=4.02.01
ncurses_version=6.2
kitty_version=0.25.2
termite_version=15
kakoune_version=2020.09.01
foot_version=1.7
zsh_doc_version=5.0.1

ncurses_url=https://ftp.gnu.org/pub/gnu/ncurses/ncurses-$ncurses_version.tar.gz
kitty_url=https://raw.githubusercontent.com/kovidgoyal/kitty/v$kitty_version/terminfo/kitty.terminfo
termite_url=https://raw.githubusercontent.com/thestinger/termite/v$termite_version/termite.terminfo
kakoune_url=https://raw.githubusercontent.com/mawww/kakoune/v$kakoune_version/contrib/tmux-256color.terminfo
foot_url=https://codeberg.org/dnkl/foot/raw/branch/releases/$foot_version/foot.info
libiconv_url=https://ftp.gnu.org/pub/gnu/libiconv/libiconv-$libiconv_version.tar.gz
icmake_url=https://gitlab.com/fbb-git/icmake/-/archive/$icmake_version/icmake-$icmake_version.tar.gz
yodl_url=https://gitlab.com/fbb-git/yodl/-/archive/$yodl_version/yodl-$yodl_version.tar.gz
zsh_url=https://github.com/zsh-users/zsh/archive/zsh-5.8.tar.gz
zsh_doc_url=https://github.com/romkatv/zsh-bin/releases/download/v$zsh_doc_version/zsh-5.8-linux-x86_64.tar.gz

ncurses_sha256=30306e0c76e0f9f1f0de987cf1c82a5c21e1ce6568b9227f7da5b71cbea86c9d
kitty_sha256=bdcac2c8cbae7bf2e2843e500ded9f550a6a8b3e66a14565b0156d8301dfc6aa
termite_sha256=771ecbde374ccefc40f06fb8ee0191bac1a81ab1f8f46b88288d1a9092770529
kakoune_sha256=b1a0c61f6ce3d123878c501c01183aac5cf951ed718eb1c73949eb86b00678be
foot_sha256=59769813ed6545c8176af291f49e281860ac70c30ceb5f72f307ec7ce9c992f4
libiconv_sha256=e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04
icmake_sha256=7c5c091f58f576da580238c5e3636e2038d9ecf5efb6562ae7e402910d9036e6
yodl_sha256=0118efd6f05ddeed4910bb5e280a37a4d8e5c2f42fb2009c840b3d5c70f700f8
zsh_sha256=db6cdfadac8d3d1f85c55c3644620cf5a0f65bf01ca26a58ff06f041bf159a5d
zsh_doc_sha256=e1310318c6d0a14e595f71f2f393bf915758f834e50a5d101a98e246b7a137cf

cpus="$(getconf _NPROCESSORS_ONLN)" || cpus="$(sysctl -n hw.ncpu)" || cpus=8

fetch() {
  local url="$1"
  local sha256="$2"
  local filename="${url##*/}"
  local base="${filename%.tar.gz}"
  printf '%s  %s\n' "$sha256" "$filename" >"$base".sha256
  if ! cp -- "$outdir/src/$filename" . 2>/dev/null || ! shasum -b -a 256 -c "$base".sha256; then
    rm -f -- "$filename"
    wget -- "$url"
    shasum -b -a 256 -c "$base".sha256
    mkdir -p -- "$outdir"/src
    cp -f -- "$filename" "$outdir"/src/tmp."$filename"
    mv -f -- "$outdir"/src/tmp."$filename" "$outdir"/src/"$filename"
  fi
  case "$filename" in
    *.tar.gz)
      tar -xzf "$filename"
    ;;
  esac
}

ncurses_configure=

case "$ZSH_BIN_KERNEL" in
  linux)
    apk update
    apk add          \
      autoconf       \
      bash           \
      binutils       \
      file           \
      g++            \
      gcc            \
      gdbm-dev       \
      groff          \
      make           \
      man            \
      musl-dev       \
      pcre-dev       \
      perl-utils     \
      rsync          \
      tar            \
      texinfo        \
      util-linux

      cd -- "$workdir"
      fetch "$libiconv_url" "$libiconv_sha256"
      cd libiconv-"$libiconv_version"
      ./configure                     \
        --prefix=/usr                 \
        --disable-dependency-tracking \
        --enable-static               \
        --disable-shared              \
        --enable-extra-encodings      \
        --disable-rpath               \
        --enable-relocatable
      make -j "$cpus" install
  ;;
  freebsd)
    pkg install -y \
      autoconf     \
      binutils     \
      gcc          \
      gmake        \
      groff        \
      libiconv     \
      perl5        \
      pcre         \
      rsync        \
      texinfo      \
      yodl
  ;;
  darwin)
    if ! command -v make >/dev/null 2>&1 || ! command -v gcc >/dev/null 2>&1; then
      >&2 echo "[error] please run 'xcode-select --install' and retry"
      exit 1
    fi
    if command -v port >/dev/null 2>&1; then
      sudo port -N install autoconf libiconv pcre wget
    elif command -v brew >/dev/null 2>&1; then
      for formula in autoconf libiconv pcre wget; do
        if brew ls --version "$formula" >/dev/null; then
          brew upgrade "$formula"
        else
          brew install "$formula"
        fi
      done
    else
      >&2 echo "[error] please install MacPorts or Homebrew and retry"
      exit 1
    fi
    mkdir -- "$workdir"/zsh_doc
    cd -- "$workdir"/zsh_doc
    fetch "$zsh_doc_url" "$zsh_doc_sha256"
  ;;
  msys*|mingw*)
    ncurses_configure='--with-pcre2'
    pacman -Syu --noconfirm
    pacman -S --needed --noconfirm \
      autoconf                     \
      binutils                     \
      gcc                          \
      groff                        \
      libiconv-devel               \
      make                         \
      man                          \
      perl                         \
      pcre-devel                   \
      rsync                        \
      texinfo                      \
      yodl
  ;;
  cygwin*)
    for cmd in autoconf bash colcrt gcc g++ groff ld make makeinfo shasum tar wget; do
      if ! command -v "$cmd" >/dev/null 2>&1; then
        >&2 echo "[error] command not found: $cmd"
        >&2 echo ""
        >&2 echo "Make sure the following Cygwin packages are installed:"
        >&2 echo ""
        >&2 echo "  autoconf"
        >&2 echo "  bash"
        >&2 echo "  binutils"
        >&2 echo "  gcc-core"
        >&2 echo "  gcc-g++"
        >&2 echo "  groff"
        >&2 echo "  libiconv-devel"
        >&2 echo "  make"
        >&2 echo "  perl"
        >&2 echo "  tar"
        >&2 echo "  texinfo"
        >&2 echo "  rsync"
        >&2 echo "  util-linux"
        >&2 echo "  wget"
        exit 1
      fi
    done
    for file in /usr/lib/libiconv.a; do
      if [ ! -e "$file" ]; then
        >&2 echo "[error] not found: $file"
        exit 1
      fi
    done
  ;;
  *)
    >&2 echo "[internal error] unhandled kernel: $ZSH_BIN_KERNEL"
    exit 1
  ;;
esac

if [ ! -e "$workdir"/zsh_doc ] && ! command -v yodl >/dev/null 2>&1; then
  if ! command -v icmake >/dev/null 2>&1; then
    cd -- "$workdir"
    fetch "$icmake_url" "$icmake_sha256"
    cd icmake-"$icmake_version"/icmake
    ./icm_prepare /
    ./icm_bootstrap x
    ./icm_install all
  fi

  cd -- "$workdir"
  fetch "$yodl_url" "$yodl_sha256"
  cd yodl-"$yodl_version"
  mkdir fake-bin
  touch fake-bin/tput
  chmod +x fake-bin/tput
  fake_bin_dir="$(pwd)"/fake-bin
  cd yodl
  PATH="$fake_bin_dir:$PATH" ./build programs
  PATH="$fake_bin_dir:$PATH" ./build macros
  PATH="$fake_bin_dir:$PATH" ./build install programs
  PATH="$fake_bin_dir:$PATH" ./build install macros
fi

cd -- "$workdir"
fetch "$ncurses_url" "$ncurses_sha256"
cd ncurses-"$ncurses_version"
mkdir fake-bin
echo 'false' >fake-bin/ldconfig
chmod +x fake-bin/ldconfig
fake_bin_dir="$(pwd)"/fake-bin
PATH="$fake_bin_dir:$PATH" ./configure \
  --prefix "$workdir"/ncurses \
  --disable-pc-files \
  --disable-mixed-case \
  --disable-rpath \
  --disable-bsdpad \
  --disable-termcap \
  --disable-rpath-hack \
  --enable-root-environ \
  --enable-widec \
  --without-ada \
  --without-manpages \
  --without-tack \
  --without-tests \
  --without-pc-suffix \
  --without-pkg-config-libdir \
  --without-shared \
  --without-debug \
  --without-cxx-shared \
  --without-gmp \
  --without-dlsym \
  --without-pcre2 \
  --with-terminfo-dirs="/etc/terminfo:/usr/share/terminfo:/lib/terminfo:/usr/lib/terminfo" \
  $ncurses_configure

if command -v gmake >/dev/null 2>/dev/null; then
  gmake -j "$cpus" install
else
  make -j "$cpus" install
fi

cd -- "$workdir"
fetch "$kitty_url" "$kitty_sha256"
ncurses/bin/tic -x -o ncurses/share/terminfo kitty.terminfo
cd -- "$workdir"
fetch "$termite_url" "$termite_sha256"
ncurses/bin/tic -x -o ncurses/share/terminfo termite.terminfo
cd -- "$workdir"
fetch "$kakoune_url" "$kakoune_sha256"
ncurses/bin/tic -x -o ncurses/share/terminfo tmux-256color.terminfo
cd -- "$workdir"
fetch "$foot_url" "$foot_sha256"
ncurses/bin/tic -x -o ncurses/share/terminfo foot.info
cp ncurses/share/terminfo/66/foot ncurses/share/terminfo/66/foot-extra
cp ncurses/share/terminfo/66/foot+base ncurses/share/terminfo/66/foot+base-extra
cp ncurses/share/terminfo/66/foot-direct ncurses/share/terminfo/66/foot-direct-extra

cd -- "$workdir"
fetch "$zsh_url" "$zsh_sha256"
cd zsh-zsh-5.8

patch -u aczsh.m4 <<-\END
	--- aczsh.m4	2020-12-21 19:06:04.847363893 +0100
	+++ aczsh.m4.new	2020-12-21 19:05:51.975418771 +0100
	@@ -118,6 +118,7 @@
	 AC_TRY_COMMAND($CC -c $CFLAGS $CPPFLAGS $DLCFLAGS conftest2.c 1>&AC_FD_CC) &&
	 AC_TRY_COMMAND($DLLD -o conftest2.$DL_EXT $LDFLAGS $DLLDFLAGS conftest2.o $LIBS 1>&AC_FD_CC); then
	     AC_TRY_RUN([
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	@@ -199,6 +200,7 @@
	 AC_TRY_COMMAND($CC -c $CFLAGS $CPPFLAGS $DLCFLAGS conftest2.c 1>&AC_FD_CC) &&
	 AC_TRY_COMMAND($DLLD -o conftest2.$DL_EXT $LDFLAGS $DLLDFLAGS conftest2.o $LIBS 1>&AC_FD_CC); then
	     AC_TRY_RUN([
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	@@ -274,6 +276,7 @@
	 AC_TRY_COMMAND($CC -c $CFLAGS $CPPFLAGS $DLCFLAGS conftest2.c 1>&AC_FD_CC) &&
	 AC_TRY_COMMAND($DLLD -o conftest2.$DL_EXT $LDFLAGS $DLLDFLAGS conftest2.o $LIBS 1>&AC_FD_CC); then
	     AC_TRY_RUN([
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	@@ -343,6 +346,7 @@
	     save_ldflags=$LDFLAGS
	     LDFLAGS="$LDFLAGS $EXTRA_LDFLAGS"
	     AC_TRY_RUN([
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	@@ -416,6 +420,7 @@
	     save_ldflags=$LDFLAGS
	     LDFLAGS="$LDFLAGS $EXTRA_LDFLAGS -s"
	     AC_TRY_RUN([
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	@@ -483,6 +488,7 @@
	 if AC_TRY_COMMAND($CC -c $CFLAGS $CPPFLAGS $DLCFLAGS conftest1.c 1>&AC_FD_CC) &&
	 AC_TRY_COMMAND($DLLD -o conftest1.$DL_EXT $LDFLAGS $DLLDFLAGS -s conftest1.o $LIBS 1>&AC_FD_CC); then
	     AC_TRY_RUN([
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	END

patch -u configure.ac <<-\END
	--- configure.ac	2020-12-21 19:06:04.851363875 +0100
	+++ configure.ac.new	2020-12-21 19:05:59.199387974 +0100
	@@ -1394,6 +1394,10 @@
	 AC_CACHE_CHECK(if tgetent accepts NULL,
	 zsh_cv_func_tgetent_accepts_null,
	 [AC_RUN_IFELSE([AC_LANG_SOURCE([[
	+#include <fcntl.h>
	+#include <stdlib.h>
	+int tgetent(char *, char *);
	+char *tgetstr(char *, char **);
	 main()
	 {
	     char buf[4096];
	@@ -1418,6 +1422,10 @@
	 AC_CACHE_CHECK(if tgetent returns 0 on success,
	 zsh_cv_func_tgetent_zero_success,
	 [AC_RUN_IFELSE([AC_LANG_SOURCE([[
	+#include <fcntl.h>
	+#include <stdlib.h>
	+int tgetent(char *, char*);
	+char *tgetstr(char *, char **);
	 main()
	 {
	     char buf[4096];
	@@ -1855,6 +1863,7 @@
	 #include <sys/time.h>
	 #endif
	 #include <sys/resource.h>
	+#include <stdlib.h>
	 main(){struct rlimit r;exit(sizeof(r.rlim_cur) <= sizeof(long));}]])],[zsh_cv_rlim_t_is_longer=yes],[zsh_cv_rlim_t_is_longer=no],[zsh_cv_rlim_t_is_longer=yes])])
	 if test x$zsh_cv_rlim_t_is_longer = xyes; then
	   AC_CACHE_CHECK(if rlim_t is a quad,
	@@ -1865,6 +1874,7 @@
	 #endif
	 #include <stdio.h>
	 #include <sys/resource.h>
	+#include <stdlib.h>
	 main() { 
	   struct rlimit r;
	   char buf[20];
	@@ -1887,6 +1897,7 @@
	 #include <sys/time.h>
	 #endif
	 #include <sys/resource.h>
	+#include <stdlib.h>
	   main(){struct rlimit r;r.rlim_cur=-1;exit(r.rlim_cur<0);}]])],[zsh_cv_type_rlim_t_is_unsigned=yes],[zsh_cv_type_rlim_t_is_unsigned=no],[zsh_cv_type_rlim_t_is_unsigned=no])])
	   if test x$zsh_cv_type_rlim_t_is_unsigned = xyes; then
	     AC_DEFINE(RLIM_T_IS_UNSIGNED)
	@@ -2258,6 +2269,9 @@
	 [AC_RUN_IFELSE([AC_LANG_SOURCE([[
	 #include <fcntl.h>
	 #include <signal.h>
	+#include <unistd.h>
	+#include <stdlib.h>
	+#include <sys/stat.h>
	 main()
	 {
	     char c;
	@@ -2299,6 +2313,7 @@
	 [AC_RUN_IFELSE([AC_LANG_SOURCE([[
	 #include <unistd.h>
	 #include <fcntl.h>
	+#include <stdlib.h>
	 main()
	 {
	     int ret;
	@@ -2331,6 +2346,7 @@
	 #include <unistd.h>
	 #include <signal.h>
	 #include <errno.h>
	+#include <stdlib.h>
	 main()
	 {
	     int pid = (getpid() + 10000) & 0xffffff;
	@@ -2356,6 +2372,7 @@
	     [AC_RUN_IFELSE([AC_LANG_SOURCE([[
	 #include <signal.h>
	 #include <unistd.h>
	+#include <stdlib.h>
	 int child=0;
	 void handler(sig)
	     int sig;
	@@ -2407,6 +2424,7 @@
	 #include <sys/types.h>
	 #include <unistd.h>
	 #include <fcntl.h>
	+#include <stdlib.h>
	 main() {
	     int fd;
	     int ret;
	@@ -2450,6 +2468,10 @@
	     zsh_cv_sys_getpwnam_faked,
	     [AC_RUN_IFELSE([AC_LANG_SOURCE([[
	 #include <pwd.h>
	+#include <stdio.h>
	+#include <string.h>
	+#include <stdlib.h>
	+#include <unistd.h>
	 main() {
	     struct passwd *pw1, *pw2;
	     char buf[1024], name[1024];
	@@ -2777,6 +2799,7 @@
	    [AC_RUN_IFELSE([AC_LANG_SOURCE([[/* Test for whether ELF binaries are produced */
	 #include <fcntl.h>
	 #include <stdlib.h>
	+#include <unistd.h>
	 main(argc, argv)
	 int argc;
	 char *argv[];
	@@ -2930,6 +2953,7 @@
	     AC_TRY_COMMAND($DLLD $LDFLAGS $DLLDFLAGS -o conftest.$DL_EXT conftest.o 1>&AS_MESSAGE_LOG_FD) &&
	     AC_RUN_IFELSE([AC_LANG_SOURCE([[
	 #include <stdio.h>
	+#include <stdlib.h>
	 #ifdef HPUX10DYNAMIC
	 #include <dl.h>
	 #define RTLD_LAZY BIND_DEFERRED
	END

case "$ZSH_BIN_CPU" in
  powerpc64le) archflag="-mcpu";;
  *)           archflag="-march";;
esac

name=zsh-5.8-"$ZSH_BIN_KERNEL"-"$ZSH_BIN_ARCH"

export CFLAGS="-Wall -Wmissing-prototypes $archflag=$ZSH_BIN_CPU -DNDEBUG -O2 -flto -fno-strict-aliasing"
export LDFLAGS=
export EXELDFLAGS=
export LIBS=

CFLAGS="$CFLAGS -I$workdir/ncurses/include"
LDFLAGS="$LDFLAGS -L$workdir/ncurses/lib"

case "$ZSH_BIN_KERNEL" in
  linux|freebsd|msys*|mingw*)
    EXELDFLAGS="$EXELDFLAGS -static"
    LIBS="$LIBS -lpcre"
  ;;
  darwin)
    mkdir -- "$workdir"/lib
    if [ -e /opt/local/lib/libiconv.a -a -e /opt/local/lib/libpcre.a ]; then
      ln -s -- /opt/local/lib/libiconv.a "$workdir"/lib
      ln -s -- /opt/local/lib/libpcre.a  "$workdir"/lib
      CFLAGS="$CFLAGS -I/opt/local/include -I/opt/local/include"
    else
      brew_prefix="$(command brew --prefix)"
      opt="$brew_prefix"/opt
      ln -s -- "$opt"/libiconv/lib/libiconv.a   "$workdir"/lib
      ln -s -- "$opt"/pcre/lib/libpcre.a        "$workdir"/lib
      CFLAGS="$CFLAGS -I$opt/libiconv/include -I$opt/pcre/include"
    fi
    LDFLAGS="$LDFLAGS -L$workdir/lib"
    LIBS="$LIBS -lpcre"
  ;;
  cygwin*)
    EXELDFLAGS="$EXELDFLAGS -static"
  ;;
  *)
    >&2 echo "[internal error] unhandled kernel: $ZSH_BIN_KERNEL"
    exit 1
  ;;
esac

export DLLDFLAGS="-shared"
export LDFLAGS="$CFLAGS $LDFLAGS"

sed_i() {
  [ $# -gt 1 ]
  local script="$1"
  while [ $# != 1 ]; do
    shift
    local file="$1"
    sed "$script" "$file" >"$file".tmp
    mv -- "$file".tmp "$file"
  done
}

sed_i 's/armv\*/armv* | arm64/' config.sub

host="$(./config.guess)"
if [ -z "$host" -o -n "${host##*?-?*}" ]; then
  >&2 echo "[error] unexpected output from ./config.guess: $host"
  exit 1
fi

./Util/preconfig
./configure                          \
  --prefix="$workdir/$name"          \
  --disable-etcdir                   \
  --disable-zshenv                   \
  --disable-zshrc                    \
  --disable-zlogin                   \
  --disable-zprofile                 \
  --disable-zlogout                  \
  --disable-site-fndir               \
  --disable-site-scriptdir           \
  --enable-cap                       \
  --enable-unicode9                  \
  --with-tcsetpgrp                   \
  --disable-dynamic                  \
  --host="$ZSH_BIN_ARCH-${host#*-}"  \
  --enable-custom-patchlevel=zsh-5.8-0-g77d203f

sed_i 's/link=no/link=static/' config.modules

case "$ZSH_BIN_KERNEL" in
  linux);;
  freebsd)
    sed_i 's/attr.mdd link=static/attr.mdd link=no/' config.modules
    sed_i 's/db_gdbm.mdd link=static/db_gdbm.mdd link=no/' config.modules
  ;;
  darwin)
    sed_i 's/db_gdbm.mdd link=static/db_gdbm.mdd link=no/' config.modules
  ;;
  msys*|mingw*)
    sed_i 's/db_gdbm.mdd link=static/db_gdbm.mdd link=no/' config.modules
  ;;
  cygwin*)
    sed_i 's/db_gdbm.mdd link=static/db_gdbm.mdd link=no/' config.modules
    sed_i 's/pcre.mdd link=static/pcre.mdd link=no/' config.modules
  ;;
  *)
    >&2 echo "[internal error] unhandled kernel: $ZSH_BIN_KERNEL"
    exit 1
  ;;
esac

magic=iLWDLaG9dUlsxzEQp10k

sed_i '46i\
#include <math.h>' Src/params.c
sed_i 's|mv -f zshpaths.h.tmp zshpaths.h|cp -f ../zshpaths.h ./|' Src/zsh.mdd
sed_i 's/\(\$(LN_S).*\);/( cd -- $(DESTDIR)$(runhelpdir) \&\& \1; );/' Doc/Makefile.in

sed_i 's/tgetent/tgetent_with_env/g' Src/init.c

patch -u Src/init.c <<-\END
	--- Src/init.c	2020-12-22 14:41:00.524162742 +0100
	+++ Src/init.c.new	2020-12-22 14:49:40.535136789 +0100
	@@ -869,6 +869,10 @@
	     return 1;
	 }
	 
	+#if defined(__APPLE__)
	+static void init_paths_from_exe_path(void);
	+#endif
	+
	 /* Initialize lots of global variables and hash tables */
	 
	 /**/
	@@ -905,6 +909,10 @@
	 #endif
	     int close_fds[10], tmppipe[2];
	 
	+#if defined(__APPLE__)
	+  init_paths_from_exe_path();
	+#endif
	+
	     /*
	      * Workaround a problem with NIS (in one guise or another) which
	      * grabs file descriptors and keeps them for future reference.
	END

cat >>Src/init.c <<-\END
	volatile char tagged_script_dir[sizeof(SCRIPT_DIR_TAG) + 4096] = {
	  SCRIPT_DIR_TAG "/usr/share/zsh/5.8/scripts"
	};
	volatile char tagged_fpath_dir[sizeof(FPATH_DIR_TAG) + 4096] = {
	  FPATH_DIR_TAG "/usr/share/zsh/5.8/functions"
	};
	volatile char tagged_terminfo_dir[sizeof(TERMINFO_DIR_TAG) + 4096] = {
	  TERMINFO_DIR_TAG "/usr/share/terminfo"
	};

	extern int tgetent_with_env(char *termbuf, char *term) {
	  int res;
	  int ev_len;
	  char **ev;
	  char *orig;
	  char *patched;
	  orig = getenv("TERMINFO_DIRS");
	  if (orig) {
	    orig -= strlen("TERMINFO_DIRS") + 1;
	    patched = tricat(orig, ":", TERMINFO_DIR);
	    putenv(patched);  /* ignore ENOMEM */
	  } else {
	    patched = bicat("TERMINFO_DIRS=", TERMINFO_DIR);
	    ev = environ;
	    ev_len = arrlen(environ);
	    environ = (char **)zalloc(sizeof(char *) * (ev_len + 2));
	    memcpy(environ, ev, sizeof(char *) * ev_len);
	    environ[ev_len] = patched;
	    environ[ev_len + 1] = NULL;
	  }
	  
	  res = tgetent(termbuf, term);
	  if (orig) {
	    putenv(orig);  /* ignore ENOMEM */
	  } else {
	    zfree(environ, sizeof(char *) * (ev_len + 2));
	    environ = ev;
	  }
	  zsfree(patched);
	  return res;
	}

	#if defined(__APPLE__)
	#include <errno.h>
	#include <stdio.h>
	#include <stdlib.h>
	#include <string.h>
	#include <libproc.h>
	#include <unistd.h>

	static void init_paths_from_exe_path(void) {
	  int n, i;
	  pid_t pid;
	  char exepath[PROC_PIDPATHINFO_MAXSIZE];

	  pid = getpid();
	  if (proc_pidpath(pid, exepath, sizeof(exepath)) < 0) {
	    perror("proc_pidpath");
	    exit(1);
	  }
	  n = strlen(exepath);
	  for (i = 0; i != 2; --n) {
	    if (n == 0) {
	      fprintf(stderr, "zsh: invalid exe path: %s\n", exepath);
	      exit(1);
	    }
	    if (exepath[n-1] == '/') ++i;
	  }
	  memcpy((char*)tagged_script_dir   + strlen(SCRIPT_DIR_TAG),   exepath, n);
	  memcpy((char*)tagged_fpath_dir    + strlen(FPATH_DIR_TAG),    exepath, n);
	  memcpy((char*)tagged_terminfo_dir + strlen(TERMINFO_DIR_TAG), exepath, n);
	  strcpy((char*)tagged_script_dir   + strlen(SCRIPT_DIR_TAG)   + n, "/share/zsh/5.8/scripts");
	  strcpy((char*)tagged_fpath_dir    + strlen(FPATH_DIR_TAG)    + n, "/share/zsh/5.8/functions");
	  strcpy((char*)tagged_terminfo_dir + strlen(TERMINFO_DIR_TAG) + n, "/share/terminfo");
	}
	#endif
	END

cat >zshpaths.h <<-\END
	#define MODULE_DIR "/dev/null"

	#define SCRIPT_DIR_TAG ":@ZSH_BIN_MAGIC@:script:"
	#define FPATH_DIR_TAG ":@ZSH_BIN_MAGIC@:fpath:"
	#define TERMINFO_DIR_TAG ":@ZSH_BIN_MAGIC@:terminfo:"

	extern volatile char tagged_script_dir[sizeof(SCRIPT_DIR_TAG) + 4096];
	extern volatile char tagged_fpath_dir[sizeof(FPATH_DIR_TAG) + 4096];
	extern volatile char tagged_terminfo_dir[sizeof(TERMINFO_DIR_TAG) + 4096];

	#define SCRIPT_DIR ((const char *)(tagged_script_dir + sizeof(SCRIPT_DIR_TAG) - 1))
	#define FPATH_DIR ((const char *)(tagged_fpath_dir + sizeof(FPATH_DIR_TAG) - 1))
	#define TERMINFO_DIR ((const char *)(tagged_terminfo_dir + sizeof(TERMINFO_DIR_TAG) - 1))

	extern int tgetent_with_env(char *, char *);
	END

sed_i "s/@ZSH_BIN_MAGIC@/$magic/g" zshpaths.h

if [ -e "$workdir"/zsh_doc ]; then
  make -j "$cpus" install.bin install.modules install.fns
else
  make -j "$cpus" install install.info
fi

rsync -a --copy-links --copy-dirlinks -- "$workdir/$name"/ "$workdir/$name".tmp/
rm -r -- "$workdir/$name"
mv -- "$workdir/$name".tmp "$workdir/$name"

cd -- "$workdir/$name"

strip bin/zsh
rm -- bin/zsh-5.8

rsync -a --copy-links --copy-dirlinks -- "$workdir"/ncurses/share/terminfo share/

for dir in share/terminfo/*; do
  mv -- "$dir" "$dir".zsh-bin
done

if [ -e "$workdir"/zsh_doc ]; then
  cp -r -- "$workdir"/zsh_doc/share/man "$workdir"/zsh_doc/share/info share/
  cp -r -- "$workdir"/zsh_doc/share/zsh/5.8/help share/zsh/5.8/
fi

cp -r -- share/man share/info ./

sed_i 's/local HELPDIR=.*/[[ -n "${HELPDIR:-}" ]] || local HELPDIR="$(<<\\'"$magic"'\
'"$magic"'\
)"/'                                \
  share/zsh/5.8/functions/run-help  \
  share/zsh/5.8/functions/_run-help

cat >share/zsh/5.8/scripts/relocate <<-\END
	#!/bin/sh

	set -ue

	magic='@ZSH_BIN_MAGIC@'
	fpath_pos=@ZSH_BIN_FPATH_POS@
	script_pos=@ZSH_BIN_SCRIPT_POS@
	terminfo_pos=@ZSH_BIN_TERMINFO_POS@
	do_patch_bin=@ZSH_BIN_DO_PATCH_BIN@

	usage="$(cat <<-\USAGE
		Usage: relocate [-s SRC] [-d DST]

		Modifies hard-coded paths within Zsh residing in directory SRC
		so that they point to directory DST.

		The source directory must exist but the destination directory
		does not have to.

		Options:

		  -s SRC  directory where Zsh is currently installed; this is the
		          directory with 'bin' and 'usr' as subdirectories; if not
		          specified, this directory is automatically derived from
		          path to 'relocate'
		  -d DST  directory from which Zsh will be used; this is the
		          directory with 'bin' and 'usr' as subdirectories; if not
		          specified, this directory is automatically derived from
		          path to 'relocate'
		USAGE
	)"

	src=
	dst=

	while getopts ':s:d:h' opt "$@"; do
	  case "$opt" in
	    h)
	      printf '%s\n' "$usage"
	      exit
	    ;;
	    s)
	      if [ -n "$src" ]; then
	        >&2 echo "[error] duplicate option: -$opt"
	        exit 1
	      fi
	      if [ -z "$OPTARG" ]; then
	        >&2 echo "[error] incorrect value of -$opt: $OPTARG"
	        exit 1
	      fi
	      src="$OPTARG"
	    ;;
	    d)
	      if [ -n "$dst" ]; then
	        >&2 echo "[error] duplicate option: -$opt"
	        exit 1
	      fi
	      if [ -z "$OPTARG" ]; then
	        >&2 echo "[error] incorrect value of -$opt: $OPTARG"
	        exit 1
	      fi
	      dst="$OPTARG"
	    ;;
	    \?) >&2 echo "[error] invalid option: -$OPTARG"           ; exit 1;;
	    :)  >&2 echo "[error] missing required argument: -$OPTARG"; exit 1;;
	    *)  >&2 echo "[internal error] unhandled option: -$opt"   ; exit 1;;
	  esac
	done

	if [ "$OPTIND" -le $# ]; then
	  >&2 echo "[error] unexpected positional argument"
	  exit 1
	fi

	if [ -z "$src" ]; then
	  src="$(dirname -- "$0")"/../../../..
	fi

	absdir() {
	  ( [ -d "$1" ] && cd -- "$1" && pwd )
	}

	if ! src="$(absdir "$src")"; then
	  >&2 echo "[error] not a directory: $src"
	  exit 1
	fi

	if [ -n "$dst" ]; then
	  if [ -n "${dst##/*}" ]; then
	    >&2 echo "[error] path not absolute: $dst"
	    exit 1
	  fi
	else
	  dst="$(dirname -- "$0")"/../../../..
	  if ! dst="$(absdir "$dst")"; then
	    >&2 echo "[error] not a directory: $dst"
	    exit 1
	  fi
	fi

	zsh="$src"/bin/zsh
	runhelp="$src"/share/zsh/5.8/functions/run-help
	runhelpcomp="$src"/share/zsh/5.8/functions/_run-help

	if [ ! -x "$zsh" ]; then
	  >&2 echo "[error] not an executable file: $zsh"
	  exit 1
	fi

	if ! grep -qF 'local HELPDIR' -- "$runhelp"; then
	  >&2 echo "[error] cannot relocate zsh from this directory: $src"
	  exit 1
	fi

	if ! grep -qF 'local HELPDIR' -- "$runhelpcomp"; then
	  >&2 echo "[error] cannot relocate zsh from this directory: $src"
	  exit 1
	fi

	dst="${dst%/}/"

	# 4096 minus 23 for "share/zsh/5.8/functions"
	if [ ${#dst} -gt 4073 ]; then
	  >&2 echo "[error] directory name too long: $dst"
	  exit 1
	fi

	if [ -z "${dst##*$magic*}" ]; then
	  >&2 echo "[error] cannot relocate to this directory: $dst"
	  exit 1
	fi

	cp -pf -- "$zsh" "$zsh".tmp

	patch_help() {
	  local data
	  data="$(cat -- "$1")"
	  local prefix="${data%%$magic*}"
	  local suffix="${data##*$magic}"
	  if [ "$prefix" = "$data" -o "$suffix" = "$data" ]; then
	    >&2 echo "[error] not a relocatable zsh directory: $src"
	    exit 1
	  fi
	  local dir="$dst"share/zsh/5.8/help
	  printf '%s\n%s\n%s\n' "$prefix$magic" "$dir" "$magic$suffix" >"$1".tmp
	}

	patch_bin() {
	  "$do_patch_bin" || return 0
	  local header_len=$((1 + ${#magic} + 1 + ${#2} + 1))
	  local header
	  if ! header="$(dd if="$zsh" bs=1 skip="$1" count="$header_len" 2>/dev/null)"; then
	    header="$(dd if="$zsh" bs=1 skip="$1" count="$header_len")"
	  fi
	  if [ "$header" != ":$magic:$2:" ]; then
	    >&2 echo "[error] not a relocatable zsh binary: $zsh"
	    exit 1
	  fi

	  local pos=$(($1 + header_len))
	  local dir="${dst}$3"
	  local err
	  if ! err="$(dd if=/dev/zero of="$zsh".tmp bs=1 seek="$pos" count=4096 conv=notrunc 2>&1)"; then
	    >&2 printf '%s\n' "$err"
	    exit 1
	  fi
	  if ! err="$(printf '%s' "$dir" |
	               dd of="$zsh".tmp bs=1 seek="$pos" count=${#dir} conv=notrunc 2>&1)"; then
	    >&2 printf '%s\n' "$err"
	    exit 1
	  fi
	}

	patch_help "$runhelp"
	patch_help "$runhelpcomp"

	patch_bin "$fpath_pos"    fpath    share/zsh/5.8/functions
	patch_bin "$script_pos"   script   share/zsh/5.8/scripts
	patch_bin "$terminfo_pos" terminfo share/terminfo

	if "$do_patch_bin"; then
	  if ! fpath="$(unset FPATH && "$zsh".tmp -c 'print -r -- $fpath[1]')" ||
	     [ "${fpath#$dst}" = "$fpath" ]; then
	    >&2 echo "[error] failed to relocate zsh"
	    exit 1
	  fi
	fi

	mv -f -- "$zsh".tmp "$zsh"
	mv -f -- "$runhelp".tmp "$runhelp"
	mv -f -- "$runhelpcomp".tmp "$runhelpcomp"
	END

sed_i "s/@ZSH_BIN_MAGIC@/$magic/g" share/zsh/5.8/scripts/relocate

if [ "$ZSH_BIN_KERNEL" = darwin ]; then
  sed_i 's/@ZSH_BIN_DO_PATCH_BIN@/false/g' share/zsh/5.8/scripts/relocate
else
  sed_i 's/@ZSH_BIN_DO_PATCH_BIN@/true/g'  share/zsh/5.8/scripts/relocate
fi

embed_pos() {
  local cmd='
    bin=$(LC_ALL=C tr -c "[:alnum:]:" " " <$0)
    parts=("${(@ps:$1:)bin}")
    (( $#parts == 2 ))
    print -r -- $#parts[1]'
  local pos
  pos="$(bin/zsh -fuec "$cmd" bin/zsh ":$magic:$1:")"
  printf '%s' "$pos" | grep -qxE '[0-9]+'
  local upper
  upper="$(printf '%s' "$1" | tr '[a-z]' '[A-Z]')"
  sed_i "s/@ZSH_BIN_${upper}_POS@/$pos/g" share/zsh/5.8/scripts/relocate
}

embed_pos fpath
embed_pos script
embed_pos terminfo

chmod +x share/zsh/5.8/scripts/relocate

share/zsh/5.8/scripts/relocate
bin/zsh -fec 'zmodload zsh/stat; autoload -Uz add-zsh-hook; add-zsh-hook precmd x'
share/zsh/5.8/scripts/relocate -d /usr/local

tar -pczf "$outdir"/tmp."$name".tar.gz *
mv -f -- "$outdir"/tmp."$name".tar.gz "$outdir"/"$name".tar.gz

cleanup

cat >&2 <<-END
	-------------------------------------------------
	SUCCESS: created ./$name.tar.gz.

	This archive contains statically-linked, hermetic,
	relocatable Zsh 5.8. Installation of Zsh from the archive
	does not require libc, terminfo, ncurses or root access.
	As long as the target machine has a compatible CPU and
	kernel, it will work.

	To install Zsh from the archive, copy it along with
	'install' script to the home directory on the target
	machine and run:

	  sh ~/install -f ~/$name.tar.gz

	END
END
)"

image=
docker=

ZSH_BIN_ARCH=
ZSH_BIN_CPU=

while getopts ':m:c:i:d:h' opt "$@"; do
  case "$opt" in
    h)
      printf '%s\n' "$usage"
      exit
    ;;
    m)
      if [ -n "$ZSH_BIN_ARCH" ]; then
        >&2 echo "[error] duplicate option: -$opt"
        exit 1
      fi
      if [ -z "$OPTARG" ]; then
        >&2 echo "[error] incorrect value of -$opt: $OPTARG"
        exit 1
      fi
      ZSH_BIN_ARCH="$OPTARG"
    ;;
    c)
      if [ -n "$ZSH_BIN_CPU" ]; then
        >&2 echo "[error] duplicate option: -$opt"
        exit 1
      fi
      if [ -z "$OPTARG" ]; then
        >&2 echo "[error] incorrect value of -$opt: $OPTARG"
        exit 1
      fi
      ZSH_BIN_CPU="$OPTARG"
    ;;
    i)
      if [ -n "$image" ]; then
        >&2 echo "[error] duplicate option: -$opt"
        exit 1
      fi
      if [ -z "$OPTARG" ]; then
        >&2 echo "[error] incorrect value of -$opt: $OPTARG"
        exit 1
      fi
      image="$OPTARG"
    ;;
    d)
      if [ -n "$docker" ]; then
        >&2 echo "[error] duplicate option: -$opt"
        exit 1
      fi
      if [ -z "$OPTARG" ]; then
        >&2 echo "[error] incorrect value of -$opt: $OPTARG"
        exit 1
      fi
      docker="$OPTARG"
    ;;
    \?) >&2 echo "[error] invalid option: -$OPTARG"           ; exit 1;;
    :)  >&2 echo "[error] missing required argument: -$OPTARG"; exit 1;;
    *)  >&2 echo "[internal error] unhandled option: -$opt"   ; exit 1;;
  esac
done

if [ "$OPTIND" -le $# ]; then
  >&2 echo "[error] unexpected positional argument"
  exit 1
fi

if [ -z "$ZSH_BIN_ARCH" ]; then
  ZSH_BIN_ARCH="$(uname -m)"
  ZSH_BIN_ARCH="$(printf '%s' "$ZSH_BIN_ARCH" | tr '[A-Z]' '[a-z]')"
fi

if [ -z "$ZSH_BIN_CPU" ]; then
  case "$ZSH_BIN_ARCH" in
    armv6l)         ZSH_BIN_CPU=armv6;;
    armv7l)         ZSH_BIN_CPU=armv7;;
    arm64)          ZSH_BIN_CPU=armv8;;
    aarch64)        ZSH_BIN_CPU=armv8-a;;
    ppc64le)        ZSH_BIN_CPU=powerpc64le;;
    x86_64|amd64)   ZSH_BIN_CPU=x86-64;;
    i386|i586|i686) ZSH_BIN_CPU="$ZSH_BIN_ARCH";;
    *)
      >&2 echo '[error] unable to infer target CPU architecture'
      >&2 echo 'Please specify explicitly with `-c CPU`.'
      exit 1
    ;;
  esac
fi

ZSH_BIN_KERNEL="$(uname -s)"
ZSH_BIN_KERNEL="$(printf '%s' "$ZSH_BIN_KERNEL" | tr '[A-Z]' '[a-z]')"

case "$ZSH_BIN_KERNEL" in
  linux)
    if [ -z "$image" ]; then
      case "$ZSH_BIN_ARCH" in
        x86_64)         image=alpine:3.9.5;;
        i386|i586|i686) image=i386/alpine:3.9.5;;
        armv6l)         image=arm32v6/alpine:3.9.5;;
        armv7l)         image=arm32v7/alpine:3.9.5;;
        aarch64)        image=arm64v8/alpine:3.9.5;;
        ppc64le)        image=ppc64le/alpine:3.9.5;;
        *)
          >&2 echo '[error] unable to infer docker image'
          >&2 echo 'Please specify explicitly with `-i IMAGE`.'
          exit 1
        ;;
      esac
    fi
    : "${docker:=docker}"
    if [ -z "${docker##*/*}" ]; then
      if [ ! -x "$docker" ]; then
        >&2 echo "[error] not an executable file: $docker"
        exit 1
      fi
    else
      if ! command -v "$docker" >/dev/null 2>&1; then
        >&2 echo "[error] command not found: $docker"
        exit 1
      fi
    fi
  ;;
  freebsd)
    if [ -n "$image" ]; then
      >&2 echo '[error] docker image (-i) is not supported on freebsd'
      exit 1
    fi
    if [ -n "$docker" ]; then
      >&2 echo '[error] docker (-d) is not supported on freebsd'
      exit 1
    fi
  ;;
  darwin)
    if [ -n "$image" ]; then
      >&2 echo '[error] docker image (-i) is not supported on macOS'
      exit 1
    fi
    if [ -n "$docker" ]; then
      >&2 echo '[error] docker (-d) is not supported on macOS'
      exit 1
    fi
  ;;
  msys_nt-*|mingw32_nt-*|mingw64_nt-*|cygwin_nt-*)
    if ! printf '%s' "$ZSH_BIN_KERNEL" | grep -Eqx '[^-]+-[0-9]+\.[0-9]+(-.*)?'; then
      >&2 echo '[error] unsupported kernel, sorry!'
      exit 1
    fi
    ZSH_BIN_KERNEL="$(printf '%s' "$ZSH_BIN_KERNEL" | sed 's/^\([^-]*-[0-9]*\.[0-9]*\).*/\1/')"
    if [ -n "$image" ]; then
      >&2 echo '[error] docker image (-i) is not supported on windows'
      exit 1
    fi
    if [ -n "$docker" ]; then
      >&2 echo '[error] docker (-d) is not supported on windows'
      exit 1
    fi
  ;;
  *)
    >&2 echo '[error] unsupported kernel, sorry!'
    exit 1
  ;;
esac

>&2 echo "Building zsh..."
>&2 echo ""
[ -z "$docker" ] || >&2 echo "  DOCKER=$docker"
[ -z "$image"  ] || >&2 echo "  IMAGE=$image"
>&2 echo "  KERNEL=$ZSH_BIN_KERNEL"
>&2 echo "  ARCH=$ZSH_BIN_ARCH"
>&2 echo "  CPU=$ZSH_BIN_CPU"

case "$ZSH_BIN_KERNEL" in
  linux)
    dir="$(pwd)"
    "$docker" run                         \
      -e ZSH_BIN_KERNEL="$ZSH_BIN_KERNEL" \
      -e ZSH_BIN_ARCH="$ZSH_BIN_ARCH"     \
      -e ZSH_BIN_CPU="$ZSH_BIN_CPU"       \
      -v "$dir":/out                      \
      -w /out                             \
      --rm                                \
      -- "$image" /bin/sh -uexc "$build"
  ;;
  freebsd|darwin|msys*|mingw*|cygwin*)
    eval "$build"
  ;;
  *)
    >&2 echo "[internal error] unhandled kernel: $ZSH_BIN_KERNEL";
    exit 1
  ;;
esac