#! /usr/bin/env bash

# @@ Do we really want to require PDF doc generation? This will unlikely
#    work anywhere except Linux. Maybe we should only distribute .xhtml
#    and only generate PDFs for web publishing? Maybe decide when moving
#    to ad hoc rules?

# Boostrap the build2 development environment.
#
# Note that this script runs git-update-index commands specified in the
# README-GIT files of cloned repositories.
#
# This script assumes the following steps have been performed:
#
# 1. The latest staged toolchain has been temporarily installed somewhere
#    other than /usr/local (e.g., /tmp/build2) and is in PATH. Hint: use
#    --local to speed things up seeing that this is a throw-away installation.
#
# 2. The CLI and ODB compilers have been built and are either installed (if
#    you don't plan to contribute to them) or symlinked in /usr/local/bin/.
#    For the latter case they would typically go to ~/work/{cli,odb/}, for
#    example, for CLI:
#
#    NOTE: clone using the git.codesynthesis.com:/var/scm/... SSH URL for rw
#          access.
#
#    mkdir -p ~/work/cli
#    cd ~/work/cli
#    git clone --recursive https://git.codesynthesis.com/cli/cli.git
#    cd cli
#    bdep init -C ../builds/gccN-asan @gccN-asan cc \
#      config.cxx=g++-N                             \
#      config.cc.coptions="-Wall -Wextra -Werror -g3 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer"
#    b
#    sudo ln -s "$(pwd)/cli/cli" /usr/local/bin/cli
#    which cli
#    cli --version
#
#    For ODB (no sanitizer use is currently possible due to the way GCC is
#    built):
#
#    mkdir -p ~/work/odb
#    cd ~/work/odb
#    git clone --recursive https://git.codesynthesis.com/odb/odb.git
#    cd odb
#    bdep init -C ../builds/gccN-asan @gccN-asan cc \
#      config.cxx=g++-N                             \
#      config.cc.coptions="-Wall -Wextra -Werror -g3"
#    b
#    sudo ln -s "$(pwd)/odb/odb"    /usr/local/bin/odb
#    sudo ln -s "$(pwd)/odb/odb.so" /usr/local/bin/odb.so
#    which odb
#    odb --version
#
# Once this is done, you should be able to:
#
# NOTE: clone using the git.build2.org:/var/scm/... SSH URL and pass the
#       --ssh bootstrap option for rw access.
#
# mkdir -p ~/work/build2
# cd ~/work/build2
# git clone --recursive https://git.build2.org/etc.git
# PATH="/tmp/build2/bin:$PATH" etc/bootstrap
#
# After a successful bootstrap you can remove the staged toolchain.
#
# Also a few notes and tips on the development process: Because the toolchain
# is used for its own development you will sometimes find yourself in a
# situation where one or more tools in the toolchain are no longer runnable or
# functioning correctly but to fix this you need to be able to run those
# tools. This can happen, for example, because of the linking errors or
# because the version (and therefore the name) of one of the shared libraries
# has changed as a result of updating another part of a toolchain (for
# instance, updating bpkg can trigger an update of libbpkg and this could make
# bdep, which also depends on libbpkg, no longer runnable).
#
# There are several mechanisms for recovering from such situations. If the
# build system is functional, it itself and the rest of the toolchain can
# always be update by first disabling the auto-synchronization hooks (which
# invoke bdep and bpkg). For example, the following commands should get the
# toolchain back into the fully-functional state:
#
# BDEP_SYNC=0 b build2/build2/ bpkg/bpkg/ bdep/bdep/
#             b build2/build2/ bpkg/bpkg/ bdep/bdep/
#
# If the build system itself is not functional, it can always be rebuilt using
# the bootstrapped build system (b-boot). For example:
#
# BDEP_SYNC=0 b-boot build2/build2/
# BDEP_SYNC=0 b      build2/build2/
#
# Note that the bootstrap build system can only be used to update build2. It
# also makes sense to rebuild it from time to time to keep it reasonably up to
# date with master. Normally this is done when master is assumed to be in a
# reasonably good shape. See build2/INSTALL for details on the bootstrap build
# system.
#
# Options:
#
# --ssh
#    Use SSH URL for cloning (rw access).
#
# --no-symlink
#    Do not create executable symlinks in /usr/local/bin/.
#
# --no-clone
#    Do not clone the repositories or build the bootstrap build system
#    (b-boot) assuming this has already been done. This option is primarily
#    useful for initializing additional configurations (e.g., Clang in
#    addition to GCC).
#
# --cxx
# --cfg
#    C++ compiler to use and the corresponding build configuration name, for
#    example, g++-9/gcc9 or clang++-8/clang8; g++/gcc by default.
#
owd="$(pwd)"
trap "{ cd '$owd'; exit 1; }" ERR
set -o errtrace # Trap in functions.

function info () { echo "$*" 1>&2; }
function error () { info "$*"; exit 1; }

# Run a command with tracing (similar to set -x).
#
# Note that this function will execute a command with arguments that contain
# spaces but it will not print them as quoted (and neither does set -x).
#
function run ()
{
  echo "+ $@" 1>&2
  "$@"
}

# Make sure the necessary tools are runnable.
#
cli  --version >/dev/null
odb  --version >/dev/null
b    --version >/dev/null
bpkg --version >/dev/null
bdep --version >/dev/null

url="https://git.build2.org"
sym=true
clone=true
cxx=g++
cfg=gcc

while [ $# -gt 0 ]; do
  case $1 in
    --ssh)
      url="git.build2.org:/var/scm"
      shift
      ;;
    --no-symlink)
      sym=
      shift
      ;;
    --no-clone)
      clone=
      shift
      ;;
    --cxx)
      shift
      cxx="$1"
      shift
      ;;
    --cfg)
      shift
      cfg="$1"
      shift
      ;;
    *)
      error "unexpected $1"
      ;;
  esac
done

cfg_bs="$cfg-asan-bs"
cfg_pm="$cfg-asan-pm"

function git_clone () # <url>
{
  local u d l

  u="$1"
  d="$(basename -s .git "$u")"

  run git clone --recursive "$u"

  # Run git-update-index commands from README-GIT.
  #
  if [ -f "$d/README-GIT" ]; then
    run cd "$d"
    l=
    while read l || [ -n "$l" ]; do
      info "+ $l"
      eval $l
    done < <(sed -rn -e 's/^(git update-index .+)/\1/p' README-GIT)
    run cd -
  fi

  # Generate documentation (currently and temporarily handled with a script).
  #
  if [ -f "$d/doc/cli.sh" ]; then
    run cd "$d/doc"
    run ./cli.sh
    run cd -
  fi
}

if [ "$clone" ]; then
  git_clone "$url/libbutl.git"
  git_clone "$url/build2.git"

  git_clone "$url/libbpkg.git"
  git_clone "$url/bpkg.git"
  git_clone "$url/bdep.git"

  # Build the bootstrap build system (b-boot).
  #
  run cd build2
  run make -f bootstrap.gmake CXX="$cxx" CXXFLAGS=-O3 -j 8
  run make -f bootstrap.gmake CXX="$cxx" CXXFLAGS=-O3 cleano # Cleanup objs.
  run build2/b-boot --version
  run cd -
fi

# @@ TODO: make build system configuration ASAN/TSAN configurable.
#
# ASAN: -g3 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer
# TSAN: -g3 -fsanitize=thread

run mkdir -p builds

# Build system configuration.
#
# Notes:
#
#   1. Using ?cli since this should be buildable by b-boot.
#
#   2. Only building shared libraries for speed of development.
#
run bpkg create -d "builds/$cfg_bs" cc ?cli \
  config.cxx="$cxx" \
  config.cc.coptions="-Wall -Wextra -Werror -g3 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer" \
  config.cc.loptions="-fuse-ld=gold -Wl,--threads,--thread-count,4" \
  config.bin.lib=shared \
  config.install.root=/tmp/install config.dist.root=/tmp/dist

run bdep init -d libbutl -A "builds/$cfg_bs" "@$cfg_bs" --no-default
run bdep init -d build2  -A "builds/$cfg_bs" "@$cfg_bs"

run b build2/build2/
run build2/build2/b --version

# Package manager (and the rest of the toolchain) configuration.
#
run bpkg create -d "builds/$cfg_pm" cc cli \
  config.cxx="$cxx" \
  config.cc.coptions="-Wall -Wextra -Werror -g3 -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer" \
  config.cc.loptions="-fuse-ld=gold -Wl,--threads,--thread-count,4" \
  config.bin.lib=shared \
  config.install.root=/tmp/install config.dist.root=/tmp/dist

run bdep init -d libbutl -A "builds/$cfg_pm" "@$cfg_pm" --default
run bdep init -d libbpkg -A "builds/$cfg_pm" "@$cfg_pm"
run bdep init -d bpkg    -A "builds/$cfg_pm" "@$cfg_pm"
run bdep init -d bdep    -A "builds/$cfg_pm" "@$cfg_pm"

# Generate database support (currently and temporarily handled with a script).
#
# Note: this has to be done after bdep-init since we need the libodb headers.
#       We also have to pre-update version headers.
#
run bpkg update -d "builds/$cfg_pm" libodb
run b "builds/$cfg_pm/libbutl/libbutl/hxx{version}"
run b "builds/$cfg_pm/libbpkg/libbpkg/hxx{version}"
run b "builds/$cfg_pm/bpkg/bpkg/hxx{version common-options}"
run b "builds/$cfg_pm/bdep/bdep/hxx{version common-options project-options}"

run cd bpkg/bpkg
run ./odb.sh
run cd -

run cd bdep/bdep
run ./odb.sh
run cd -

run b bpkg/bpkg/ bdep/bdep/
run bpkg/bpkg/bpkg --version
run bdep/bdep/bdep --version

# Add symlinks.
#
if [ "$sym" ]; then
  # Note that we symlink the actual executable in the build configuration
  # rather than the backlink in the forwarded source to make the old binary
  # runnable in the face of compilation errors (which trigger the removal of
  # backlinks).
  #
  run sudo ln -s "$owd/build2/build2/b-boot"            /usr/local/bin/b-boot
  run sudo ln -s "$owd/$builds/$cfg_bs/build2/build2/b" /usr/local/bin/b
  run sudo ln -s "$owd/$builds/$cfg_pm/bpkg/bpkg/bpkg"  /usr/local/bin/bpkg
  run sudo ln -s "$owd/$builds/$cfg_pm/bdep/bdep/bdep"  /usr/local/bin/bdep

  run export PATH="/usr/local/bin:$PATH"
  run which b bpkg bdep

  # Re-run update using the bootstrapped toolchain (normally should be a
  # noop).
  #
  run b build2/build2/ bpkg/bpkg/ bdep/bdep/
fi