#!/usr/bin/env bash
set -eo pipefail
# This file will need to be run in bash, for now.
# === CONFIGURATION AND SETUP ===
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# minerva | lagom
FINAL_TARGET="${1:-minerva}"
# shellcheck source=/dev/null
. "${DIR}/../Meta/shell_include.sh"
exit_if_running_as_root "Do not run BuildJakt.sh as root, your Build directory will become root-owned"
ARCHES=("x86_64" "aarch64" "riscv64")
PREFIX="$DIR/Local/jakt"
VALID_TOOLCHAINS=()
declare -A CXX_GNU=()
declare -A CXX_CLANG=()
declare -A RANLIB_GNU=()
declare -A RANLIB_CLANG=()
declare -A BUILD_GNU=()
declare -A BUILD_CLANG=()
: "${BUILD_GNU[@]}" "${BUILD_CLANG[@]}" # make shellcheck understand that these might be used.
for ARCH in "${ARCHES[@]}"; do
TARGET="$ARCH-pc-minerva"
BUILD_GNU["$ARCH"]="$DIR/../Build/$ARCH"
BUILD_CLANG["$ARCH"]="$DIR/../Build/${ARCH}clang"
CXX_GNU["$ARCH"]="$DIR/Local/$ARCH/bin/$TARGET-g++"
CXX_CLANG["$ARCH"]="$DIR/Local/clang/bin/$TARGET-clang++"
# Indirectly accessed below.
# shellcheck disable=SC2034
RANLIB_GNU["$ARCH"]="$DIR/Local/$ARCH/bin/$TARGET-ranlib"
# shellcheck disable=SC2034
RANLIB_CLANG["$ARCH"]="$DIR/Local/clang/bin/llvm-ranlib"
if [ -x "${CXX_GNU["$ARCH"]}" ]; then
VALID_TOOLCHAINS+=("GNU;${ARCH}")
fi
if [ -x "${CXX_CLANG[${ARCH}]}" ]; then
VALID_TOOLCHAINS+=("CLANG;${ARCH}")
fi
done
if [ "$FINAL_TARGET" = minerva ] && [ "${#VALID_TOOLCHAINS}" -eq 0 ]; then
die "Need to build at least one C++ toolchain (either GNU or Clang) before BuildJakt.sh can succeed"
fi
REALPATH="realpath"
SYSTEM_NAME="$(uname -s)"
NPROC=$(get_number_of_processing_units)
if [ "$SYSTEM_NAME" = "OpenBSD" ]; then
REALPATH="readlink -f"
export CXX=eg++
fi
if command -v ginstall &>/dev/null; then
INSTALL=ginstall
else
INSTALL=install
fi
buildstep() {
NAME=$1
shift
"$@" 2>&1 | sed $'s|^|\x1b[34m['"${NAME}"$']\x1b[39m |'
}
buildstep_ninja() {
# When ninja writes to a pipe, it strips ANSI escape codes and prints one line per buildstep.
# Instead, use NINJA_STATUS so that we get colored output from LLVM's build and fewer lines of output when running in a tty.
# ANSI escape codes in NINJA_STATUS are slightly janky (ninja thinks that "\e[34m" needs 5 output characters instead of 5, so
# its middle elision is slightly off; also it would happily elide the "\e39m" which messes up the output if the terminal is too
# narrow), but it's still working better than the alternative.
NAME=$1
shift
env NINJA_STATUS=$'\e[34m['"${NAME}"$']\e[39m [%f/%t] ' "$@"
}
mkdir -p "$DIR/Tarballs"
JAKT_COMMIT_HASH="bf6e9ce89206fb744d8acc6e700e31e4330a5f25"
JAKT_NAME="jakt-${JAKT_COMMIT_HASH}"
JAKT_TARBALL="${JAKT_NAME}.tar.gz"
JAKT_GIT_URL="https://github.com/minerva/jakt"
function already_available() {
local TOOLCHAIN="$1"; shift
local ARCH="$1"; shift
HASH_FILE="${PREFIX}/.jakt-${TOOLCHAIN}-${ARCH}.hash"
if [ -f "$HASH_FILE" ] && [ "$JAKT_COMMIT_HASH" = "$(<"$HASH_FILE")" ]; then
# Make sure we have a binary.
if ! [ -e "$PREFIX/bin/jakt" ]; then
# We don't actually have anything, the file lied.
echo "$HASH_FILE exists and says we have $JAKT_COMMIT_HASH, but there's no associated binary; rebuilding!"
rm "$HASH_FILE"
fi
fi
if [ -f "$HASH_FILE" ] && [ "$JAKT_COMMIT_HASH" = "$(<"$HASH_FILE")" ]; then
echo "Already have the latest compiler built; remove $HASH_FILE to force-rebuild."
return 0
elif [ -f "$HASH_FILE" ]; then
echo "Expected $JAKT_COMMIT_HASH but found $(<"$HASH_FILE")"
return 1
else
echo "Expected $JAKT_COMMIT_HASH but found none (for $TOOLCHAIN $ARCH)"
return 1
fi
}
function stamp() {
local TOOLCHAIN="$1"; shift
local ARCH="$1"; shift
echo "$JAKT_COMMIT_HASH" > "${PREFIX}/.jakt-$TOOLCHAIN-$ARCH.hash"
}
# === DEPENDENCIES ===
buildstep dependencies echo "Checking whether 'ninja' is available..."
if ! command -v "${NINJA:-ninja}" >/dev/null; then
buildstep dependencies echo "Please make sure to install Ninja (for the '${NINJA:-ninja}' tool)."
exit 1
fi
buildstep dependencies echo "Checking whether 'cmake' is available..."
if ! command -v cmake >/dev/null; then
buildstep dependencies echo "Please make sure to install CMake (for the 'cmake' tool)."
exit 1
fi
buildstep dependencies echo "Checking whether your C++ compiler works..."
if ! ${CXX:-c++} -o /dev/null -xc++ - >/dev/null <<'PROGRAM'
int main() {}
PROGRAM
then
buildstep dependencies echo "Please make sure to install a working C++ compiler."
exit 1
fi
# === DOWNLOAD AND PATCH ===
pushd "$DIR/Tarballs"
if [ ! -d "${JAKT_NAME}" ]; then
echo "Downloading jakt..."
rm -f "$JAKT_TARBALL"
curl -L --output "$JAKT_TARBALL" "$JAKT_GIT_URL/archive/$JAKT_COMMIT_HASH.tar.gz"
echo "Extracting jakt..."
tar -xzf "${JAKT_TARBALL}"
else
echo "Using existing Jakt source directory"
fi
popd
# === COMPILE AND INSTALL ===
if ! already_available local host; then
rm -rf "$PREFIX"
mkdir -p "$PREFIX"
rm -rf "$DIR/Build/jakt"
mkdir -p "$DIR/Build/jakt"
pushd "$DIR/Build/jakt"
echo "XXX configure jakt"
buildstep "jakt/configure" cmake -S "$DIR/Tarballs/${JAKT_NAME}" -B . \
-DMINERVA_SOURCE_DIR="$DIR/.." \
-DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_BUILD_TYPE=Release \
-GNinja \
|| exit 1
echo "XXX build jakt"
buildstep_ninja "jakt/build" ninja jakt_stage1
echo "XXX install jakt"
buildstep_ninja "jakt/install" ninja install
popd
fi
stamp local host
if ! [ "$FINAL_TARGET" = minerva ]; then
echo "Done creating jakt toolchain for host"
exit 0
fi
build_for() {
TOOLCHAIN="$1"
ARCH="$2"
TARGET="$ARCH-pc-minerva"
JAKT_TARGET="$TARGET-unknown"
current_build="BUILD_${TOOLCHAIN}[${ARCH}]"
current_cxx="CXX_${TOOLCHAIN}[${ARCH}]"
current_ranlib="RANLIB_${TOOLCHAIN}[${ARCH}]"
BUILD="${!current_build}"
TARGET_CXX="${!current_cxx}"
TARGET_RANLIB="${!current_ranlib}"
if already_available "$TOOLCHAIN" "$ARCH"; then
return 0
fi
# On at least OpenBSD, the path must exist to call realpath(3) on it
if [ ! -d "$BUILD" ]; then
mkdir -p "$BUILD"
fi
BUILD=$($REALPATH "$BUILD")
echo "XXX building jakt support libs in $BUILD with $TARGET_CXX"
SYSROOT="$BUILD/Root"
echo SYSROOT is "$SYSROOT"
rm -rf "$DIR/Build/jakt-$TOOLCHAIN-$ARCH"
mkdir -p "$DIR/Build/jakt-$TOOLCHAIN-$ARCH"
pushd "$DIR/Build/jakt-$TOOLCHAIN-$ARCH"
echo "XXX minerva libc headers"
mkdir -p "$BUILD"
pushd "$BUILD"
mkdir -p Root/usr/include/
SRC_ROOT=$($REALPATH "$DIR"/..)
FILES=$(find \
"$SRC_ROOT"/AK \
"$SRC_ROOT"/Kernel/API \
"$SRC_ROOT"/Kernel/Arch \
"$SRC_ROOT"/Userland/Libraries/LibC \
"$SRC_ROOT"/Userland/Libraries/LibELF/ELFABI.h \
"$SRC_ROOT"/Userland/Libraries/LibRegex/RegexDefs.h \
-name '*.h' -print)
for header in $FILES; do
target=$(echo "$header" | sed \
-e "s|$SRC_ROOT/AK/|AK/|" \
-e "s|$SRC_ROOT/Userland/Libraries/LibC||" \
-e "s|$SRC_ROOT/Kernel/|Kernel/|" \
-e "s|$SRC_ROOT/Userland/Libraries/LibELF/|LibELF/|" \
-e "s|$SRC_ROOT/Userland/Libraries/LibRegex/|LibRegex/|")
buildstep "system_headers" mkdir -p "$(dirname "Root/usr/include/$target")"
buildstep "system_headers" "$INSTALL" "$header" "Root/usr/include/$target"
done
unset SRC_ROOT
popd
echo "XXX build jakt support libs"
rm -fr "$SYSROOT/usr/local/lib/$JAKT_TARGET"
buildstep "jakt/support/build/$TOOLCHAIN" "$PREFIX/bin/jakt" cross \
--only-support-libs \
--install-root "$PREFIX/usr/local" \
--target-triple "$JAKT_TARGET" \
--target-links-ak \
-C "$TARGET_CXX" \
-O \
-J "$NPROC"
buildstep "jakt/support/build/$TOOLCHAIN/ranlib" "$TARGET_RANLIB" "$PREFIX/usr/local/lib/$JAKT_TARGET/libjakt_runtime_$JAKT_TARGET.a"
buildstep "jakt/support/build/$TOOLCHAIN/ranlib" "$TARGET_RANLIB" "$PREFIX/usr/local/lib/$JAKT_TARGET/libjakt_main_$JAKT_TARGET.a"
popd
}
for TOOLCHAIN_AND_ARCH in "${VALID_TOOLCHAINS[@]}"; do
IFS=';' read -r TOOLCHAIN ARCH <<< "$TOOLCHAIN_AND_ARCH"
buildstep "build/$TOOLCHAIN/$ARCH" build_for "$TOOLCHAIN" "$ARCH"
done
for TOOLCHAIN_AND_ARCH in "${VALID_TOOLCHAINS[@]}"; do
IFS=';' read -r TOOLCHAIN ARCH <<< "$TOOLCHAIN_AND_ARCH"
stamp "$TOOLCHAIN" "$ARCH"
done
echo "Done creating jakt toolchain for targets " "${VALID_TOOLCHAINS[@]}"