zeek/auxil/spicy/ci/run-ci
Patrick Kelley 8fd444092b initial
2025-05-07 15:35:15 -04:00

319 lines
7.8 KiB
Bash
Executable File

#! /usr/bin/env bash
#
# Copyright (c) 2020-now by the Zeek Project. See LICENSE for details.
#
# Runs selected stages of the CI pipeline. Each stage assumes the
# preceesing one has been successfully executed already.
#
# We use bash here since that's useful (for pipefail in particular) and
# users are unlikely to run this script.
set -o pipefail
name=$(basename $0)
root=$(cd $(dirname $0)/.. && pwd)
build=
install=
color_red=$'\e[1;31m'
color_green=$'\e[1;32m'
color_yellow=$'\e[1;33m'
color_blue=$'\e[1;34m'
color_magenta=$'\e[1;35m'
color_cyan=$'\e[1;36m'
color_normal=$'\e[0m'
function usage {
cat <<EOF
Usage: ${mame} [<global options>] <stage> [<command options>]
Stages
configure [release|debug] [<configure options>] Configure the build for compiling a release/debug version
build Compile the code
install Install the built version
test-code Run code & formatting checks
test-build Run the test suite on the built version
test-install Run the test suite on the installed version
cleanup Delete all build artifacts.
Global options:
-r <dir> Base directory of repository checkout (default: ${root})
-b <dir> Build directory; will be deleted at completion (default: \${root}/build-ci)
Configure options:
--build-toolchain={yes,no} Build the Spicy compiler toolchain [default: yes]
--clang-tidy <path> Path to clang-tidy to use (default: found in PATH)
--cxx-compiler <path> Path to C++ compiler to use (default: found by cmake)
--disable-precompiled-headers Disable use of precompiled headers for developer tests
EOF
exit 1
}
function log_colored {
color=$1
shift
printf "%s" "${color}"
printf "%s" "$@"
printf "%s\n" "${color_normal}"
}
function log_stage {
echo
log_colored "${color_magenta}" "### $@"
}
function log_warning {
log_colored "${color_yellow}" "### $@"
}
function log_error {
log_colored "${color_red}" "### $@"
}
function log_diag {
log_colored "${color_yellow}" "--- $@"
}
function error {
echo "### Error: $@"
exit 1
}
function run_configure {
build_type="$1"
shift
mkdir -p ${install}
configure="./configure --builddir=${build} --prefix=${install} --generator=Ninja --enable-werror --enable-ccache --with-hilti-compiler-launcher=ccache"
clang_tidy=$(which clang-tidy 2>/dev/null)
if [ "${build_type}" == "release" ]; then
:
elif [ "${build_type}" == "debug" ]; then
configure="${configure} --enable-debug --enable-sanitizer"
else
usage
fi
while [ $# -ne 0 ]; do
case "$1" in
--cxx-compiler)
test $# -gt 0 || usage
configure="${configure} --with-cxx-compiler=$2"
shift 2;
;;
--clang-tidy)
test $# -gt 0 || usage
clang_tidy="$2"
test -x ${clang_tidy} || error "clang-tidy not found in $2"
shift 2;
;;
--build-toolchain)
configure="${configure} --build-toolchain=$2"
shift 2;
;;
--disable-precompiled-headers)
configure="${configure} --disable-precompiled-headers"
shift 1;
;;
--enable-werror)
configure="${configure} --enable-werror"
shift 1;
;;
--build-static-libs)
configure="${configure} --build-static-libs"
shift 1;
;;
*) usage;;
esac
done
if [ -e ${build} ]; then
error "Build directory ${build} already exists, delete first"
fi
test -z "${clang_tidy}" && log_stage "Warning: No clang-tidy found, will skip any related tests"
# Looks like Cirrus CI doesn't fetch tags.
git fetch --tags
log_stage "Running configure ... (${configure})"
${configure} || exit 1
mkdir -p ${artifacts}
echo "${clang_tidy}" >${build}/.clang_tidy
if [ -x "${clang_tidy}" ]; then
pushd "${build}" >/dev/null || exit 1
cmake -DCMAKE_CXX_CLANG_TIDY="${clang_tidy}" -DCMAKE_C_CLANG_TIDY="${clang_tidy}" ..
popd >/dev/null || exit 1
fi
}
function run_build {
log_stage "Building code ..."
# The level of parallelism chosen here is tuned for what's configured
# in `.cirrus.yml` so that (1) we align with number of CPUs, and (2) we
# do not trigger OOM kills in the Docker environments.
(cd ${build} && ninja -j4 all) || exit 1
log_stage "Building docs ..."
(cd ${root}/doc && make BUILDDIR=${build} DESTDIR=${artifacts}/doc doxygen html) || exit 1
}
function run_install {
log_stage "Installing code ..."
(cd ${build} && ninja install) || exit 1
}
function execute_btest {
name="$1"
cwd="$2"
alternative="$3"
if [ -n "${alternative}" ]; then
alternative="-a ${alternative}"
else
alternative=""
fi
if [ -n "${SPICY_BTEST_GROUPS}" ]; then
group="-g ${SPICY_BTEST_GROUPS}"
else
group=""
fi
log_stage "Running ${name} tests ... (${btest_alternative} ${btest_group})"
pushd ${cwd} >/dev/null
eval ${preload} \
btest -j 5 \
-f ${artifacts}/diag.log \
-x ${artifacts}/diag.xml \
-z 3 \
${alternative} \
${group}
rc=$?
if [ ${rc} != 0 ]; then
cp -a .tmp ${artifacts}/btest-tmp
log_diag "Begin diagnostics"
cat ${artifacts}/diag.log
log_diag "End diagnostics (complete test output in 'btest-tmp')"
log_error "Tests have failed"
fi
popd >/dev/null
return ${rc}
}
function run_test_btest {
alternative="$1"
execute_btest "Spicy" "tests" "${alternative}"
}
function run_test_code {
rc=0
pre-commit run -a --show-diff-on-failure || rc=1
return ${rc}
}
function run_test_common {
btest_alternative="$1"
rc=0
run_test_btest "${btest_alternative}" || rc=1
log_stage "Checking docs ..."
# We currently ignore this check since lint checks can fail spuriously if
# the remote is temporarily unavailable.
(cd "${root}"/doc && make BUILDDIR="${build}" DESTDIR="${artifacts}"/doc check) || true
return ${rc}
}
function run_test_build {
export SPICY_BUILD_DIRECTORY=${build}
run_test_common
return ${rc}
}
function run_test_install {
export SPICY_INSTALLATION_DIRECTORY=${install}
run_test_common installation
}
function run_cleanup {
log_stage "Cleaning up ..."
(cd ${build} && ninja clean) || exit 1
(cd doc && make clean) || exit 1
}
### Main
while getopts "r:b:" opt; do
case "${opt}" in
h)
usage
;;
r)
root=${OPTARG}
;;
b)
build=${OPTARG}
;;
*)
echo "unknown option -${opt}"
exit 1
;;
esac
done
shift $((OPTIND-1))
if [ -z "${build}" ]; then
build=${root}/build-ci
fi
install=/tmp/ci-install-$(basename ${build})
root=$(realpath ${root})
build=$(realpath ${build})
artifacts=${build}/ci
cmd=$1
shift
test -n "${cmd}" || usage
cd ${root}
test -e CMakeLists.txt || error "${root} is not the project's root git repository"
case "${cmd}" in
configure) (run_configure $@);;
build) (run_build $@);;
install) (run_install $@);;
test-build) (run_test_build $@);;
test-install) (run_test_install $@);;
test-code) (run_test_code $@);;
cleanup) (run_cleanup $@);;
*) usage;;
esac