#!/bin/sh
# akku-install: Akku's install hook. In the package directory it lives in,
# it builds a release shim into the consumer's .akku/ffi and writes
# src/libuv/private/config.sls naming it, which Akku then installs with the
# other libraries.
#
# `akku install` runs it before the install step: from Akku.manifest's
# scripts when the lock entry carries them (the Akku index copies them), or
# from the consumer's own manifest, by path. Run it with sh: an install from
# an archive keeps no file modes.
#
# It reads akku_path_root and akku_path_lib_ffi, which Akku sets, and CC: a
# command on PATH or a path, a relative one taken from the directory the
# hook was started in. Every make it runs has the environment's MAKEFLAGS,
# MFLAGS and MAKELEVEL cleared, so a parent make's variables cannot reach it,
# and FLAVOR=prod on its command line. It writes nothing into the package
# but config.sls: the build's own files go to a temporary directory under
# .akku/ffi, removed on exit or interruption.
#
# When chez-libuv itself is the project Akku installs, it does nothing: the
# checkout builds with `make build`. It refuses a package directory outside
# .akku/src that `make build` has built (a directory source pointing at a
# checkout), and writes nothing there.
#
# Otherwise it first deletes the package's config.sls, so that no earlier
# run's file outlives a failure. On failure it writes a stub in its place,
# whose first use raises the reason and names this hook's log
# ($akku_path_lib_ffi/chez-libuv-install.log); prints the log; and exits 1.
# Akku stops the install on that status from 1.1.1 on, and an older Akku
# installs the stub. Failing before make has said what config.sls exports,
# it can write no stub, and leaves none. The previous install's shim is
# replaced only once a new one is built.
set -u

# CC's command (its first word), when a relative path, is taken from here,
# before the cd below.
case ${CC:-} in
  '' | /*) ;;
  *)
    case ${CC%% *} in
      */*) CC="$(pwd)/$CC" ;;
    esac ;;
esac

cd "$(dirname "$0")/.." || exit 2
pkg=$(pwd -P)
config=src/libuv/private/config.sls

if [ -z "${akku_path_root:-}" ] || [ -z "${akku_path_lib_ffi:-}" ]; then
  echo "akku-install: akku install runs this (akku_path_root and akku_path_lib_ffi are unset)" >&2
  exit 2
fi

# akku_path_root is the installing project's .akku, as a physical path.
if [ "$(dirname "$akku_path_root")" = "$pkg" ]; then
  echo "chez-libuv: this is chez-libuv's own checkout, which builds with make build; the install hook does nothing here"
  exit 0
fi

ffi=$akku_path_lib_ffi
log=$ffi/chez-libuv-install.log
mkdir -p "$ffi" && : > "$log" || {
  echo "chez-libuv: cannot write the install hook's log at $log"
  exit 1
}
note() { printf '%s\n' "$*" >> "$log"; }
note "chez-libuv install hook in $pkg"

case $pkg/ in
  "$akku_path_root/src/"*) ;;
  *)
    origin=
    [ -f "$config" ] && origin=$(sed -n 2p "$config")
    case $origin in
      '' | ';; GENERATED by akku-install'*) built= ;;
      *) built=yes ;;
    esac
    if [ -n "$built" ] || ls build/lib/libchezlibuv.* > /dev/null 2>&1; then
      echo "chez-libuv: $pkg is a make build (its build/lib or config.sls); the install hook leaves it alone. Point Akku.lock at a clean copy of chez-libuv, or run make clean there."
      exit 1
    fi ;;
esac

[ -w src/libuv/private ] || {
  echo "chez-libuv: cannot write into $pkg/src/libuv/private"
  exit 1
}
rm -f "$config"

run_make() {
  env -u MAKEFLAGS -u MFLAGS -u MAKELEVEL make FLAVOR=prod "$@"
}

# scheme_string TEXT: TEXT with \ and " escaped for a Scheme string.
scheme_string() {
  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}

exports=
# fail REASON: write the stub, show the log, exit 1.
fail() {
  note "chez-libuv: $1"
  if [ -n "$exports" ]; then
    message=$(scheme_string "$1. The install hook's log is $log")
    {
      printf '%s\n' '#!r6rs' \
        ';; GENERATED by akku-install, which failed -- do not edit, do not commit.' \
        '(library (libuv private config)' \
        "  (export $exports)" \
        '  (import (rnrs))' \
        "  (define (failed) (error 'chez-libuv \"$message\"))"
      for name in $exports; do
        printf '  (define %s (failed))\n' "$name"
      done
      printf '  )\n'
    } > "$config"
  fi
  cat "$log"
  exit 1
}

facts=$(run_make -s akku-facts 2>> "$log") || fail "make could not read chez-libuv's Makefile"
fact() { printf '%s\n' "$facts" | sed -n "s/^$1=//p"; }
exports=$(fact exports)
[ -n "$exports" ] || fail "make printed no config.sls exports"
have_pkg=$(fact have_pkg)
floor=$(fact floor)
ext=$(fact ext)
libuv=$(fact libuv)

case $ffi in
  *[[:space:]]* | *'$'* | *:* | *%* | *"'"* | *'"'* | *'\'*)
    fail "the path $ffi holds a character make cannot take (whitespace, or one of \$ : % ' \" \\)" ;;
esac

cc=$(sh scripts/find-cc) || fail "no C compiler: set CC, or put cc, gcc or clang on PATH"

command -v pkg-config > /dev/null 2>&1 ||
  fail "pkg-config not found: install it (brew install pkg-config, or apt install pkg-config)"
[ "$have_pkg" = yes ] ||
  fail "pkg-config finds no libuv $floor or newer: install libuv (brew install libuv, or apt install libuv1-dev) and run akku install again"

shim=$ffi/libchezlibuv.$ext
new=$ffi/.libchezlibuv.$ext.new
# On macOS the linker records the -o path as the install name; give it the
# shim's final one.
install_name=
[ "$ext" = dylib ] && install_name="-Wl,-install_name,$shim"
build=$(mktemp -d "$ffi/.chez-libuv-build.XXXXXX") || fail "could not make a build directory in $ffi"
trap 'rm -rf "$build"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
rm -f "$new"
note "building with $cc, libuv $(pkg-config --modversion libuv)"
run_make CC="$cc" WERROR= BUILD_DIR="$build" SHIM="$new" SHIM_LDFLAGS="$install_name" "$new" >> "$log" 2>&1 ||
  fail "compiling the shim failed"
[ -f "$new" ] || fail "make built no shim"
mv -f "$new" "$shim" || fail "could not move the shim into $ffi"
run_make CC="$cc" WERROR= BUILD_DIR="$build" SHIM="$shim" CONFIG_GENERATOR=akku-install \
  -o "$shim" "$config" >> "$log" 2>&1 || fail "writing config.sls failed"
grep -qF "\"$shim\"" "$config" || fail "config.sls does not name $shim"
for lib in $libuv; do
  [ -f "$lib" ] || fail "the libuv library $lib, which pkg-config names, does not exist"
done

note "built $shim; $pkg/$config names it"
echo "chez-libuv: built $shim; config.sls names it"
