From 2ae8b374dac83ed6886b692941ee85d1a2ee1b89 Mon Sep 17 00:00:00 2001 From: Florian Fischer <florian.fischer@muhq.space> Date: Tue, 19 Apr 2022 14:16:07 +0200 Subject: [PATCH] add tooling from emper --- .clang-tidy | 25 +++++ .gitignore | 1 + .gitlab-ci.yml | 79 ++++++++++++++ Makefile | 73 +++++++++++++ compile_commands_wo_subprojects/.gitignore | 1 + docker.sh | 16 +++ tools/check-format | 79 ++++++++++++++ tools/docker-prepare | 13 +++ tools/gen-compile-commands-wo-subprojects | 26 +++++ tools/prepare-build-dir | 116 +++++++++++++++++++++ tools/run-clang-tidy | 45 ++++++++ 11 files changed, 474 insertions(+) create mode 100644 .clang-tidy create mode 100644 .gitlab-ci.yml create mode 100644 Makefile create mode 100644 compile_commands_wo_subprojects/.gitignore create mode 100755 docker.sh create mode 100755 tools/check-format create mode 100755 tools/docker-prepare create mode 100755 tools/gen-compile-commands-wo-subprojects create mode 100755 tools/prepare-build-dir create mode 100755 tools/run-clang-tidy diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..0acc102 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,25 @@ +Checks: > + bugprone-*, + cert-*, + linuxkernel-*, + modernize-*, + performance-*, + portability-*, + readability-*, + -bugprone-easily-swappable-parameters, + -cert-err58-cpp, + -clang-diagnostic-empty-translation-unit, + -readability-braces-around-statements, + -readability-function-cognitive-complexity, + -readability-implicit-bool-conversion, + -readability-isolate-declaration, + -readability-magic-numbers, + +WarningsAsErrors: > + bugprone-*, + modernize-*, + clang-*, + readability-*, + performance-*, + +HeaderFilterRegex: '(?!subprojects).*' diff --git a/.gitignore b/.gitignore index 0c14453..fbd8cd6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .cache/ +build build*/ subprojects/liburing/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..9896520 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,79 @@ +image: "flowdalic/debian-testing-dev:1.20" + +before_script: + - ulimit -a + - nproc + - | + readarray TOOLS <<EOF + c++ + cc + clang + clang++ + clang-tidy + doxygen + g++ + gcc + include-what-you-use + meson + nasm + valgrind + EOF + for tool in ${TOOLS[@]}; do + echo -n "$tool version: " + $tool --version + done + +cache: + paths: + - subprojects/packagecache + +variables: + BUILDDTYPE: debugoptimized + CC: gcc + CXX: g++ + EXTRA_NINJA_ARGS: -v + +smoke-test-suite: + extends: + - .meson-test + stage: smoke-test + script: make smoke-test-suite + +fast-static-analysis: + script: make fast-static-analysis + +tidy: + script: make tidy + +.build: + script: + - make + +.release-build: + variables: + BUILDTYPE: release + +.debug-build: + variables: + BUILDTYPE: debug + +.clang: + variables: + CC: clang + CXX: clang++ + +build-release: + extends: + - .build + - .release-build + +build-clang: + extends: + - .build + - .clang + +build-clang-release: + extends: + - .build + - .clang + - .release-build diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..66f3c78 --- /dev/null +++ b/Makefile @@ -0,0 +1,73 @@ +SHELL = bash + +# https://stackoverflow.com/a/39124162/194894 +word-dot = $(word $2,$(subst ., ,$1)) + +MESON_VERSION=$(shell meson --version) +MESON_MAJOR_VERSION=$(call word-dot, $(MESON_VERSION), 1) +MESON_MINOR_VERSION=$(call word-dot, $(MESON_VERSION), 2) + +.PHONY: all build check check-format clean distclean\ + doc release debug stresstest test + +all: build + +export BUILDTYPE ?= debugoptimized +export BUILDDIR = build-$(BUILDTYPE) + +NPROC := $(shell nproc) +JOBS := $(shell echo $$(( $(NPROC) + 6))) +LOAD := $(shell echo $$(( $(NPROC) * 2))) + +NINJA_BIN ?= ninja +NINJA := $(NINJA_BIN) -j $(JOBS) -l $(LOAD) $(EXTRA_NINJA_ARGS) + +build: + [[ -L build ]] || ./tools/prepare-build-dir + $(NINJA) -C $@ + +release: + rm -f build + $(MAKE) build BUILDTYPE=$@ + +debug: + rm -f build + $(MAKE) build BUILDTYPE=$@ + +.PHONY: clang +clang: + rm -f build + $(MAKE) build \ + CC=clang CXX=clang++ \ + BUILDDIR="build-$@" + +.PHONY: fast-static-analysis +fast-static-analysis: all check-format + +.PHONY: static-analysis +static-analysis: fast-static-analysis tidy + +check: static-analysis + +clean: + rm -f build + rm -rf build-* + +distclean: clean + git clean -x -d -f + +check-format: + ./tools/check-format + +.PHONY: format +format: all + $(NINJA) -C build clang-format + +.PHONY: tidy +tidy: compile_commands_wo_subprojects/compile_commands.json + ./tools/run-clang-tidy + +build/compile_commands.json: all + +compile_commands_wo_subprojects/compile_commands.json: all build/compile_commands.json + ./tools/gen-compile-commands-wo-subprojects diff --git a/compile_commands_wo_subprojects/.gitignore b/compile_commands_wo_subprojects/.gitignore new file mode 100644 index 0000000..c52ad08 --- /dev/null +++ b/compile_commands_wo_subprojects/.gitignore @@ -0,0 +1 @@ +compile_commands.json \ No newline at end of file diff --git a/docker.sh b/docker.sh new file mode 100755 index 0000000..74fe18c --- /dev/null +++ b/docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# The directory of this script is also the projects' root directory. +PROJ_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +IMAGE=$(sed --regexp-extended --quiet 's;^image: "([^"]*)"$;\1;p' "${PROJ_ROOT}/.gitlab-ci.yml") + +docker run \ + --volume="${PROJ_ROOT}:${PROJ_ROOT}" \ + --interactive \ + --tty \ + --env USER_ID="${UID}" \ + --env GROUP_ID="$(id -g ${USER})" \ + --security-opt=seccomp:unconfined \ + "${IMAGE}" \ + "${PROJ_ROOT}/tools/docker-prepare" "${PROJ_ROOT}" $@ diff --git a/tools/check-format b/tools/check-format new file mode 100755 index 0000000..f3a9b43 --- /dev/null +++ b/tools/check-format @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Pretty fancy method to get reliable the absolute path of a shell +# script, *even if it is sourced*. Credits go to GreenFox on +# stackoverflow: http://stackoverflow.com/a/12197518/194894 +pushd . > /dev/null +SCRIPTDIR="${BASH_SOURCE[0]}"; +while([ -h "${SCRIPTDIR}" ]); do + cd "`dirname "${SCRIPTDIR}"`" + SCRIPTDIR="$(readlink "`basename "${SCRIPTDIR}"`")"; +done +cd "`dirname "${SCRIPTDIR}"`" > /dev/null +SCRIPTDIR="`pwd`"; +popd > /dev/null + +DEBUG=false +VERBOSE=false +while getopts dv OPT; do + case $OPT in + d) + set -x + DEBUG=true + VERBOSE=true + ;; + v) + VERBOSE=true + ;; + *) + echo "usage: ${0##*/} [-dq} [--] ARGS..." + exit 2 + esac +done +shift $(( OPTIND - 1 )) +OPTIND=1 + +ROOTDIR=$(readlink -f "${SCRIPTDIR}/..") + +MAX_PROCS=$(nproc) + +FILES_TO_CHECK_FILE=$(mktemp) +cleanup() { + rm "${FILES_TO_CHECK_FILE}" +} +if ! $DEBUG; then + trap cleanup EXIT +fi + +cd "${ROOTDIR}" + +PRUNE_PATHS=() +PRUNE_PATHS+=(./build*) # Generated files +PRUNE_PATHS+=(./subprojects) # Subprojects, since are under different licenses + +PRUNE_PATH_ARG="" +# https://stackoverflow.com/a/12298615/194894 +for path in "${PRUNE_PATHS[@]::${#PRUNE_PATHS[@]}-1}"; do + PRUNE_PATH_ARG+="-path ${path} -o " +done +PRUNE_PATH_ARG+="-path ${PRUNE_PATHS[-1]}" + +# shellcheck disable=SC2086 +find . \( ${PRUNE_PATH_ARG} \) -prune -o \ + -type f -regextype posix-extended -regex '.*\.(c|h|cpp|hpp)' -print0 \ + > "${FILES_TO_CHECK_FILE}" + +if $VERBOSE; then + echo "About to check the following files for correct formatting via clang-format" + tr '\0' '\n' < "${FILES_TO_CHECK_FILE}" +fi + +# Note that the --dry-run and --Werror clang-format arguments require +# clang-format 10 or higher. See https://reviews.llvm.org/D68554 +xargs --null --max-args=3 --max-procs="${MAX_PROCS}" \ + clang-format --style=file --dry-run -Werror \ + < "${FILES_TO_CHECK_FILE}" + +FILE_COUNT=$(<"${FILES_TO_CHECK_FILE}" tr -cd '\0' | wc -c) +echo "Checked ${FILE_COUNT} files for format violations" diff --git a/tools/docker-prepare b/tools/docker-prepare new file mode 100755 index 0000000..1dedbcf --- /dev/null +++ b/tools/docker-prepare @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +useradd -u "${USER_ID}" -o -m user +groupmod -g "${GROUP_ID}" user + +OUTSIDE_PROJ_ROOT="${1}" +shift + +cd "${OUTSIDE_PROJ_ROOT}" + +# shellcheck disable=SC2068 +exec sudo -u user $@ diff --git a/tools/gen-compile-commands-wo-subprojects b/tools/gen-compile-commands-wo-subprojects new file mode 100755 index 0000000..924ae10 --- /dev/null +++ b/tools/gen-compile-commands-wo-subprojects @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +import json + +COMPILE_COMMANDS_FILENAME = "compile_commands.json" + +input_compile_db_path = Path(COMPILE_COMMANDS_FILENAME) + +with input_compile_db_path.open() as f: + input_compile_db = json.load(f) + +output_compile_db = [] + +for entry in input_compile_db: + entry_file = entry["file"] + if entry_file.startswith("../subprojects/"): + continue + + output_compile_db.append(entry) + +output_compile_db_path = Path("compile_commands_wo_subprojects") / Path(COMPILE_COMMANDS_FILENAME) + +with output_compile_db_path.open(mode='w') as f: + json.dump(output_compile_db, f, indent=4) diff --git a/tools/prepare-build-dir b/tools/prepare-build-dir new file mode 100755 index 0000000..c02e75d --- /dev/null +++ b/tools/prepare-build-dir @@ -0,0 +1,116 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Pretty fancy method to get reliable the absolute path of a shell +# script, *even if it is sourced*. Credits go to GreenFox on +# stackoverflow: http://stackoverflow.com/a/12197518/194894 +pushd . > /dev/null +SCRIPTDIR="${BASH_SOURCE[0]}"; +while([ -h "${SCRIPTDIR}" ]); do + cd "`dirname "${SCRIPTDIR}"`" + SCRIPTDIR="$(readlink "`basename "${SCRIPTDIR}"`")"; +done +cd "`dirname "${SCRIPTDIR}"`" > /dev/null +SCRIPTDIR="`pwd`"; +popd > /dev/null + +DEBUG=false +QUIET=false +while getopts :dq OPT; do + case $OPT in + d) + set -x + DEBUG=true + ;; + q) + QUIET=true + ;; + *) + echo "usage: ${0##*/} [-dq} [--] ARGS..." + exit 2 + esac +done +shift $(( OPTIND - 1 )) +OPTIND=1 + +ROOTDIR=$(readlink -f "${SCRIPTDIR}/..") + +set +u +if [[ ! ${BUILDTYPE} ]]; then + BUILDTYPE="debugoptimized" + echo "BUILDTYPE not explicitly set via environment variable, defaulting to ${BUILDTYPE}" +fi +if [[ ! ${BUILDDIR} ]]; then + BUILDDIR="build-${BUILDTYPE}" +fi +set -u + +readonly ABSOLUTE_BUILDDIR="${ROOTDIR}/${BUILDDIR}" +readonly ABSOLUTE_BUILDDIR_SYMLINK="${ROOTDIR}/build" + +if [[ ! -d "${ABSOLUTE_BUILDDIR}" ]]; then + mkdir "${ABSOLUTE_BUILDDIR}" +fi + +if [[ ! -d "${ABSOLUTE_BUILDDIR_SYMLINK}" + || $(realpath "${ABSOLUTE_BUILDDIR}") != $(realpath "${ABSOLUTE_BUILDDIR_SYMLINK}") ]]; then + if [[ -e "${ABSOLUTE_BUILDDIR_SYMLINK}" ]]; then + if [[ ! -L "${ABSOLUTE_BUILDDIR_SYMLINK}" ]]; then + echo "${ABSOLUTE_BUILDDIR_SYMLINK} is not a symlink" + exit 1 + fi + rm "${ABSOLUTE_BUILDDIR_SYMLINK}" + fi + + ln -rs "${ABSOLUTE_BUILDDIR}" "${ABSOLUTE_BUILDDIR_SYMLINK}" +fi + +# Now filter the environment variables for the configuration +# arguments. +# TODO: Ideally this should be determined by parsing the meson project +# name. But this is not easily possibly right now. See +# https://github.com/mesonbuild/meson/issues/3535 +readonly VARIABLE_PREFIX="PROJ_" +MESON_ARGS=() +for var in $(compgen -e); do + if [[ ${var} != ${VARIABLE_PREFIX}* ]]; then + continue; + fi + + # Strip the variable prefix from the build option environment variable. + MESON_BUILD_OPTION_NAME="${var#${VARIABLE_PREFIX}}" + # Lowercase the build option name. + MESON_BUILD_OPTION_NAME="${MESON_BUILD_OPTION_NAME,,}" + MESON_BUILD_OPTION_VALUE="${!var}" + if [[ -z $MESON_BUILD_OPTION_VALUE ]]; then + echo "Environment variable ${var} is empty" + exit 1 + fi + MESON_ARGS+=("-D${MESON_BUILD_OPTION_NAME}=${MESON_BUILD_OPTION_VALUE}") +done + +LOGFILE=$(mktemp --tmpdir=/var/tmp) +cleanup() { + rm -f "${LOGFILE}" +} +trap cleanup EXIT + +if ! $QUIET; then + set -x +fi + +meson --buildtype=${BUILDTYPE} \ + --fatal-meson-warnings \ + ${MESON_ARGS[@]} \ + "${ABSOLUTE_BUILDDIR}" |\ + tee "${LOGFILE}" + +if ! $DEBUG; then + set +x +fi + +if grep -F "WARNING: Unknown options:" "${LOGFILE}"; then + echo "ERROR: Unknown meson options found" + rm -r "${ABSOLUTE_BUILDDIR}" "${ABSOLUTE_BUILDDIR_SYMLINK}" + exit 1 +fi diff --git a/tools/run-clang-tidy b/tools/run-clang-tidy new file mode 100755 index 0000000..ebaeb67 --- /dev/null +++ b/tools/run-clang-tidy @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ROOTDIR="$(realpath "${SCRIPTDIR}/..")" + +while getopts dv OPT; do + case $OPT in + d) + set -x + ;; + + *) + echo "usage: ${0##*/} [-d]" + exit 2 + esac +done +shift $(( OPTIND - 1 )) +OPTIND=1 + +RUN_CLANG_TIDY_CANDIDATES=( + run-clang-tidy + run-clang-tidy.py + /usr/share/clang/run-clang-tidy.py +) + +for candidate in "${RUN_CLANG_TIDY_CANDIDATES[@]}"; do + if ! command -v "${candidate}"; then + continue; + fi + + RUN_CLANG_TIDY="${candidate}" + break; +done + +if [[ ! -v RUN_CLANG_TIDY ]]; then + echo "No run-clang-tidy executable found" + exit 1 +fi + +JOBS=$(nproc) + +${RUN_CLANG_TIDY} \ + -p "${ROOTDIR}/compile_commands_wo_subprojects/" \ + -j "${JOBS}" -- GitLab