#!/bin/sh
# Put the engine inside the package, at install time.
#
# **This is what makes an installed copy self-contained.** `fathom_binary()` asks
# `system.file("bin", "fathom", package = "fathom")`, and nothing else in the
# package ever creates that file. Without this script the lookup falls through to
# a development checkout, which is fine on the machine that built it and useless
# anywhere else.
#
# R runs `configure` on install, not on `R CMD build`. That ordering is not
# cosmetic and it is the one trap here that ships a broken artifact silently:
# **install first, then build the tarball**, or the tarball carries no engine and
# works only for whoever built it.
#
#   R CMD INSTALL r-pkg/fathom          # runs this, fills inst/bin/
#   R CMD build   r-pkg/fathom          # tars what is now on disk
#
# Five ways the engine arrives, in the order they are tried:
#
#   1. already in `inst/bin/`   — a release tarball, and no workspace beside it
#   2. $FATHOM_BIN              — an engine built somewhere else
#   3. a `target/` above us     — a development checkout
#   4. built from the workspace — the crate sources that traveled with the repo
#   5. built from `tools/rust/` — the crate sources staged *inside* the package
#
# **Step 4 is what makes `remotes::install_github()` work.** A GitHub tarball
# carries `fathom-core/`, `fathom-cli/`, `Cargo.toml` and `Cargo.lock` but not
# `target/` (it is gitignored), so before this step a GitHub install failed at
# step 3.
#
# **Step 5 is what makes a subdirectory build work, and it is the one the package
# farm needs.** That farm clones the whole repository but then runs `R CMD build`
# on the `subdir` alone, so `../../` does not exist in the resulting tarball and
# steps 3 and 4 both miss. `.prepare` runs before that build, in the full clone,
# and copies the crates — and their vendored dependency — to `tools/rust/`.
#
# Failing loudly when none of the five holds is the point. Installing a package
# that is guaranteed not to answer anything is the silent drop this project
# refuses everywhere else, so the diagnostic below gives direction rather than a
# verdict.

set -e

# `configure.win` sources this file with the Windows name set, so the two
# platforms share one copy of the logic rather than two that can drift.
EXE="${FATHOM_EXE_NAME:-fathom}"
DEST="inst/bin"
STAGED="tools/rust"

# ---------------------------------------------------------------------------
# WebAssembly is not a platform this package can install on
# ---------------------------------------------------------------------------
#
# The farm builds every package for WebAssembly as well as for the three desktop
# platforms, and that build is the one place the whole design of this bridge
# cannot reach. The R package answers by running `fathom` as a separate process;
# R compiled to WebAssembly has no way to start one. So there is no engine that
# could be bundled here, and no arrangement of the five sources below that ends
# in a package able to describe a document.
#
# Refusing *here*, rather than after the search, also stops a cross-compile that
# cannot help: the farm's WebAssembly image replaces `cargo` with a shim that
# adds an emscripten target to any `cargo build` arriving without one, so step 5
# would compile happily and write something this script cannot use.
for _arg in "$@"; do
  case "${_arg}" in
    --host=*emscripten*) FATHOM_WASM_HOST=1 ;;
  esac
done
case "${CC}" in
  *emcc*) FATHOM_WASM_HOST=1 ;;
esac

if [ -n "${FATHOM_WASM_HOST}" ]; then
  echo "fathom: this package cannot be built for WebAssembly." >&2
  echo "" >&2
  echo "It describes a document by running the fathom engine as a separate" >&2
  echo "process, and R compiled to WebAssembly cannot start one. There is no" >&2
  echo "engine to bundle for this target, and an install without one could not" >&2
  echo "answer anything." >&2
  echo "" >&2
  echo "fathom is available for Linux, macOS and Windows." >&2
  exit 1
fi

# ---------------------------------------------------------------------------
# Climb once, and record both things worth finding on the way up. Counting
# levels would work today and break the first time the package moves; this
# counts nothing, which is what the binding already does at run time.
# ---------------------------------------------------------------------------
WORKSPACE=""
BUILT=""
DIR="$(pwd)"
while [ "${DIR}" != "/" ]; do
  if [ -z "${BUILT}" ]; then
    for PROFILE in release debug; do
      if [ -x "${DIR}/target/${PROFILE}/${EXE}" ]; then
        BUILT="${DIR}/target/${PROFILE}/${EXE}"
        break
      fi
    done
  fi
  if [ -z "${WORKSPACE}" ] && [ -f "${DIR}/Cargo.toml" ] && [ -f "${DIR}/fathom-cli/Cargo.toml" ]; then
    WORKSPACE="${DIR}"
  fi
  DIR="$(dirname "${DIR}")"
done

# Build `fathom-cli` from a workspace manifest and report the binary's path on
# success, nothing on failure.
#
# `cargo` is looked for in `~/.cargo/bin` as well as on `PATH`, because rustup
# installs it there and adds it to the *interactive* shell's profile — which the
# non-interactive shell `R CMD INSTALL` runs has not read.
build_engine() {
  _root="$1"
  _cargo=""
  if command -v cargo >/dev/null 2>&1; then
    _cargo="cargo"
  elif [ -x "${HOME}/.cargo/bin/cargo" ]; then
    _cargo="${HOME}/.cargo/bin/cargo"
  fi
  [ -n "${_cargo}" ] || return 0

  echo "* fathom: no prebuilt engine found — building it from ${_root}/." >&2
  "${_cargo}" --version >&2 || true

  # **`--offline` only when the dependency is vendored beside the sources.**
  # `.prepare` writes `.cargo/config.toml` next to the staged crates, which
  # redirects crates.io to `vendor/`. Passing `--offline` without that config
  # fails on a machine with a cold cargo registry — which is most farm builders
  # — so the flag follows the config rather than being assumed.
  _offline=""
  [ -f "${_root}/.cargo/config.toml" ] && [ -d "${_root}/vendor" ] && _offline="--offline"

  # **Build from inside the workspace, not with `--manifest-path` from outside.**
  # Cargo walks up from the *working directory* to find its configuration, not up
  # from the manifest, so a build launched from here would read a different
  # configuration than one launched from the crate root — and with a vendored
  # tree that difference is the whole build.
  #
  # `-j 2` because a shared build farm is not the place to ask for every core.
  #
  # `|| true` because a build failure should reach the diagnostic at the bottom
  # of this script, which names every fix, rather than exiting on cargo's alone.
  ( cd "${_root}" && "${_cargo}" build --release -p fathom-cli -j 2 ${_offline} ) >&2 || true

  # **Report nothing, but report it successfully.** The caller reads this
  # function through a command substitution — `FOUND="$(build_engine …)"` — and
  # under `set -e` an assignment whose substitution exits non-zero ends the
  # script right there. A bare `[ -f … ] && echo …` as the last command does
  # exactly that whenever the build produced no binary, which would make the
  # diagnostic at the bottom unreachable in the single case it is written for.
  if [ -f "${_root}/target/release/${EXE}" ]; then
    echo "${_root}/target/release/${EXE}"
  fi
  return 0
}

# 1. A release tarball already carries it — and *already carries it* has to be
#    told apart from *has a leftover from the last install*.
#
#    `inst/bin/` is gitignored, so in a development checkout it is whatever the
#    previous `R CMD INSTALL` put there. Preferring it unconditionally would mean
#    that once it existed, every later install shipped that engine no matter how
#    far the sources had moved. The fact that separates the two is not an mtime,
#    it is *what you are installing*: a release tarball has no workspace beside
#    it, and a development checkout does.
if [ -f "${DEST}/${EXE}" ] && [ -z "${WORKSPACE}" ]; then
  echo "* fathom: using the engine already in ${DEST}/"
  exit 0
fi

FOUND=""

# 2. An engine built elsewhere.
if [ -n "${FATHOM_BIN}" ] && [ -f "${FATHOM_BIN}" ]; then
  FOUND="${FATHOM_BIN}"
# 3. A development checkout, with a warm target/ somewhere above.
elif [ -n "${BUILT}" ]; then
  FOUND="${BUILT}"
fi

# Anything already on the path, before compiling one.
if [ -z "${FOUND}" ]; then
  FOUND="$(command -v "${EXE}" 2>/dev/null || true)"
fi

# 4. Build it from the crate sources that came along with the repo — a
#    `remotes::install_github()` install, where the tarball carries the whole tree.
if [ -z "${FOUND}" ] && [ -n "${WORKSPACE}" ]; then
  FOUND="$(build_engine "${WORKSPACE}")"
fi

# 5. Build it from the crate sources staged *inside* the package by `.prepare` —
#    a subdirectory build, which is how the package farm arrives. There is no
#    workspace above that tarball, so this is the only source left.
if [ -z "${FOUND}" ] && [ -f "${STAGED}/Cargo.toml" ] && [ -f "${STAGED}/fathom-cli/Cargo.toml" ]; then
  FOUND="$(build_engine "${STAGED}")"
fi

if [ -z "${FOUND}" ]; then
  echo "---------------------------- fathom ----------------------------" >&2
  echo "The fathom engine was not found, so this package cannot install." >&2
  echo "" >&2
  echo "fathom is a vocabulary over an engine, and the engine is a Rust" >&2
  echo "binary that has to exist before the R package can carry it. None" >&2
  echo "of the five sources had one:" >&2
  echo "  * a prebuilt engine in ${DEST}/" >&2
  echo "  * \$FATHOM_BIN" >&2
  echo "  * a target/{release,debug}/ in any directory above this one" >&2
  echo "  * a source build from the workspace (the whole repository)" >&2
  echo "  * a source build from ${STAGED}/ (sources staged by .prepare)" >&2
  echo "" >&2
  echo "Fixes, cheapest first:" >&2
  echo "  Build it:        cargo build --release -p fathom-cli" >&2
  echo "  Or point at one: FATHOM_BIN=/path/to/fathom R CMD INSTALL r-pkg/fathom" >&2
  echo "  No Rust?         install it from https://rustup.rs/" >&2
  echo "" >&2
  echo "If this is a subdirectory build, the likely cause is that .prepare did" >&2
  echo "not run or did not finish: the farm invokes it as \`|| true\`, so its" >&2
  echo "failure is swallowed and surfaces here instead. Check the build log for" >&2
  echo "a \`fathom/.prepare:\` line." >&2
  echo "" >&2
  echo "Installing without it would produce a package that cannot describe a" >&2
  echo "document, which is the failure this check exists to make loud." >&2
  echo "----------------------------------------------------------------" >&2
  exit 1
fi

mkdir -p "${DEST}"
cp "${FOUND}" "${DEST}/${EXE}"
chmod 0755 "${DEST}/${EXE}"
echo "* fathom: bundled the engine from ${FOUND}"

exit 0
