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