From ba596bbd798060cf288693c2c0afc54cc041ba7c Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Thu, 16 Oct 2025 01:04:17 -0700 Subject: [PATCH 01/41] feat: add cross compilation capabilities (closes #22) --- .dockerignore | 193 ++++++++++++++++++++++++++++++++++ CMakePresets.json | 77 ++++++++++++++ Dockerfile.aarch64 | 29 +++++ Makefile | 13 ++- cmake/aarch64-toolchain.cmake | 15 +++ 5 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile.aarch64 create mode 100644 cmake/aarch64-toolchain.cmake diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..631eaea --- /dev/null +++ b/.dockerignore @@ -0,0 +1,193 @@ +# Same as .gitignore + +# Temporary tests +temp/ + +# Binary files +bin/ +build/ +*.so +*.o +*.out +*.a +*.exe + +##### From Python.gitignore +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +reports/ +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Vim swap file +.swp diff --git a/CMakePresets.json b/CMakePresets.json index b8b9d99..61565e2 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -41,6 +41,18 @@ ] } }, + { + "name": "aarch64-overlay", + "hidden": true, + "description": "Overrides for AArch64 cross-compilation", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/aarch64-toolchain.cmake", + "ASMGRADER_ENABLE_SANITIZER_ADDRESS": "OFF", + "ASMGRADER_ENABLE_SANITIZER_LEAK": "OFF", + "ASMGRADER_ENABLE_SANITIZER_THREAD": "OFF", + "ASMGRADER_ENABLE_SANITIZER_UNDEFINED": "OFF" + } + }, { "name": "unixlike-gcc-debug", "displayName": "gcc Debug", @@ -85,6 +97,26 @@ "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, + { + "name": "aarch64-clang-debug", + "inherits": ["unixlike-clang-debug", "aarch64-overlay"], + "displayName": "clang Debug (AArch64)" + }, + { + "name": "aarch64-clang-release", + "inherits": ["unixlike-clang-release", "aarch64-overlay"], + "displayName": "clang Release (AArch64)" + }, + { + "name": "aarch64-gcc-debug", + "inherits": ["unixlike-gcc-debug", "aarch64-overlay"], + "displayName": "gcc Debug (AArch64)" + }, + { + "name": "aarch64-gcc-release", + "inherits": ["unixlike-gcc-release", "aarch64-overlay"], + "displayName": "gcc Release (AArch64)" + }, { "name": "unixlike-docs-only", "displayName": "docs only", @@ -128,6 +160,31 @@ "description": "Target Unix-like OS, only configure for docs and nothing else", "configurePreset": "unixlike-docs-only", "targets": "doxygen-docs" + }, + { + "name": "aarch64-gcc-debug", + "inherits": "unixlike-gcc-debug", + "configurePreset": "aarch64-gcc-debug" + }, + { + "name": "aarch64-gcc-release", + "inherits": "unixlike-gcc-release", + "configurePreset": "aarch64-gcc-release" + }, + { + "name": "aarch64-clang-debug", + "inherits": "unixlike-clang-debug", + "configurePreset": "aarch64-clang-debug" + }, + { + "name": "aarch64-clang-release", + "inherits": "unixlike-clang-release", + "configurePreset": "aarch64-clang-release" + }, + { + "name": "aarch64-docs-only", + "inherits": "unixlike-docs-only", + "configurePreset": "unixlike-docs-only" } ], "testPresets": [ @@ -169,6 +226,26 @@ "description": "Enable output and stop on failure", "inherits": "test-common", "configurePreset": "unixlike-clang-release" + }, + { + "name": "aarch64-gcc-debug", + "inherits": "test-common", + "configurePreset": "aarch64-gcc-debug" + }, + { + "name": "aarch64-gcc-release", + "inherits": "test-common", + "configurePreset": "aarch64-gcc-release" + }, + { + "name": "aarch64-clang-debug", + "inherits": "test-common", + "configurePreset": "aarch64-clang-debug" + }, + { + "name": "aarch64-clang-release", + "inherits": "test-common", + "configurePreset": "aarch64-clang-release" } ] } diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 new file mode 100644 index 0000000..a5aa073 --- /dev/null +++ b/Dockerfile.aarch64 @@ -0,0 +1,29 @@ +# Dockerfile.aarch64 +FROM debian:bookworm + +RUN apt-get update -qq && apt-get install -qq --no-install-recommends \ + build-essential \ + cmake \ + ninja-build \ + g++-aarch64-linux-gnu \ + gcc-aarch64-linux-gnu \ + ccache \ + git \ + ca-certificates \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace + +COPY . /workspace/ + +ENV CPM_SOURCE_CACHE="/workspace/CPM" \ + CCACHE_DIR="/workspace/ccache" \ + CCACHE_SLOPPINESS="include_file_ctime,include_file_mtime,pch_defines,time_macros" \ + CCACHE_NAMESPACE="asmgrader-aarch64" \ + CCACHE_MAXSIZE="10Gi" \ + CCACHE_PCH_EXTSUM="true" + +VOLUME /workspace/CPM + +CMD ["/bin/bash"] diff --git a/Makefile b/Makefile index 8482c68..c84555b 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ CTEST_EXTRA_ARGS ?= CMAKE_CONFIGURE_EXTRA_ARGS ?= CMAKE_BUILD_EXTRA_ARGS ?= +CROSS_COMPILE ?= # gcc or clang COMPILER ?= gcc NUM_JOBS ?= $(shell echo $$(( $$(nproc) / 2 ))) @@ -23,12 +24,18 @@ ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BUILD_DIR := $(ROOT_DIR)/build SOURCE_DIR := $(ROOT_DIR) -DEBUG_PRESET := unixlike-$(COMPILER)-debug -RELEASE_PRESET := unixlike-$(COMPILER)-release -DOCS_PRESET := unixlike-docs-only SRC_ENV := if [ -f "$(ROOT_DIR)/.env" ]; then export $$(cat "$(ROOT_DIR)/.env" | xargs); echo "Set enviornment variables:"; sed -E 's/=.*//' "$(ROOT_DIR)/.env"; echo; fi +ifdef CROSS_COMPILE + DEBUG_PRESET := aarch64-$(COMPILER)-debug + RELEASE_PRESET := aarch64-$(COMPILER)-release + DOCS_PRESET := aarch64-docs-only +else + DEBUG_PRESET := unixlike-$(COMPILER)-debug + RELEASE_PRESET := unixlike-$(COMPILER)-release + DOCS_PRESET := unixlike-docs-only +endif default: help diff --git a/cmake/aarch64-toolchain.cmake b/cmake/aarch64-toolchain.cmake new file mode 100644 index 0000000..c7e2139 --- /dev/null +++ b/cmake/aarch64-toolchain.cmake @@ -0,0 +1,15 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR aarch64) + +set(CROSS_COMPILE aarch64-linux-gnu) + +set(CMAKE_C_COMPILER ${CROSS_COMPILE}-gcc) +set(CMAKE_CXX_COMPILER ${CROSS_COMPILE}-g++) +set(CMAKE_FIND_ROOT_PATH /usr/${CROSS_COMPILE}) +# TODO: Fedora only? +list(APPEND CMAKE_FIND_ROOT_PATH /usr/lib/gcc/${CROSS_COMPILE}-linux-gnu) + +# Search target libs/includes only in the sysroot +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) From f3844edb18dbcd69126454b4a00b5bf9f10de460 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Mon, 20 Oct 2025 16:11:24 -0700 Subject: [PATCH 02/41] hotfix: prevent race condition with traced subproc timeouts (closes #34) Also add trace logs to linux syscall wrappers --- include/asmgrader/common/linux.hpp | 42 ++++++++++++++++++++++++------ src/subprocess/tracer.cpp | 5 ++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/include/asmgrader/common/linux.hpp b/include/asmgrader/common/linux.hpp index d44def3..5937ad9 100644 --- a/include/asmgrader/common/linux.hpp +++ b/include/asmgrader/common/linux.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -51,10 +52,12 @@ inline Expected write(int fd, std::string_view data) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("write failed: '{}'", err); + LOG_DEBUG("write(fd={}, data={:?}, size={}) failed: '{}'", fd, data, data.size(), err); return err; } + LOG_TRACE("write(fd={}, data={:?}, size={}) = {}", fd, data, data.size(), res); + return res; } @@ -68,13 +71,16 @@ inline Expected read(int fd, size_t count) { // NOLINT if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("read failed: '{}'", err); + LOG_DEBUG("read(fd={}, count={}) failed: '{}'", fd, count, err); + return err; } DEBUG_ASSERT(res >= 0, "read result is negative and != -1"); buffer.resize(static_cast(res)); + LOG_TRACE("read(fd={}, count={}) = {}; buffer={:?}", fd, count, res, buffer); + return buffer; } @@ -101,10 +107,13 @@ inline Expected<> kill(pid_t pid, int sig) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("kill failed: '{}'", err); + LOG_DEBUG("kill(pid={}, sig={}) failed: '{}'", pid, sig, err); + return err; } + LOG_DEBUG("kill(pid={}, sig={}) = {}", pid, sig, res); + return {}; } @@ -118,10 +127,13 @@ inline Expected<> access(gsl::czstring path, int mode) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("access failed: '{}'", err); + LOG_DEBUG("access(path={:?}, mode={}) failed '{}'", path, mode, err); + return err; } + LOG_TRACE("access(path={:?}, mode={}) = {}", path, mode, res); + return {}; } @@ -189,10 +201,12 @@ inline Expected open(const std::string& pathname, int flags, mode_t mode = if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("open failed: '{}'", err); + LOG_DEBUG("open(pathname={:?}, flags={}, mode={}) failed: '{}'", pathname, flags, mode, err); return err; } + LOG_TRACE("open(pathname={:?}, flags={}, mode={}) = {}", pathname, flags, mode, res); + return res; } @@ -281,11 +295,14 @@ inline Expected waitid(idtype_t idtype, id_t id, int options = WSTOPP if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("waitid failed: '{}'", err); + LOG_DEBUG("waitid(idtype={}, id={}, options={}) failed: '{}'", fmt::underlying(idtype), id, options, err); return err; } + LOG_TRACE("waitid(idtype={}, id={}, options={}) = {}; info={}", fmt::underlying(idtype), id, options, res, + format_or_unknown(info)); + return info; } @@ -297,11 +314,13 @@ inline Expected<> raise(int sig) { if (res == -1) { auto err = std::error_code(errno, std::system_category()); - LOG_DEBUG("raise failed: '{}'", err); + LOG_DEBUG("raise({}) failed: '{}'", sig, err); return err; } + LOG_TRACE("raise(sig={}) = {}", sig, res); + return {}; } @@ -324,11 +343,13 @@ inline Expected pipe2(int flags = 0) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("pipe failed: '{}'", err); + LOG_DEBUG("pipe(..., flags={}) failed: '{}'", flags, err); return err; } + LOG_TRACE("pipe(..., flags={}) = {}; pipes=(r={}, w={})", flags, res, pipe.read_fd, pipe.write_fd); + return pipe; } @@ -363,6 +384,9 @@ inline Expected ptrace(int request, pid_t pid = 0, AddrT addr = NULL, Data return err; } + LOG_TRACE("ptrace(req={}, pid={}, addr={}, data={}) = {}", request, pid, format_or_unknown(addr), + format_or_unknown(data), res); + return res; } @@ -380,6 +404,8 @@ inline Expected stat(const std::string& pathname) { return err; } + LOG_TRACE("stat(pathname={:?}) = {}; data={}", pathname, res, format_or_unknown(data_result)); + return data_result; } diff --git a/src/subprocess/tracer.cpp b/src/subprocess/tracer.cpp index 1d71735..b10d83e 100644 --- a/src/subprocess/tracer.cpp +++ b/src/subprocess/tracer.cpp @@ -173,6 +173,8 @@ void Tracer::assert_invariants() const { expected_ppid, info.ppid); throw std::runtime_error("contract violation"); } + + LOG_TRACE("Invariant assertions passed for pid={}", pid_); } SyscallRecord Tracer::get_syscall_entry_info(struct ptrace_syscall_info* entry) const { @@ -347,6 +349,9 @@ Result Tracer::run_until(const std::function& pr // stop process to keep in tracable state TRYE(linux::kill(pid_, SIGSTOP), SyscallFailure); + // wait until process has confirmed stop + TRYE(linux::waitid(P_PID, static_cast(pid_), WSTOPPED), SyscallFailure); + return ErrorKind::TimedOut; } From 143becc77280eddbc263a003daa8795177383588 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Mon, 20 Oct 2025 16:13:12 -0700 Subject: [PATCH 03/41] chore: major workflow updates and cross compilation support (closes #63) Deprecate Makefile with more robust scripts Dynamically generate CMakePresets.json --- .github/workflows/ci.yml | 14 +- .gitignore | 4 + CMakePresets.json | 688 ++++++++++++++++---- Dockerfile.aarch64 => Dockerfile | 7 +- Makefile | 126 ---- hooks/pre-push | 4 +- scripts/build | 165 +++++ scripts/debug | 408 ++++++++++++ scripts/debug.sh | 38 -- scripts/generate-presets | 229 +++++++ scripts/{install-hooks.sh => install-hooks} | 0 scripts/lint | 166 +++++ scripts/lint.sh | 63 -- scripts/stats | 10 + scripts/utest | 108 +++ 15 files changed, 1666 insertions(+), 364 deletions(-) rename Dockerfile.aarch64 => Dockerfile (90%) delete mode 100644 Makefile create mode 100755 scripts/build create mode 100755 scripts/debug delete mode 100755 scripts/debug.sh create mode 100755 scripts/generate-presets rename scripts/{install-hooks.sh => install-hooks} (100%) create mode 100755 scripts/lint delete mode 100755 scripts/lint.sh create mode 100755 scripts/stats create mode 100755 scripts/utest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5de6adf..90acf29 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,18 +51,22 @@ jobs: run: | echo "CPM_SOURCE_CACHE=./build/CPM" > .env - - name: Configure + - name: Strip compiler version run: | - make configure-${{ matrix.build_type }} + base_compiler="${{ matrix.compiler }}" + base_compiler="${base_compiler%%-*}" + base_compiler="${base_compiler/llvm/clang}" + echo "Base compiler: $base_compiler" + echo "BASE_COMPILER=$base_compiler" >> "$GITHUB_ENV" - # We want ccache to be capable of caching PCH + # We want ccache to be capable of caching with a PCH - name: Set ccache Config run: | ccache --set-config 'sloppiness=pch_defines,time_macros,include_file_mtime,include_file_ctime' - name: Build run: | - make build-${{ matrix.build_type }} CMAKE_CONFIGURE_EXTRA_ARGS="-DASMGRADER_ENABLE_TRACE=ON" + ./scripts/build ${{ matrix.build_type }} $BASE_COMPILER ${{ matrix.generator }} - name: ccache Stats run: | @@ -70,7 +74,7 @@ jobs: - name: Test run: | - LOG_LEVEL=trace make test-${{ matrix.build_type }} CTEST_EXTRA_ARGS="--output-junit \"$PWD/reports/junit-ctest.xml\"" + LOG_LEVEL=trace ./scripts/utest --no-build ${{ matrix.build_type }} $BASE_COMPILER ${{ matrix.generator }} - uses: actions/upload-artifact@v4 # upload test results if: ${{ !cancelled() }} # run this step even if previous step failed diff --git a/.gitignore b/.gitignore index f53ef56..320da00 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ build/ *.a *.exe +# Cached files +.cache/ + ##### From Python.gitignore # Byte-compiled / optimized / DLL files __pycache__/ @@ -142,6 +145,7 @@ celerybeat.pid *.sage.py # Environments +.envrc .env .venv env/ diff --git a/CMakePresets.json b/CMakePresets.json index 61565e2..e4e9f6d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,24 +1,20 @@ { - "version": 3, + "version": 6, "cmakeMinimumRequired": { "major": 3, - "minor": 21, + "minor": 25, "patch": 0 }, "configurePresets": [ { - "name": "conf-common", + "name": "conf-base", "description": "General settings that apply to all configurations", "hidden": true, - "generator": "Ninja", "binaryDir": "${sourceDir}/build/${presetName}", - "installDir": "${sourceDir}/install/${presetName}", - "warnings": { - "uninitialized": true - } + "installDir": "${sourceDir}/install/${presetName}" }, { - "name": "conf-variables", + "name": "conf-variables-base", "hidden": true, "description": "General variable options that apply to most configurations", "cacheVariables": { @@ -28,10 +24,10 @@ } }, { - "name": "conf-unixlike-common", - "description": "Unix-like OS settings toolchains", + "name": "conf-unixlike-base", + "description": "Unix-like OS settings", "hidden": true, - "inherits": "conf-common", + "inherits": "conf-base", "condition": { "type": "inList", "string": "${hostSystemName}", @@ -42,22 +38,21 @@ } }, { - "name": "aarch64-overlay", + "name": "conf-aarch64cross-base", "hidden": true, - "description": "Overrides for AArch64 cross-compilation", + "description": "Cross-compilation to AArch64 settings", "cacheVariables": { - "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/aarch64-toolchain.cmake", - "ASMGRADER_ENABLE_SANITIZER_ADDRESS": "OFF", - "ASMGRADER_ENABLE_SANITIZER_LEAK": "OFF", - "ASMGRADER_ENABLE_SANITIZER_THREAD": "OFF", - "ASMGRADER_ENABLE_SANITIZER_UNDEFINED": "OFF" + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/aarch64-toolchain.cmake" } }, { - "name": "unixlike-gcc-debug", - "displayName": "gcc Debug", - "description": "Target Unix-like OS with the gcc compiler, debug build type", - "inherits": ["conf-unixlike-common", "conf-variables"], + "name": "unixlike-gcc-ninja-debug", + "displayName": "Configure: target Unix-like OS using gcc and ninja in debug mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Ninja", "cacheVariables": { "CMAKE_C_COMPILER": "gcc", "CMAKE_CXX_COMPILER": "g++", @@ -65,10 +60,28 @@ } }, { - "name": "unixlike-gcc-release", - "displayName": "gcc Release", - "description": "Target Unix-like OS with the gcc compiler, release build type", - "inherits": ["conf-unixlike-common", "conf-variables"], + "name": "crossaarch64-unixlike-gcc-ninja-debug", + "displayName": "Configure: cross-compile to AArch64, target Unix-like OS using gcc and ninja in debug mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base", + "conf-aarch64cross-base" + ], + "generator": "Ninja", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unixlike-gcc-ninja-release", + "displayName": "Configure: target Unix-like OS using gcc and ninja in release mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Ninja", "cacheVariables": { "CMAKE_C_COMPILER": "gcc", "CMAKE_CXX_COMPILER": "g++", @@ -76,52 +89,138 @@ } }, { - "name": "unixlike-clang-debug", - "displayName": "clang Debug", - "description": "Target Unix-like OS with the clang compiler, debug build type", - "inherits": ["conf-unixlike-common", "conf-variables"], + "name": "crossaarch64-unixlike-gcc-ninja-release", + "displayName": "Configure: cross-compile to AArch64, target Unix-like OS using gcc and ninja in release mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base", + "conf-aarch64cross-base" + ], + "generator": "Ninja", "cacheVariables": { - "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "unixlike-gcc-make-debug", + "displayName": "Configure: target Unix-like OS using gcc and make in debug mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", "CMAKE_BUILD_TYPE": "Debug" } }, { - "name": "unixlike-clang-release", - "displayName": "clang Release", - "description": "Target Unix-like OS with the clang compiler, release build type", - "inherits": ["conf-unixlike-common", "conf-variables"], + "name": "crossaarch64-unixlike-gcc-make-debug", + "displayName": "Configure: cross-compile to AArch64, target Unix-like OS using gcc and make in debug mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base", + "conf-aarch64cross-base" + ], + "generator": "Unix Makefiles", "cacheVariables": { - "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unixlike-gcc-make-release", + "displayName": "Configure: target Unix-like OS using gcc and make in release mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", "CMAKE_BUILD_TYPE": "RelWithDebInfo" } }, { - "name": "aarch64-clang-debug", - "inherits": ["unixlike-clang-debug", "aarch64-overlay"], - "displayName": "clang Debug (AArch64)" + "name": "crossaarch64-unixlike-gcc-make-release", + "displayName": "Configure: cross-compile to AArch64, target Unix-like OS using gcc and make in release mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base", + "conf-aarch64cross-base" + ], + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } }, { - "name": "aarch64-clang-release", - "inherits": ["unixlike-clang-release", "aarch64-overlay"], - "displayName": "clang Release (AArch64)" + "name": "unixlike-clang-ninja-debug", + "displayName": "Configure: target Unix-like OS using clang and ninja in debug mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Ninja", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_BUILD_TYPE": "Debug" + } }, { - "name": "aarch64-gcc-debug", - "inherits": ["unixlike-gcc-debug", "aarch64-overlay"], - "displayName": "gcc Debug (AArch64)" + "name": "unixlike-clang-ninja-release", + "displayName": "Configure: target Unix-like OS using clang and ninja in release mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Ninja", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } }, { - "name": "aarch64-gcc-release", - "inherits": ["unixlike-gcc-release", "aarch64-overlay"], - "displayName": "gcc Release (AArch64)" + "name": "unixlike-clang-make-debug", + "displayName": "Configure: target Unix-like OS using clang and make in debug mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "unixlike-clang-make-release", + "displayName": "Configure: target Unix-like OS using clang and make in release mode", + "inherits": [ + "conf-unixlike-base", + "conf-variables-base" + ], + "generator": "Unix Makefiles", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } }, { "name": "unixlike-docs-only", - "displayName": "docs only", - "description": "Target Unix-like OS, only configure for docs and nothing else", - "inherits": "conf-unixlike-common", + "displayName": "Configure: target Unix-like OS, for only docs", + "inherits": "conf-unixlike-base", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "ASMGRADER_BUILD_DOCS": "ON", @@ -131,121 +230,460 @@ ], "buildPresets": [ { - "name": "unixlike-gcc-debug", - "displayName": "gcc Debug", - "description": "Target Unix-like OS with the gcc compiler, debug build type", - "configurePreset": "unixlike-gcc-debug" + "name": "unixlike-gcc-ninja-debug", + "displayName": "Build: target Unix-like OS using gcc and ninja in debug mode", + "configurePreset": "unixlike-gcc-ninja-debug" }, { - "name": "unixlike-gcc-release", - "displayName": "gcc Release", - "description": "Target Unix-like OS with the gcc compiler, release build type", - "configurePreset": "unixlike-gcc-release" + "name": "crossaarch64-unixlike-gcc-ninja-debug", + "displayName": "Build: cross-compile to AArch64, target Unix-like OS using gcc and ninja in debug mode", + "configurePreset": "crossaarch64-unixlike-gcc-ninja-debug" }, { - "name": "unixlike-clang-debug", - "displayName": "clang Debug", - "description": "Target Unix-like OS with the clang compiler, debug build type", - "configurePreset": "unixlike-clang-debug" + "name": "unixlike-gcc-ninja-release", + "displayName": "Build: target Unix-like OS using gcc and ninja in release mode", + "configurePreset": "unixlike-gcc-ninja-release" }, { - "name": "unixlike-clang-release", - "displayName": "clang Release", - "description": "Target Unix-like OS with the clang compiler, release build type", - "configurePreset": "unixlike-clang-release" + "name": "crossaarch64-unixlike-gcc-ninja-release", + "displayName": "Build: cross-compile to AArch64, target Unix-like OS using gcc and ninja in release mode", + "configurePreset": "crossaarch64-unixlike-gcc-ninja-release" }, { - "name": "unixlike-docs-only", - "displayName": "docs only", - "description": "Target Unix-like OS, only configure for docs and nothing else", - "configurePreset": "unixlike-docs-only", - "targets": "doxygen-docs" + "name": "unixlike-gcc-make-debug", + "displayName": "Build: target Unix-like OS using gcc and make in debug mode", + "configurePreset": "unixlike-gcc-make-debug" }, { - "name": "aarch64-gcc-debug", - "inherits": "unixlike-gcc-debug", - "configurePreset": "aarch64-gcc-debug" + "name": "crossaarch64-unixlike-gcc-make-debug", + "displayName": "Build: cross-compile to AArch64, target Unix-like OS using gcc and make in debug mode", + "configurePreset": "crossaarch64-unixlike-gcc-make-debug" }, { - "name": "aarch64-gcc-release", - "inherits": "unixlike-gcc-release", - "configurePreset": "aarch64-gcc-release" + "name": "unixlike-gcc-make-release", + "displayName": "Build: target Unix-like OS using gcc and make in release mode", + "configurePreset": "unixlike-gcc-make-release" }, { - "name": "aarch64-clang-debug", - "inherits": "unixlike-clang-debug", - "configurePreset": "aarch64-clang-debug" + "name": "crossaarch64-unixlike-gcc-make-release", + "displayName": "Build: cross-compile to AArch64, target Unix-like OS using gcc and make in release mode", + "configurePreset": "crossaarch64-unixlike-gcc-make-release" }, { - "name": "aarch64-clang-release", - "inherits": "unixlike-clang-release", - "configurePreset": "aarch64-clang-release" + "name": "unixlike-clang-ninja-debug", + "displayName": "Build: target Unix-like OS using clang and ninja in debug mode", + "configurePreset": "unixlike-clang-ninja-debug" }, { - "name": "aarch64-docs-only", - "inherits": "unixlike-docs-only", - "configurePreset": "unixlike-docs-only" + "name": "unixlike-clang-ninja-release", + "displayName": "Build: target Unix-like OS using clang and ninja in release mode", + "configurePreset": "unixlike-clang-ninja-release" + }, + { + "name": "unixlike-clang-make-debug", + "displayName": "Build: target Unix-like OS using clang and make in debug mode", + "configurePreset": "unixlike-clang-make-debug" + }, + { + "name": "unixlike-clang-make-release", + "displayName": "Build: target Unix-like OS using clang and make in release mode", + "configurePreset": "unixlike-clang-make-release" + }, + { + "name": "unixlike-docs-only", + "displayName": "Build: target Unix-like OS, for only docs", + "configurePreset": "unixlike-docs-only", + "targets": "doxygen-docs" } ], "testPresets": [ { - "name": "test-common", + "name": "test-base", "description": "Test CMake settings that apply to all configurations", "hidden": true, "output": { - "outputOnFailure": true + "outputOnFailure": true, + "outputJUnitFile": "${sourceDir}/reports/junit-ctest.xml" }, "execution": { "noTestsAction": "error" } }, { - "name": "unixlike-gcc-debug", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-gcc-debug" + "name": "unixlike-gcc-ninja-debug", + "displayName": "Test: target Unix-like OS using gcc and ninja in debug mode", + "inherits": "test-base", + "configurePreset": "unixlike-gcc-ninja-debug" + }, + { + "name": "unixlike-gcc-ninja-release", + "displayName": "Test: target Unix-like OS using gcc and ninja in release mode", + "inherits": "test-base", + "configurePreset": "unixlike-gcc-ninja-release" + }, + { + "name": "unixlike-gcc-make-debug", + "displayName": "Test: target Unix-like OS using gcc and make in debug mode", + "inherits": "test-base", + "configurePreset": "unixlike-gcc-make-debug" + }, + { + "name": "unixlike-gcc-make-release", + "displayName": "Test: target Unix-like OS using gcc and make in release mode", + "inherits": "test-base", + "configurePreset": "unixlike-gcc-make-release" }, { - "name": "unixlike-gcc-release", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-gcc-release" + "name": "unixlike-clang-ninja-debug", + "displayName": "Test: target Unix-like OS using clang and ninja in debug mode", + "inherits": "test-base", + "configurePreset": "unixlike-clang-ninja-debug" + }, + { + "name": "unixlike-clang-ninja-release", + "displayName": "Test: target Unix-like OS using clang and ninja in release mode", + "inherits": "test-base", + "configurePreset": "unixlike-clang-ninja-release" + }, + { + "name": "unixlike-clang-make-debug", + "displayName": "Test: target Unix-like OS using clang and make in debug mode", + "inherits": "test-base", + "configurePreset": "unixlike-clang-make-debug" + }, + { + "name": "unixlike-clang-make-release", + "displayName": "Test: target Unix-like OS using clang and make in release mode", + "inherits": "test-base", + "configurePreset": "unixlike-clang-make-release" + } + ], + "workflowPresets": [ + { + "name": "unixlike-gcc-ninja-debug", + "displayName": "Workflow: target Unix-like OS using gcc and ninja in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-ninja-debug" + }, + { + "type": "build", + "name": "unixlike-gcc-ninja-debug" + } + ] }, { - "name": "unixlike-clang-debug", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-clang-debug" + "name": "crossaarch64-unixlike-gcc-ninja-debug", + "displayName": "Workflow: cross-compile to AArch64, target Unix-like OS using gcc and ninja in debug mode", + "steps": [ + { + "type": "configure", + "name": "crossaarch64-unixlike-gcc-ninja-debug" + }, + { + "type": "build", + "name": "crossaarch64-unixlike-gcc-ninja-debug" + } + ] }, { - "name": "unixlike-clang-release", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-clang-release" + "name": "unixlike-gcc-ninja-release", + "displayName": "Workflow: target Unix-like OS using gcc and ninja in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-ninja-release" + }, + { + "type": "build", + "name": "unixlike-gcc-ninja-release" + } + ] }, { - "name": "aarch64-gcc-debug", - "inherits": "test-common", - "configurePreset": "aarch64-gcc-debug" + "name": "crossaarch64-unixlike-gcc-ninja-release", + "displayName": "Workflow: cross-compile to AArch64, target Unix-like OS using gcc and ninja in release mode", + "steps": [ + { + "type": "configure", + "name": "crossaarch64-unixlike-gcc-ninja-release" + }, + { + "type": "build", + "name": "crossaarch64-unixlike-gcc-ninja-release" + } + ] }, { - "name": "aarch64-gcc-release", - "inherits": "test-common", - "configurePreset": "aarch64-gcc-release" + "name": "unixlike-gcc-make-debug", + "displayName": "Workflow: target Unix-like OS using gcc and make in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-make-debug" + }, + { + "type": "build", + "name": "unixlike-gcc-make-debug" + } + ] }, { - "name": "aarch64-clang-debug", - "inherits": "test-common", - "configurePreset": "aarch64-clang-debug" + "name": "crossaarch64-unixlike-gcc-make-debug", + "displayName": "Workflow: cross-compile to AArch64, target Unix-like OS using gcc and make in debug mode", + "steps": [ + { + "type": "configure", + "name": "crossaarch64-unixlike-gcc-make-debug" + }, + { + "type": "build", + "name": "crossaarch64-unixlike-gcc-make-debug" + } + ] }, { - "name": "aarch64-clang-release", - "inherits": "test-common", - "configurePreset": "aarch64-clang-release" + "name": "unixlike-gcc-make-release", + "displayName": "Workflow: target Unix-like OS using gcc and make in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-make-release" + }, + { + "type": "build", + "name": "unixlike-gcc-make-release" + } + ] + }, + { + "name": "crossaarch64-unixlike-gcc-make-release", + "displayName": "Workflow: cross-compile to AArch64, target Unix-like OS using gcc and make in release mode", + "steps": [ + { + "type": "configure", + "name": "crossaarch64-unixlike-gcc-make-release" + }, + { + "type": "build", + "name": "crossaarch64-unixlike-gcc-make-release" + } + ] + }, + { + "name": "unixlike-clang-ninja-debug", + "displayName": "Workflow: target Unix-like OS using clang and ninja in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-ninja-debug" + }, + { + "type": "build", + "name": "unixlike-clang-ninja-debug" + } + ] + }, + { + "name": "unixlike-clang-ninja-release", + "displayName": "Workflow: target Unix-like OS using clang and ninja in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-ninja-release" + }, + { + "type": "build", + "name": "unixlike-clang-ninja-release" + } + ] + }, + { + "name": "unixlike-clang-make-debug", + "displayName": "Workflow: target Unix-like OS using clang and make in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-make-debug" + }, + { + "type": "build", + "name": "unixlike-clang-make-debug" + } + ] + }, + { + "name": "unixlike-clang-make-release", + "displayName": "Workflow: target Unix-like OS using clang and make in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-make-release" + }, + { + "type": "build", + "name": "unixlike-clang-make-release" + } + ] + }, + { + "name": "unixlike-gcc-ninja-debug-test", + "displayName": "Workflow (with tests): target Unix-like OS using gcc and ninja in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-ninja-debug" + }, + { + "type": "build", + "name": "unixlike-gcc-ninja-debug" + }, + { + "type": "test", + "name": "unixlike-gcc-ninja-debug" + } + ] + }, + { + "name": "unixlike-gcc-ninja-release-test", + "displayName": "Workflow (with tests): target Unix-like OS using gcc and ninja in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-ninja-release" + }, + { + "type": "build", + "name": "unixlike-gcc-ninja-release" + }, + { + "type": "test", + "name": "unixlike-gcc-ninja-release" + } + ] + }, + { + "name": "unixlike-gcc-make-debug-test", + "displayName": "Workflow (with tests): target Unix-like OS using gcc and make in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-make-debug" + }, + { + "type": "build", + "name": "unixlike-gcc-make-debug" + }, + { + "type": "test", + "name": "unixlike-gcc-make-debug" + } + ] + }, + { + "name": "unixlike-gcc-make-release-test", + "displayName": "Workflow (with tests): target Unix-like OS using gcc and make in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-gcc-make-release" + }, + { + "type": "build", + "name": "unixlike-gcc-make-release" + }, + { + "type": "test", + "name": "unixlike-gcc-make-release" + } + ] + }, + { + "name": "unixlike-clang-ninja-debug-test", + "displayName": "Workflow (with tests): target Unix-like OS using clang and ninja in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-ninja-debug" + }, + { + "type": "build", + "name": "unixlike-clang-ninja-debug" + }, + { + "type": "test", + "name": "unixlike-clang-ninja-debug" + } + ] + }, + { + "name": "unixlike-clang-ninja-release-test", + "displayName": "Workflow (with tests): target Unix-like OS using clang and ninja in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-ninja-release" + }, + { + "type": "build", + "name": "unixlike-clang-ninja-release" + }, + { + "type": "test", + "name": "unixlike-clang-ninja-release" + } + ] + }, + { + "name": "unixlike-clang-make-debug-test", + "displayName": "Workflow (with tests): target Unix-like OS using clang and make in debug mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-make-debug" + }, + { + "type": "build", + "name": "unixlike-clang-make-debug" + }, + { + "type": "test", + "name": "unixlike-clang-make-debug" + } + ] + }, + { + "name": "unixlike-clang-make-release-test", + "displayName": "Workflow (with tests): target Unix-like OS using clang and make in release mode", + "steps": [ + { + "type": "configure", + "name": "unixlike-clang-make-release" + }, + { + "type": "build", + "name": "unixlike-clang-make-release" + }, + { + "type": "test", + "name": "unixlike-clang-make-release" + } + ] + }, + { + "name": "unixlike-docs-only", + "displayName": "Workflow: target Unix-like OS, for only docs", + "steps": [ + { + "type": "configure", + "name": "unixlike-docs-only" + }, + { + "type": "build", + "name": "unixlike-docs-only" + } + ] } ] -} +} \ No newline at end of file diff --git a/Dockerfile.aarch64 b/Dockerfile similarity index 90% rename from Dockerfile.aarch64 rename to Dockerfile index a5aa073..e5dd053 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile @@ -1,10 +1,11 @@ -# Dockerfile.aarch64 FROM debian:bookworm RUN apt-get update -qq && apt-get install -qq --no-install-recommends \ build-essential \ cmake \ ninja-build \ + g++ \ + gcc \ g++-aarch64-linux-gnu \ gcc-aarch64-linux-gnu \ ccache \ @@ -15,8 +16,6 @@ RUN apt-get update -qq && apt-get install -qq --no-install-recommends \ WORKDIR /workspace -COPY . /workspace/ - ENV CPM_SOURCE_CACHE="/workspace/CPM" \ CCACHE_DIR="/workspace/ccache" \ CCACHE_SLOPPINESS="include_file_ctime,include_file_mtime,pch_defines,time_macros" \ @@ -24,6 +23,4 @@ ENV CPM_SOURCE_CACHE="/workspace/CPM" \ CCACHE_MAXSIZE="10Gi" \ CCACHE_PCH_EXTSUM="true" -VOLUME /workspace/CPM - CMD ["/bin/bash"] diff --git a/Makefile b/Makefile deleted file mode 100644 index c84555b..0000000 --- a/Makefile +++ /dev/null @@ -1,126 +0,0 @@ -# !!DISCLAIMER!! -# This was adapted from the following source -# jeremy-rifkin/cpptrace -# https://github.com/jeremy-rifkin/cpptrace/tree/b724d1328ced0d71315c28f6ec6e11d0652062bc -# Courtesy of jeremy-rifkin -# Who themselves adapted their's from the following source -# compiler-explorer/compiler-explorer -# https://github.com/compiler-explorer/compiler-explorer/blob/main/Makefile -# Courtesy of Matt Godbolt, et al. - -# https://gist.github.com/bbl/5429c4a0a498c5ef91c10201e1b651c2 - -# Intended user-args -CTEST_EXTRA_ARGS ?= -CMAKE_CONFIGURE_EXTRA_ARGS ?= -CMAKE_BUILD_EXTRA_ARGS ?= -CROSS_COMPILE ?= -# gcc or clang -COMPILER ?= gcc -NUM_JOBS ?= $(shell echo $$(( $$(nproc) / 2 ))) - -ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) - -BUILD_DIR := $(ROOT_DIR)/build -SOURCE_DIR := $(ROOT_DIR) - - -SRC_ENV := if [ -f "$(ROOT_DIR)/.env" ]; then export $$(cat "$(ROOT_DIR)/.env" | xargs); echo "Set enviornment variables:"; sed -E 's/=.*//' "$(ROOT_DIR)/.env"; echo; fi - -ifdef CROSS_COMPILE - DEBUG_PRESET := aarch64-$(COMPILER)-debug - RELEASE_PRESET := aarch64-$(COMPILER)-release - DOCS_PRESET := aarch64-docs-only -else - DEBUG_PRESET := unixlike-$(COMPILER)-debug - RELEASE_PRESET := unixlike-$(COMPILER)-release - DOCS_PRESET := unixlike-docs-only -endif - -default: help - -##### --- snipped from gh:compiler-explorer/compiler-explorer/blob/main/Makefile -help: # with thanks to Ben Rady - @printf '\033[33mOPTIONS:\033[0m\n' - @grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' -#### --- END snip - -# Each preset will create a subdirectory within build -# CMake is smart when using --preset --build and will automatically re-configure -# if any cmake sources change, so we don't have to handle that manually here. - -$(BUILD_DIR)/$(DEBUG_PRESET): - @$(SRC_ENV) && cmake --preset $(DEBUG_PRESET) $(CMAKE_CONFIGURE_EXTRA_ARGS) - -$(BUILD_DIR)/$(RELEASE_PRESET): - @$(SRC_ENV) && cmake --preset $(RELEASE_PRESET) $(CMAKE_CONFIGURE_EXTRA_ARGS) - -$(BUILD_DIR)/$(DOCS_PRESET): - @$(SRC_ENV) && cmake --preset $(DOCS_PRESET) $(CMAKE_CONFIGURE_EXTRA_ARGS) - -.PHONY: configure-debug -configure-debug: $(BUILD_DIR)/$(DEBUG_PRESET) - -.PHONY: configure-release -configure-release: $(BUILD_DIR)/$(RELEASE_PRESET) - -.PHONY: configure-docs -configure-docs: $(BUILD_DIR)/$(DOCS_PRESET) - - -.PHONY: build -build: build-debug ## alias for build-debug - -.PHONY: build-debug -build-debug: configure-debug ## build in debug mode - @$(SRC_ENV) && cmake --build --preset $(DEBUG_PRESET) $(CMAKE_BUILD_EXTRA_ARGS) -j $(NUM_JOBS) - -.PHONY: build-release -build-release: configure-release ## build in release mode (with debug info) - @$(SRC_ENV) && cmake --build --preset $(RELEASE_PRESET) $(CMAKE_BUILD_EXTRA_ARGS) -j $(NUM_JOBS) - -.PHONY: build-docs -build-docs: configure-docs ## build in release mode (with debug info) - @$(SRC_ENV) && cmake --build --preset $(DOCS_PRESET) $(CMAKE_BUILD_EXTRA_ARGS) -j $(NUM_JOBS) - -.PHONY: build-tests-debug -build-tests-debug: configure-debug - @$(SRC_ENV) && cmake --build --preset $(DEBUG_PRESET) --target asmgrader_tests $(CMAKE_BUILD_EXTRA_ARGS) -j $(NUM_JOBS) - -.PHONY: build-tests-release -build-tests-release: configure-release - @$(SRC_ENV) && cmake --build --preset $(RELEASE_PRESET) --target asmgrader_tests $(CMAKE_BUILD_EXTRA_ARGS) -j $(NUM_JOBS) - -.PHONY: test -test: test-debug ## alias for test-debug - -.PHONY: test-debug -test-debug: build-tests-debug ## run tests in debug mode - @$(SRC_ENV) && ctest --preset $(DEBUG_PRESET) --progress --output-on-failure --no-tests=error $(CTEST_EXTRA_ARGS) -j $(NUM_JOBS) - -.PHONY: test-release -test-release: build-tests-release ## run tests in release mode - @$(SRC_ENV) && ctest --preset $(RELEASE_PRESET) --progress --output-on-failure --no-tests=error $(CTEST_EXTRA_ARGS) -j $(NUM_JOBS) - - -.PHONY: clean -clean: ## remove build objects, libraries, executables, and test reports - rm -rf reports/* - -cmake --build --preset $(DEBUG_PRESET) --target clean - -cmake --build --preset $(RELEASE_PRESET) --target clean - -cmake --build --preset $(DOCS_PRESET) --target clean - -.PHONY: deep-clean -deep-clean: ## remove all build files and configuration - rm -rf $(BUILD_DIR)/ reports/* - -.PHONY: list-opts -list-opts: ## list available CMake options - @if ! cmake -S . -B $(shell find build/ -maxdepth 1 -mindepth 1 | head -n1) -LAH -N 2>/dev/null \ - | grep ASMGRADER_ --before-context 1 --group-separator='' \ - | sed '/\/\// { s/^//; s/$$//; }' \ - | sed '/^\w/ { s/^//; s/:/:/; }' \ - ; then \ - printf '\033[31mError: CMake command failed. Is the project configured?\033[0m\n'; \ - exit 1; \ - fi diff --git a/hooks/pre-push b/hooks/pre-push index 54cb87a..17a6c69 100755 --- a/hooks/pre-push +++ b/hooks/pre-push @@ -7,7 +7,7 @@ echo "[pre-push] Checking that project builds and that all tests pass" REPO_ROOT=$(git rev-parse --show-toplevel) ->/dev/null 2>&1 make -f "$REPO_ROOT/Makefile" build || (echo "[pre-push] Build failed!" && exit 1) ->/dev/null 2>&1 make -f "$REPO_ROOT/Makefile" test || (echo "[pre-push] Test(s) failed!" && exit 1) +"$REPO_ROOT/scripts/build" || (echo "[pre-push] Build failed!" && exit 1) +"$REPO_ROOT/scripts/utest" --no-build || (echo "[pre-push] Test(s) failed!" && exit 1) echo "[pre-push] Build and tests passed successfully" diff --git a/scripts/build b/scripts/build new file mode 100755 index 0000000..02e5a6d --- /dev/null +++ b/scripts/build @@ -0,0 +1,165 @@ +#!/usr/bin/env bash + +# +# Util script to simplify build process +# + +# Exit on error; exit status of pipeline is nonzero if any command fails +set -eo pipefail + +usage() { + cat <&2 + +expand() { + eval "IFS='|'; echo \"\${$1[*]}\"" +} + +build_type="debug" +compiler="gcc" +generator="ninja" +cross_compile= +fresh= +use_docker= +jobs=${JOBS-$(nproc)} + +deep_clean= + +cmake_args=() + +# Process all args +while (($#)); do + opt=${1,,} # transform to all lowercase + case "$opt" in + -n | --dry-run) + dry_run=yes + ;; + -j | --jobs) + if ! [[ $2 =~ ^[0-9]+$ ]]; then + >&2 echo 'Specified JOBS arg is invalid' + exit 1 + fi + jobs="$2" + shift + ;; + --cross) + if [[ $(uname -m) == 'aarch64' ]]; then + >&2 echo 'Should not be cross-compiling on AArch64-based system' + exit 1 + fi + cross_compile="crossaarch64-" + ;; + --fresh) + fresh="--fresh" + ;; + --deep-clean) + deep_clean=yes + ;; + -h | --help) + usage + exit + ;; + docker) + use_docker=yes + ;; + release | debug) + build_type="$opt" + ;; + gcc | clang) + compiler="$opt" + ;; + ninja | make) + generator="$opt" + ;; + docs | docs-only) + preset='unixlike-docs-only' + ;; + *) + >&2 echo 'Invalid option!' "($opt)" + usage + exit 1 + ;; + esac + shift +done + +if [[ $jobs -gt $(nproc) ]]; then + >&2 echo "Warning: number of jobs ($jobs) exceeds nproc ($(nproc)). Reducing to nproc." + jobs=$(nproc) +fi + +preset="${preset-${cross_compile}unixlike-${compiler}-${generator}-${build_type}}" + +if [[ -n $deep_clean ]]; then + rm -rf "./build/$preset" +fi + +cmake_args+=(--workflow --preset "$preset") +[[ -n $fresh ]] && cmake_args+=("$fresh") + +if [[ -n $use_docker ]]; then + # Only add :Z if SELinux is enabled + if command -v getenforce &>/dev/null && [[ $(getenforce) != "Disabled" ]]; then + selinux_flag=":Z" + else + selinux_flag="" + fi + + ccache_dir="$(ccache -p | sed -En 's/^.*cache_dir =\s+(.*)/\1/p')" + docker_volumes+=(--volume .:/workspace/asmgrader"$selinux_flag") + [[ -n $ccache_dir ]] && docker_volumes+=(--volume "$ccache_dir":/workspace/ccache"$selinux_flag") + [[ -n $CPM_SOURCE_CACHE ]] && docker_volumes+=(--volume "$CPM_SOURCE_CACHE":/workspace/CPM"$selinux_flag") +fi + +if [[ -n $dry_run ]]; then + if [[ -n $use_docker ]]; then + cat < build directory [a path or just the build preset name] + note that cross-compiled builds have a low chance of working non-natively + -t, --target remote debugging target to connect to + -l, --log log level for exec [may also be specified with env{LOG_LEVEL}] + -a, --args args to pass to executable [consumes until --] + -s, --server start or manage gdb server + arg is one of: + - start a gdb server on port + k[ill] - kill all running gdb servers + l[ist] - list all running gdb servers + +Example: + debug tests --build unixlike-clang-release --args --nothrow --invisibles -- --tui +EOF +} >&2 + +# Prompt user for y/n confirmation +# Defaults to N (no) for any response that is not affirmative +# Args: +# $1 prompt - the string to prompt the user with +# " [y/N] " is implicitly appended to the end +# Returns: zero if user replied affirmatively, nonzero otherwise +confirm() { + read -rp "$1 [y/N] " + [[ $REPLY =~ ^[Yy](es)?$ ]] +} + +# Check that a command is available +# Args: +# $1 cmd +# Returns: zero upon success, nonzero otherwise +available() { + command -v "$1" &>/dev/null +} + +# Returns success if stdout is a terminal +# used to determine whether to do fancy formatting to output +stdout_is_term() { + [[ -t 1 ]] +} + +# Print an missing arg error along with usage, and exit +arg_error() { + >&2 echo "Missing required argument for $1" + usage + exit 1 +} + +# Get a string to eval/print from an array of cmd+args +# Args: +# $1 cmd +# $2- args +get_cmd_str() { + result="$1" + shift + + for arg; do + [[ $arg =~ [$IFS] ]] && arg="'$arg'" + result+=" $arg" + done + + echo "$result" +} + +# Get a default build directory out of those that exist in ./build/ +# Returns: zero if dir was found, nonzero otherwise +get_default_build_dir() { + include_regex='.*' + exclude_regex='.*docs.*' + + # FIXME: shit + if [[ -n $cross_comp ]]; then + include_regex='.*aarch64.*' + # We'd only ever be cross-compiling x86_64 => AArch64, so this else is fine + elif [[ $(arch) != "aarch64" ]]; then + exclude_regex+='|.*aarch64.*' + fi + + find ./build/ -regextype egrep \ + -maxdepth 1 -mindepth 1 \ + -type d \ + -not -regex "$exclude_regex" \ + -regex "$include_regex" \ + -print -quit | + grep . # This is to exit with a nonzero status if no files are found + +} + +# -------------------------------------------------------------------------------- +# Variables +# -------------------------------------------------------------------------------- + +# ----------------------------- +# ---------- OPTIONS ---------- +### left as empty if unspecified + +# whether program is in dry-run mode +dry_run= +# whether we are attempting to debug a cross-compiled binary +cross_comp= +# argument for -s/--server subcommand +server_arg= +# build directory where execs are located +# default is set later if unspecified based on other options (see get_default_build_dir) +build_dir= +# executable path relative to build dir; specified with tests|grader|prof +exec= +# remote executable or server specified with -t/--target +remote_target= +# log level passed to exec +# defaults to error since logs are generally superfluous when using gdb +log_level="${LOG_LEVEL-error}" +# args passed to executable specified with -a/--args +exec_args=() +# args passed to gdb specified after -- +gdb_args=( + # TODO: disable all sanitizers if running cross-compiled exec non-natively + # + # LSAN does not work under gdb + # abortions will cause gdb to break on ASAN errors + '--quiet' + '--ex' 'set environment ASAN_OPTIONS detect_leaks=0:abort_on_error=1' +) + +# ------------------------------ +# the command to run, where cmd[0] is the exec and cmd[1:] are the args +# Printed in the case of dry-run and passed to `eval` otherwise +cmd=() +# string to eval/print from cmd generated by get_cmd_str +cmd_str= +# file to log to for gdbserver +gdbserver_logfile= +# pid of gdbserver +gdbserver_pid= + +# -------------------------------------------------------------------------------- +# Script arguments +# -------------------------------------------------------------------------------- + +# ---------------------------------------- +# ---------- Raw arg processing ---------- +while (($#)); do + opt=${1,,} # transform to all lowercase + + case "$opt" in + --) # end of normal arguments and options delimeter; start of gdbargs + shift + gdb_args+=("$@") + shift $# + break + ;; + # ------------------------------------------ + # ---------- Positional arguments ---------- + g | grader) + exec="tests/asmgrader_dumb_cli" + ;; + p | prof) + exec="tests/asmgrader_dumb_profcli" + ;; + t | tests) + exec="tests/asmgrader_tests" + # Tell catch2 to trigger a breakpoint upon failure + exec_args+=(--break) + ;; + # ---------------------------------------- + # ---------- OPTIONAL arguments ---------- + -h | --help) + usage + exit + ;; + -n | --dry-run) + dry_run=yes + ;; + -b | --build) + (($# > 1)) || arg_error "$1" + shift + build_dir=build/"$1" + ;; + -t | --target) + (($# > 1)) || arg_error "$1" + shift + remote_target="$1" + ;; + -l | --log) + (($# > 1)) || arg_error "$1" + shift + log_level="$1" + ;; + -a | --args) + # consume arguments until -- + while (($#)) && [[ $1 != -- ]]; do + exec_args+=("$1") + shift + done + ;; + -s | --server) + (($# > 1)) || arg_error "$1" + shift + server_arg="$1" + ;; + *) + >&2 echo 'Invalid option!' "($1)" + usage + exit 1 + ;; + esac + shift +done + +# ------------------------------------------------- +# ---------- Arg validation + processing ---------- + +if [[ $remote_target && $server ]]; then + >&2 echo 'Error: -t/--target is mutually exclusive with -s/--server' + usage + exit 1 +fi + +if [[ $remote_target ]]; then + echo "Attempting to connect to target $remote_target..." + gdb_args+=('--ex' "target remote $remote_target") +fi + +if [[ $log_level ]]; then + gdb_args+=('--ex' "set environment LOG_LEVEL $log_level") +fi + +if [[ -z $exec ]]; then + >&2 echo 'Selecting default executable: tests/asmgrader_tests' + exec=tests/asmgrader_tests +fi + +if [[ -z $build_dir ]]; then + # Try to find a default build dir + if ! build_dir="$(get_default_build_dir)"; then + >&2 echo "Could not find a directory in ./build/ based on specs" + exit 1 + else + >&2 echo "Selecting default build directory: $build_dir" + fi +fi +if ! [[ -d $build_dir ]]; then + >&2 echo "Build dir '$build_dir' does not exist" + exit 1 +fi + +if [[ -n $server_arg ]]; then + if ! available gdbserver; then + >&2 echo 'gdbserver command is not available' + exit 1 + fi + + case $server_arg in + k | kill) + if ! gdbserver_pid=$(pgrep gdbserver | xargs echo); then + >&2 echo 'It seems that no gdbserver is running' + exit 1 + fi + ps h $gdbserver_pid + num_pids=$(wc -w <<<"$gdbserver_pid") + if [[ $num_pids -gt 1 ]]; then + pid_list="$(tr ' ' '|' <<<"$gdbserver_pid")" + read -rp 'Choose which pid to kill '"[$pid_list] " + pid_regex="^$pid_list$" + [[ $REPLY =~ $pid_regex ]] || { + >&2 echo 'Bad choice' + exit 1 + } + echo 'Your choice:' + ps h $REPLY + gdbserver_pid=$REPLY + fi + confirm "Kill the above process" + kill -9 "$gdbserver_pid" + exit + ;; + l | list) + echo 'List of running gdbservers:' + pgrep -a gdbserver || echo '' + exit + ;; + *:*) ;; + *) + if ! [[ $server_arg =~ ^[0-9]+$ ]]; then + >&2 echo 'Invalid argument for server!' "($server_arg)" + usage + exit 1 + fi + + server_arg="localhost:$server_arg" + ;; + esac +fi + +# Add directories as found in compile_commands.json +if [[ -z $server_arg && -f $build_dir/compile_commands.json ]]; then + search_dirs="" + # Add all -I and -isystem directory entries + search_dirs="$(grep -o -e '-I[^ ]*' -e '-isystem [^ ]*' "$build_dir/compile_commands.json" | sed -E 's/-I|-isystem //g')" + + if available jq; then + search_dirs+=" $(jq --raw-output '.[].file' "$build_dir/compile_commands.json" | xargs dirname)" + else + >&2 echo 'Warning: jq does not exist, so parts of compile_commands.json were not parsed to add search dirs for GDB' + fi + + search_dirs=$(sort -u <<<"$search_dirs") + + echo "Adding $(wc -l <<<"$search_dirs") directories to GDB search path" + + for d in $search_dirs; do + # Directories may not exist if, for instance, we cross-compiled somewhere else + [[ -d $d ]] && gdb_args+=("--directory" "$d") + done +fi + +# -------------------------------------------------------------------------------- +# Command building +# -------------------------------------------------------------------------------- + +if [[ $server_arg ]]; then + cmd+=('nohup' 'gdbserver' "$server_arg") +else + cmd+=('gdb' "${gdb_args[@]}" '--args') +fi + +cmd+=("$build_dir/$exec" "${exec_args[@]}") + +if [[ -n $server_arg ]]; then + gdbserver_logfile="$(mktemp /tmp/gdbserver-log.XXX)" + cmd+=("&>$gdbserver_logfile" '&') +fi + +cmd_str="$(get_cmd_str "${cmd[@]}")" + +if [[ $dry_run ]]; then + stdout_is_term || { + echo "$cmd_str" + exit + } + + # FANCY output + + optc=$'\e[33m' # option color + dirc=$'\e[34m' # directory color + strc=$'\e[32m' # other string color + spec=$'\e[31m' # special operators + nc=$'\e[0m' # no color + + sed -E " + # Non-quoted strings + s/([^-./][[:graph:]]+)/${strc}\1${nc}/g + # Options (start with -) + s/((\s|')-[[:graph:]]+)/${optc}\1${nc}/g + # Quoted strings + s/(['\"][^-][^']+['\"])/${strc}\1${nc}/g + # Directories (start with / or ./) + s/((\/|\.\/)[[:graph:]]+)/${dirc}\1${nc}/g + # Special bash operators + s/(&|&>|>(\/|\.\/)[[:graph:]]+)/${spec}\1${nc}/g + " <<<"${cmd_str// -/$'\n '-}" + exit +fi + +eval "$cmd_str" + +if [[ $server_arg ]]; then + gdbserver_pid=$! + >&2 echo "Starting gdbserver in the background on $server_arg (pid=$gdbserver_pid)" + >&2 echo "Logging to: $gdbserver_logfile" + sleep 0.5 + if ! kill -0 $gdbserver_pid &>/dev/null; then + printf '%80s\n' '' | tr ' ' '#' >&2 + >&2 echo 'Command Failed! Output:' + cat "$gdbserver_logfile" + fi +fi diff --git a/scripts/debug.sh b/scripts/debug.sh deleted file mode 100755 index 06bda69..0000000 --- a/scripts/debug.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -set -e - -BUILD_DIR="./build/unixlike-gcc-debug" - -if [[ $1 == "tests" || $1 == "t" ]]; then - # Tell catch2 to break upon failure, allowing gdb to attach - EXEC="$BUILD_DIR/tests/asmgrader_tests" - EXEC_ARGS=(--break) - shift -elif [[ $1 == "prof" || $1 == "p" ]]; then - EXEC="$BUILD_DIR/cs3b-grader/profgrader" - shift -else - EXEC="$BUILD_DIR/cs3b-grader/grader" - EXEC_ARGS=() -fi - -if [[ $# ]]; then - GDB_ARGS=("$@") -fi -for d in "$BUILD_DIR"/_deps/*-src; do - GDB_ARGS=("--directory" "$d" "${GDB_ARGS[@]}") -done - -# logs are probably kind of useless if we know what we're debugging -if [ ! -v LOG_LEVEL ]; then - LOG_LEVEL=error -fi - -# LSAN does not work under gdb -# abortions will cause gdb to break on ASAN errors -gdb --ex 'set environment ASAN_OPTIONS detect_leaks=0:abort_on_error=1' \ - --ex "set environment LOG_LEVEL $LOG_LEVEL" \ - --quiet \ - "${GDB_ARGS[@]}" \ - --args $EXEC "${EXEC_ARGS[@]}" diff --git a/scripts/generate-presets b/scripts/generate-presets new file mode 100755 index 0000000..cc8c1a0 --- /dev/null +++ b/scripts/generate-presets @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +# +# Generates the set of matrix-like presets for use with cmake presets +# + +import json, itertools +import subprocess +import sys + +compilers = [ + ("gcc", "g++"), + ("clang", "clang++") +] +generators = [ + ("ninja", "Ninja"), + ("make", "Unix Makefiles") +] +configs = [ + ("debug", "Debug"), + ("release", "RelWithDebInfo") +] + +def make_description(comp: str, gen: str, conf: str, crosscomp: bool, kind: str) -> str: + res = f"target Unix-like OS using {comp} and {gen} in {conf} mode" + if crosscomp: + res = "cross-compile to AArch64, " + res + + res = kind[0].upper() + kind[1:] + ": " + res + + return res + +# Common configure presets to inherit from + +conf_common = [ + { + "name": "conf-base", + "description": "General settings that apply to all configurations", + "hidden": True, + "binaryDir": "${sourceDir}/build/${presetName}", + "installDir": "${sourceDir}/install/${presetName}" + }, + { + "name": "conf-variables-base", + "hidden": True, + "description": "General variable options that apply to most configurations", + "cacheVariables": { + "ASMGRADER_BUILD_TESTS": "ON", + "ASMGRADER_ENABLE_CACHE": "ON", + "ASMGRADER_ENABLE_PCH": "ON" + } + }, + { + "name": "conf-unixlike-base", + "description": "Unix-like OS settings", + "hidden": True, + "inherits": "conf-base", + "condition": { + "type": "inList", + "string": "${hostSystemName}", + "list": [ + "Linux", + "Darwin" + ] + } + }, + { + "name": "conf-aarch64cross-base", + "hidden": True, + "description": "Cross-compilation to AArch64 settings", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/aarch64-toolchain.cmake" + } + }, +] + +conf_docs_only = [ + { + "name": "unixlike-docs-only", + "displayName": "Configure: target Unix-like OS, for only docs", + "inherits": "conf-unixlike-base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "ASMGRADER_BUILD_DOCS": "ON", + "ASMGRADER_BUILD_DOCS_ONLY": "ON" + } + } +] + +test_common = [ + { + "name": "test-base", + "description": "Test CMake settings that apply to all configurations", + "hidden": True, + "output": { + "outputOnFailure": True, + "outputJUnitFile": "${sourceDir}/reports/junit-ctest.xml" + }, + "execution": { + "noTestsAction": "error" + }, + } +] + +build_docs_only = [ + { + "name": "unixlike-docs-only", + "displayName": "Build: target Unix-like OS, for only docs", + "configurePreset": "unixlike-docs-only", + "targets": "doxygen-docs" + } +] + +workflow_docs_only = [ + { + "name": "unixlike-docs-only", + "displayName": "Workflow: target Unix-like OS, for only docs", + "steps": [ + { + "type": "configure", + "name": "unixlike-docs-only" + }, + { + "type": "build", + "name": "unixlike-docs-only" + } + ] + } +] + +presets = { + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 25, + "patch": 0 + }, + + "configurePresets": conf_common + [ + { + "name": ("crossaarch64-" if crosscomp else "") + f"unixlike-{cc}-{genname}-{cfgname}", + "displayName": make_description(cc, genname, cfgname, crosscomp, "configure"), + "inherits": ["conf-unixlike-base", "conf-variables-base"] + + (["conf-aarch64cross-base"] if crosscomp else []), + "generator": gen, + "cacheVariables": { + "CMAKE_C_COMPILER": cc, + "CMAKE_CXX_COMPILER": cxx, + "CMAKE_BUILD_TYPE": cfg, + }, + } + for (cc, cxx), (genname, gen), (cfgname, cfg), crosscomp in itertools.product(compilers, generators, configs, (False, True)) if not (crosscomp and cc != "gcc") + ] + conf_docs_only, + + "buildPresets": [ + { + "name": ("crossaarch64-" if crosscomp else "") + f"unixlike-{cc}-{genname}-{cfgname}", + "displayName": make_description(cc, genname, cfgname, crosscomp, "build"), + "configurePreset": ("crossaarch64-" if crosscomp else "") + f"unixlike-{cc}-{genname}-{cfgname}" + } + for (cc, _), (genname, _), (cfgname, _), crosscomp in itertools.product(compilers, generators, configs, (False, True)) if not (crosscomp and cc != "gcc") + ] + build_docs_only, + + "testPresets": test_common + [ + { + "name": f"unixlike-{cc}-{genname}-{cfgname}", + "displayName": make_description(cc, genname, cfgname, False, "test"), + "inherits": "test-base", + "configurePreset": f"unixlike-{cc}-{genname}-{cfgname}" + } + for (cc, _), (genname, _), (cfgname, _) in itertools.product(compilers, generators, configs) + ], + + "workflowPresets": [ + { + "name": ("crossaarch64-" if crosscomp else "") + f"unixlike-{cc}-{genname}-{cfgname}", + "displayName": make_description(cc, genname, cfgname, crosscomp, "workflow"), + "steps": [ + { + "type": "configure", + "name": ("crossaarch64-" if crosscomp else "") + f"unixlike-{cc}-{genname}-{cfgname}" + }, + { + "type": "build", + "name": ("crossaarch64-" if crosscomp else "") + f"unixlike-{cc}-{genname}-{cfgname}" + } + ] + } + for (cc, _), (genname, _), (cfgname, _), crosscomp in itertools.product(compilers, generators, configs, (False, True)) if not (crosscomp and cc != "gcc") + ] + [ + { + "name": f"unixlike-{cc}-{genname}-{cfgname}-test", + "displayName": make_description(cc, genname, cfgname, False, "workflow (with tests)"), + "steps": [ + { + "type": "configure", + "name": f"unixlike-{cc}-{genname}-{cfgname}" + }, + { + "type": "build", + "name": f"unixlike-{cc}-{genname}-{cfgname}" + }, + { + "type": "test", + "name": f"unixlike-{cc}-{genname}-{cfgname}" + } + ] + } + for (cc, _), (genname, _), (cfgname, _) in itertools.product(compilers, generators, configs) + ] + workflow_docs_only +} + +def main(): + if len(sys.argv) > 1 and sys.argv[1] in ("-l", "--list"): + list_res = subprocess.run( + ["cmake", "--list-presets", "all"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ).stdout + + print(str(list_res, encoding="utf-8")) + return + + file = "CMakePresets.json" + json.dump(presets, open(file, "w"), indent=4) + print("CMake presets written to", file) + +if __name__ == "__main__": + main() diff --git a/scripts/install-hooks.sh b/scripts/install-hooks similarity index 100% rename from scripts/install-hooks.sh rename to scripts/install-hooks diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..7550c18 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# +# Lint project with various static analysis tools +# + +# Exit on error; exit status of pipeline is nonzero if any command fails +set -eo pipefail + +jobs=12 + +usage() { + cat <&2 + +readarray -t lint_files < <( + find src/ include/ tests/ examples/ -name '*.hpp' -or -name '*.cpp' +) + +compilation_db=./build/unixlike-clang-ninja-release/compile_commands.json #"$(find build/ -name compile_commands.json -not -path '*cross*' -not -path '*docs*' | sort | head -n1)" + +# shellcheck disable=SC2034 +readarray -t cmake_files < <( + find . -name 'CMakeLists.txt' -or -name '*.cmake' -not -path 'build' +) + +# Run a group of commands in parallel. Displays a progress bar. +# Args: +# $1 maxjobs : maximum number of jobs +# $2-... commands : commands, which are ;-seperated strings as "exec;args..." +parallel() { + [[ $# -lt 2 ]] && return 1 + + local maxjobs=$1 + shift + + local cmds=("$@") + local -i num_cmds=${#cmds[@]} + + local -i running=0 + local -i done=0 + local -i retcode=0 + + for cmd in "${cmds[@]}"; do + eval "$cmd" & + printf '[%3d/%3d] Running: %s\n' $((++done)) $num_cmds "$cmd" + if [[ $((++running)) -ge $maxjobs ]]; then + wait -n || retcode+=1 + ((--running)) + fi + done + + while [[ -n $(jobs -r) ]]; do + wait -n || retcode+=1 + done + + return $retcode +} + +run_tools() { + local -i cf_failed + local -i ct_failed + local -i cppc_failed + + echo + echo "==========================================================" + echo "Clang Format:" + echo "==========================================================" + readarray -t cf_cmds < <( + printf 'clang-format %q --dry-run -Werror\n' \ + "${lint_files[@]}" + ) + parallel $jobs "${cf_cmds[@]}" || cf_failed+=$? + + echo + echo "==========================================================" + echo "Clang Tidy" + echo "==========================================================" + header_filter="$(pwd)/(src|include|tests|examples)/.*" + + readarray -t ct_cmds < <( + printf "clang-tidy %q --use-color -p \"$compilation_db\" --warnings-as-errors='*' --header-filter \"$header_filter\"\n" \ + "${lint_files[@]}" + ) + parallel $jobs "${ct_cmds[@]}" || ct_failed+=$? + + echo + echo "==========================================================" + echo "Cppcheck" + echo "==========================================================" + cppcheck_dir=.cache/cppcheck + mkdir -p $cppcheck_dir + cppcheck_report=reports/cppcheck.xml + + cppcheck --project="$compilation_db" \ + -ibuild -i"${CPM_SOURCE_CACHE--}" \ + --check-level=exhaustive --max-ctu-depth=10 --enable=all --inconclusive \ + --platform=native \ + --inline-suppr --suppress=missingIncludeSystem \ + -j $jobs \ + --check-library \ + --library=gnu --library=boost --library=posix \ + --checkers-report=reports/cppcheck-checkers.txt \ + --cppcheck-build-dir=$cppcheck_dir \ + --xml --output-file=$cppcheck_report + + cppc_failed="$(grep -c '$skipfile_path <>$skipfile_path </dev/null; then + run_codechecker +else + run_tools +fi diff --git a/scripts/lint.sh b/scripts/lint.sh deleted file mode 100755 index 4b2478a..0000000 --- a/scripts/lint.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -# TODO: Replace this with CMake targets - -# Exit on error -set -e - -ROOT_DIR=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - pwd -P -) - -cd "$ROOT_DIR" - -failed=0 - -lint_files=$( - find core/ cs3b-grader/ tests/ -name '*.hpp' -or -name '*.cpp' -) -compilation_db=build/compile_commands.json - -cmake_files=$( - find . -name 'CMakeLists.txt' -not -path 'build' -) - -echo "Missing CMake Includes:" -echo "==========================================================" -for f in $lint_files; do - stripped_path="${f#*/}" # remove top-level dir - if ! grep -Fq "$stripped_path" $cmake_files; then - echo "$f" - failed=1 - fi -done -if [[ $failed -eq 0 ]]; then - echo "" -fi - -echo -echo "Improper Library Conventions:" -echo "==========================================================" -# Find improper library header conventions ("" instead of <>) -if grep -E '"(range|boost|fmt|catch2|nlohmann|argparse|gsl)/' $lint_files; then - failed=1 -else - echo "" -fi - -echo -echo "Clang Tidy:" -echo "==========================================================" -if ! clang-tidy $lint_files -p "$compilation_db" --warnings-as-errors='*'; then - failed=1 -fi - -echo -echo "Clang Format:" -echo "==========================================================" -if ! clang-format $lint_files --dry-run -Werror; then - failed=1 -fi - -exit $failed diff --git a/scripts/stats b/scripts/stats new file mode 100755 index 0000000..61e0d8a --- /dev/null +++ b/scripts/stats @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# +# A fun little script to count the project's lines of code +# + +readarray -t all_files < <(find . -name '*.[hc]pp' -not -path './build/*') + +echo "${#all_files[@]} files" +echo "$(cat "${all_files[@]}" | wc -l) lines" diff --git a/scripts/utest b/scripts/utest new file mode 100755 index 0000000..a8f68cf --- /dev/null +++ b/scripts/utest @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +# +# Util script to simplify running tests +# + +usage() { + cat <&2 + +expand() { + eval "IFS='|'; echo \"\${$1[*]}\"" +} + +cmd="cmake" + +build_type="debug" +compiler="gcc" +generator="ninja" +fresh= +no_build= +report= +jobs=${JOBS-$(nproc)} + +cmake_args=() + +# Process all args +while (($#)); do + opt=${1,,} # transform to all lowercase + case "$opt" in + -n | --dry-run) + dry_run=yes + ;; + --no-build) + no_build=yes + ;; + --fresh) + fresh="--fresh" + ;; + -h | --help) + usage + exit + ;; + -j | --jobs) + if ! [[ $2 =~ ^[0-9]+$ ]]; then + >&2 echo 'Specified JOBS arg is invalid' + exit 1 + fi + jobs="$2" + shift + ;; + release | debug) + build_type="$opt" + ;; + gcc | clang) + compiler="$opt" + ;; + ninja | make) + generator="$opt" + ;; + *) + >&2 echo 'Invalid option!' "($opt)" + usage + exit 1 + ;; + esac + shift +done + +if [[ $jobs -gt $(nproc) ]]; then + >&2 echo "Warning: number of jobs ($jobs) exceeds nproc ($(nproc)). Reducing to nproc." + jobs=$(nproc) +fi + +preset="unixlike-${compiler}-${generator}-${build_type}" + +if [[ -z $no_build ]]; then + preset+="-test" + cmake_args+=(--workflow "$preset") + [[ -n $fresh ]] && cmake_args+=("$fresh") +else + cmd=ctest + cmake_args+=(--preset "$preset") +fi + +if [[ -n $dry_run ]]; then + cat < Date: Fri, 24 Oct 2025 15:44:32 -0700 Subject: [PATCH 04/41] Massive updates to workflow script, add cross-compilation support and Docker dev image (closes #22) * chore: huge improvements to build scripts; Makefile deprecated * fix utest script * fix pre-push git hook * fix .gitignore for envrc files * more logging for pre-push script * chore: add docs only to workflows; integrate into build script * chore: output JUnit from ctest by default in presets * chore: update workflow with docker * chore: fix ci to use new workflow scripts * chore: fix utest script * add more logging to linux syscall wrappers * fix: potentially fixed race condition timeout bug with waitid(2) call From 1ba5848fd47aa6e96c421146e8dfbc2eb33f0950 Mon Sep 17 00:00:00 2001 From: Matt <38063055+Terracom12@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:11:22 -0700 Subject: [PATCH 05/41] feat: very basic api for temporary file management (#65) * feat: add very basic temporary file API * fix: ensure open fds are marked as FD_CLOEXEC in parent proc * chore: refactor run result * feat: add api for process statistics and minor api and logging tweaks * feat: add TempFile::remove * add str impl for RunResult * add more functionality to tempfile api; various fixes and logging improvements --- include/asmgrader/api/process_statistics.hpp | 43 ++++ include/asmgrader/api/tempfile.hpp | 57 ++++++ include/asmgrader/api/test_context.hpp | 11 +- include/asmgrader/asmgrader.hpp | 1 + include/asmgrader/common/linux.hpp | 42 ++-- include/asmgrader/subprocess/run_result.hpp | 72 ++++++- include/asmgrader/subprocess/subprocess.hpp | 4 + include/asmgrader/subprocess/tracer.hpp | 3 + src/CMakeLists.txt | 3 +- src/api/process_statistics.cpp | 52 +++++ src/api/tempfile.cpp | 194 +++++++++++++++++++ src/api/test_context.cpp | 28 ++- src/output/plaintext_serializer.cpp | 4 + src/subprocess/run_result.cpp | 29 --- src/subprocess/subprocess.cpp | 39 ++++ src/subprocess/traced_subprocess.cpp | 3 + tests/CMakeLists.txt | 1 + tests/test_tempfile.cpp | 77 ++++++++ 18 files changed, 604 insertions(+), 59 deletions(-) create mode 100644 include/asmgrader/api/process_statistics.hpp create mode 100644 include/asmgrader/api/tempfile.hpp create mode 100644 src/api/process_statistics.cpp create mode 100644 src/api/tempfile.cpp delete mode 100644 src/subprocess/run_result.cpp create mode 100644 tests/test_tempfile.cpp diff --git a/include/asmgrader/api/process_statistics.hpp b/include/asmgrader/api/process_statistics.hpp new file mode 100644 index 0000000..a7833f9 --- /dev/null +++ b/include/asmgrader/api/process_statistics.hpp @@ -0,0 +1,43 @@ +/// \file +/// Provides an API for basic process statistics +#pragma once + +#include +#include +#include +#include + +#include + +namespace asmgrader { + +/// Obtains statistics of a process with a given PID. +/// Provides various accessors returning POD types. +/// +/// For now, features are extremely limited. +/// +/// Makes use of proc(5) +class ProcessStats +{ +public: + /// Open File Descriptor(s) Info POD + struct OpenFds + { + std::vector fds; + }; + + explicit ProcessStats(pid_t pid); + + OpenFds open_fds() const { return open_fds_; } + +private: + std::filesystem::path procfs_path(std::string_view suffix) const; + + OpenFds get_open_fds() const; + + pid_t pid_; + + OpenFds open_fds_; +}; + +} // namespace asmgrader diff --git a/include/asmgrader/api/tempfile.hpp b/include/asmgrader/api/tempfile.hpp new file mode 100644 index 0000000..5a61bfb --- /dev/null +++ b/include/asmgrader/api/tempfile.hpp @@ -0,0 +1,57 @@ +/// \file +/// Provides an API for creating and managing temporary files in the context of tests +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace asmgrader { + +/// Interface onto a temporary file. +class TempFile : NonMovable +{ +public: + explicit TempFile(bool create = true); + explicit TempFile(u16 perms); + ~TempFile() noexcept; + + std::string read_all(); + void write(std::string_view str); + void truncate(); + + bool exists() const; + + std::filesystem::path path() const { return file_info_.path; } + + std::string path_str() const { return file_info_.path.string(); } + + [[nodiscard]] static std::filesystem::path unique_path(); + + /// Remove (delete) a file. Intended for use with files created based on + /// \ref unique_path, but could be used to safely remove any arbitrary file. + static Expected<> remove(const std::filesystem::path& path); + +private: + struct FileInfo + { + std::filesystem::path path; + std::fstream handle; + }; + + [[nodiscard]] static FileInfo generate_unique_file(); + + bool ensure_created(); + + FileInfo file_info_; +}; + +} // namespace asmgrader diff --git a/include/asmgrader/api/test_context.hpp b/include/asmgrader/api/test_context.hpp index e860128..d07a5af 100644 --- a/include/asmgrader/api/test_context.hpp +++ b/include/asmgrader/api/test_context.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -107,7 +108,15 @@ class TestContext AsmFunction find_function(std::string name); /// Run the program normally from `_start`, stopping at the first exit(2) or exit_group(2) syscall invocation - RunResult run(); + Result run(); + + /// Run the program from `_start`, stopping at the first syscall matching syscallnr + /// OR the first exit(2) or exit_group(2) syscall invocation [whichever happens first] + Result run_until(u64 syscallnr); + + /// Obtain statistics for the subprocess being tested + /// Data is more limited when the process is not stopped! + ProcessStats stats(); private: bool require_impl(bool condition, const std::string& description, diff --git a/include/asmgrader/asmgrader.hpp b/include/asmgrader/asmgrader.hpp index 351ab8d..8b3f230 100644 --- a/include/asmgrader/asmgrader.hpp +++ b/include/asmgrader/asmgrader.hpp @@ -4,6 +4,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export diff --git a/include/asmgrader/common/linux.hpp b/include/asmgrader/common/linux.hpp index 5937ad9..afcfdee 100644 --- a/include/asmgrader/common/linux.hpp +++ b/include/asmgrader/common/linux.hpp @@ -52,7 +52,7 @@ inline Expected write(int fd, std::string_view data) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("write(fd={}, data={:?}, size={}) failed: '{}'", fd, data, data.size(), err); + LOG_TRACE("write(fd={}, data={:?}, size={}) failed: '{}'", fd, data, data.size(), err); return err; } @@ -71,7 +71,7 @@ inline Expected read(int fd, size_t count) { // NOLINT if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("read(fd={}, count={}) failed: '{}'", fd, count, err); + LOG_TRACE("read(fd={}, count={}) failed: '{}'", fd, count, err); return err; } @@ -92,10 +92,12 @@ inline Expected<> close(int fd) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("close failed: '{}'", err); + LOG_TRACE("close(fd={}) failed: '{}'", fd, err); return err; } + LOG_TRACE("close(fd={}) = {}", fd, res); + return {}; } @@ -107,12 +109,12 @@ inline Expected<> kill(pid_t pid, int sig) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("kill(pid={}, sig={}) failed: '{}'", pid, sig, err); + LOG_TRACE("kill(pid={}, sig={}) failed: '{}'", pid, sig, err); return err; } - LOG_DEBUG("kill(pid={}, sig={}) = {}", pid, sig, res); + LOG_TRACE("kill(pid={}, sig={}) = {}", pid, sig, res); return {}; } @@ -127,7 +129,7 @@ inline Expected<> access(gsl::czstring path, int mode) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("access(path={:?}, mode={}) failed '{}'", path, mode, err); + LOG_TRACE("access(path={:?}, mode={}) failed '{}'", path, mode, err); return err; } @@ -160,9 +162,9 @@ inline Expected<> execve(const std::string& exec, const std::vector auto err = make_error_code(errno); if (res == -1) { - LOG_DEBUG("execve failed: '{}'", err); + LOG_TRACE("execve failed: '{}'", err); } else { - LOG_DEBUG("execve failed (INVALID RETURN CODE = {}): '{}'", res, err); + LOG_TRACE("execve failed (INVALID RETURN CODE = {}): '{}'", res, err); } return err; @@ -182,7 +184,7 @@ inline Expected fork() { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("fork failed: '{}'", err); + LOG_TRACE("fork failed: '{}'", err); return err; } @@ -201,7 +203,7 @@ inline Expected open(const std::string& pathname, int flags, mode_t mode = if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("open(pathname={:?}, flags={}, mode={}) failed: '{}'", pathname, flags, mode, err); + LOG_TRACE("open(pathname={:?}, flags={}, mode={}) failed: '{}'", pathname, flags, mode, err); return err; } @@ -217,7 +219,7 @@ inline Expected lseek(int fd, off_t offset, int whence) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("lseek failed: '{}'", err); + LOG_TRACE("lseek failed: '{}'", err); return err; } @@ -233,9 +235,9 @@ inline Expected<> dup2(int oldfd, int newfd) { auto err = make_error_code(errno); if (res == -1) { - LOG_DEBUG("dup2 failed: '{}'", err); + LOG_TRACE("dup2 failed: '{}'", err); } else { - LOG_DEBUG("dup2 failed (INVALID RETURN CODE = {}): '{}'", res, err); + LOG_TRACE("dup2 failed (INVALID RETURN CODE = {}): '{}'", res, err); } return err; @@ -254,7 +256,7 @@ inline Expected ioctl(int fd, unsigned long request, void* argp) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("ioctl failed: '{}'", err); + LOG_TRACE("ioctl failed: '{}'", err); return err; } @@ -278,7 +280,7 @@ inline Expected fcntl(int fd, int cmd, std::optional arg = std::nullop if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("fcntl failed: '{}'", err); + LOG_TRACE("fcntl failed: '{}'", err); return err; } @@ -295,7 +297,7 @@ inline Expected waitid(idtype_t idtype, id_t id, int options = WSTOPP if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("waitid(idtype={}, id={}, options={}) failed: '{}'", fmt::underlying(idtype), id, options, err); + LOG_TRACE("waitid(idtype={}, id={}, options={}) failed: '{}'", fmt::underlying(idtype), id, options, err); return err; } @@ -314,7 +316,7 @@ inline Expected<> raise(int sig) { if (res == -1) { auto err = std::error_code(errno, std::system_category()); - LOG_DEBUG("raise({}) failed: '{}'", sig, err); + LOG_TRACE("raise({}) failed: '{}'", sig, err); return err; } @@ -343,7 +345,7 @@ inline Expected pipe2(int flags = 0) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("pipe(..., flags={}) failed: '{}'", flags, err); + LOG_TRACE("pipe(..., flags={}) failed: '{}'", flags, err); return err; } @@ -378,7 +380,7 @@ inline Expected ptrace(int request, pid_t pid = 0, AddrT addr = NULL, Data if (errno) { auto err = make_error_code(errno); - LOG_DEBUG("ptrace(req={}, pid={}, addr={}, data={}) failed: '{}'", request, pid, format_or_unknown(addr), + LOG_TRACE("ptrace(req={}, pid={}, addr={}, data={}) failed: '{}'", request, pid, format_or_unknown(addr), format_or_unknown(data), err); return err; @@ -399,7 +401,7 @@ inline Expected stat(const std::string& pathname) { if (res == -1) { auto err = make_error_code(errno); - LOG_DEBUG("stat failed: '{}'", err); + LOG_TRACE("stat failed: '{}'", err); return err; } diff --git a/include/asmgrader/subprocess/run_result.hpp b/include/asmgrader/subprocess/run_result.hpp index 74e9cba..47eee48 100644 --- a/include/asmgrader/subprocess/run_result.hpp +++ b/include/asmgrader/subprocess/run_result.hpp @@ -1,5 +1,10 @@ #pragma once +#include + +#include +#include + namespace asmgrader { class RunResult @@ -7,18 +12,73 @@ class RunResult public: enum class Kind { Exited, Killed, SignalCaught }; - static RunResult make_exited(int code); - static RunResult make_killed(int code); - static RunResult make_signal_caught(int code); + static constexpr RunResult make_exited(int code); + static constexpr RunResult make_killed(int code); + static constexpr RunResult make_signal_caught(int code); + + constexpr Kind get_kind() const; + constexpr int get_code() const; + + constexpr bool operator==(const RunResult&) const = default; - Kind get_kind() const; - int get_code() const; + std::string str() const; private: - RunResult(Kind kind, int code); + constexpr RunResult(Kind kind, int code); Kind kind_; int code_; }; +constexpr RunResult::RunResult(Kind kind, int code) + : kind_{kind} + , code_{code} {} + +constexpr RunResult RunResult::make_exited(int code) { + return {Kind::Exited, code}; +} + +constexpr RunResult RunResult::make_killed(int code) { + return {Kind::Killed, code}; +} + +constexpr RunResult RunResult::make_signal_caught(int code) { + return {Kind::SignalCaught, code}; +} + +constexpr RunResult::Kind RunResult::get_kind() const { + return kind_; +} + +constexpr int RunResult::get_code() const { + return code_; +} + +static constexpr auto RUN_SUCCESS = RunResult::make_exited(0); + +constexpr std::string_view format_as(const RunResult::Kind& from) { + switch (from) { + case RunResult::Kind::Exited: + return "Exited"; + case RunResult::Kind::Killed: + return "Killed"; + case RunResult::Kind::SignalCaught: + return "SignalCaught"; + default: + return ""; + } +} + +inline std::string format_as(const RunResult& from) { + return fmt::format("{}({})", from.get_kind(), from.get_code()); +} + +constexpr std::string_view str(const RunResult::Kind& from) { + return format_as(from); +} + +inline std::string RunResult::str() const { + return fmt::to_string(*this); +} + } // namespace asmgrader diff --git a/include/asmgrader/subprocess/subprocess.hpp b/include/asmgrader/subprocess/subprocess.hpp index 885fe4f..69fdc63 100644 --- a/include/asmgrader/subprocess/subprocess.hpp +++ b/include/asmgrader/subprocess/subprocess.hpp @@ -85,6 +85,10 @@ class Subprocess : NonCopyable Result read_stdout_poll_impl(int timeout_ms); + /// Marks all open fds (other than 0,1,2) as FD_CLOEXEC so that they get closed in the child proc + /// Run in the PARENT process. + Expected<> mark_cloexec_all() const; + /// Reads any data on the stdout pipe to stdout_buffer_ Result read_stdout_impl(); diff --git a/include/asmgrader/subprocess/tracer.hpp b/include/asmgrader/subprocess/tracer.hpp index 144f1d5..c2f3826 100644 --- a/include/asmgrader/subprocess/tracer.hpp +++ b/include/asmgrader/subprocess/tracer.hpp @@ -30,6 +30,7 @@ #include #include +#include #include #include // pid_t #include // user_regs_struct, user_fpregs_struct (user_fpsimd_struct) @@ -115,6 +116,8 @@ class Tracer std::uintptr_t get_mmapped_addr() const { return mmaped_address_; } + pid_t get_pid() const { return pid_; }; + private: /// Ensure that invariants hold /// pid_ parent is this process diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c8baec2..fb5ed3c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,7 +36,6 @@ set( subprocess/tracer.cpp subprocess/memory/memory_io_base.cpp subprocess/memory/ptrace_memory_io.cpp - subprocess/run_result.cpp common/terminal_checks.cpp @@ -55,6 +54,8 @@ set( user/cl_args.cpp + api/tempfile.cpp + api/process_statistics.cpp api/assignment.cpp api/test_context.cpp api/syntax_highlighter.cpp diff --git a/src/api/process_statistics.cpp b/src/api/process_statistics.cpp new file mode 100644 index 0000000..1bad891 --- /dev/null +++ b/src/api/process_statistics.cpp @@ -0,0 +1,52 @@ +#include "api/process_statistics.hpp" + +#include "logging.hpp" + +#include + +#include +#include +#include + +#include + +namespace asmgrader { + +namespace fs = std::filesystem; + +ProcessStats::ProcessStats(pid_t pid) + : pid_{pid} + , open_fds_{get_open_fds()} {} + +fs::path ProcessStats::procfs_path(std::string_view suffix) const { + constexpr std::string_view procfs_template = "/proc/{}"; + fs::path base_path = fmt::format(procfs_template, pid_); + return base_path / suffix; +} + +ProcessStats::OpenFds ProcessStats::get_open_fds() const { + const fs::path procfs_fds = procfs_path("fd/"); + + if (!fs::exists(procfs_fds)) { + LOG_WARN("Could not get open fds : path {} does not exist (pid={})", procfs_fds, pid_); + return {}; + } + + OpenFds res; + + for (const auto& dir_entry : fs::directory_iterator{procfs_fds}) { + std::string filename = dir_entry.path().filename().string(); + + LOG_DEBUG("Got filename: {:?}", filename); + + try { + res.fds.push_back(std::stoi(filename)); + } catch (...) { + res.fds.push_back(-1); + } + } + + return res; +} + +} // namespace asmgrader diff --git a/src/api/tempfile.cpp b/src/api/tempfile.cpp new file mode 100644 index 0000000..d264ac5 --- /dev/null +++ b/src/api/tempfile.cpp @@ -0,0 +1,194 @@ +#include "api/tempfile.hpp" + +#include "common/aliases.hpp" +#include "common/expected.hpp" +#include "logging.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace asmgrader { + +namespace fs = std::filesystem; + +TempFile::TempFile(bool create) + : file_info_{.path = unique_path(), .handle = {}} { + if (create) { + ensure_created(); + } +} + +TempFile::TempFile(u16 perms) + : TempFile() { + auto fs_perms = static_cast(perms); + if ((fs_perms & ~fs::perms::mask) != fs::perms::none) { + LOG_ERROR("Invalid file perms specified ({:o}); Not setting permissions.", perms); + return; + } + + std::error_code err; + fs::permissions(file_info_.path, fs_perms, err); + if (err != std::error_code{}) { + LOG_ERROR("Failed to set file permissions to {:o} for {} : {}", perms, file_info_.path, err); + } +} + +TempFile::~TempFile() noexcept { + std::error_code err; + fs::remove(file_info_.path, err); + if (err != std::error_code{}) { + LOG_ERROR("Failed to remove tempfile {} : {}", file_info_.path, err); + } +} + +bool TempFile::exists() const { + return fs::exists(file_info_.path); +} + +bool TempFile::ensure_created() { + if (!file_info_.handle.is_open()) { + file_info_.handle.open(file_info_.path, std::ios::in | std::ios::out | std::ios::trunc); + + LOG_DEBUG("Creating a temporary file at {}", file_info_.path); + if (!file_info_.handle) { + LOG_FATAL("Failed to create temporary file {}", file_info_.path); + } + } + + return true; +} + +std::string TempFile::read_all() { + if (!ensure_created()) { + return ""; + } + // seek to the beginning of the file + file_info_.handle.seekg(0); + + std::stringstream strm; + strm << file_info_.handle.rdbuf(); + return strm.str(); +} + +void TempFile::write(std::string_view str) { + if (!ensure_created()) { + return; + } + // seek to the end of the file + file_info_.handle.seekg(0, std::ios::end); + file_info_.handle << str; + file_info_.handle.flush(); +} + +void TempFile::truncate() { + if (!ensure_created()) { + return; + } + std::error_code err; + fs::resize_file(file_info_.path, 0, err); + if (err != std::error_code{}) { + LOG_ERROR("Failed truncate tempfile {} : {}", file_info_.path, err); + } +} + +std::filesystem::path TempFile::unique_path() { + // Adapted from https://stackoverflow.com/a/79631059 + // Courtesy of Ted Lyngmo + // NOLINTBEGIN + + static const auto chars = [] { + // the characters you'd like to include + auto arr = + std::to_array({'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}); + // randomize the order of the characters: + ranges::shuffle(arr, std::mt19937(std::random_device{}())); + return arr; + }(); + + constexpr auto length_of_basename = 8; + + thread_local auto baseidx = [&] { + // randomize the initial state: + std::array rv{}; + std::mt19937 prng(std::random_device{}()); + std::uniform_int_distribution dist(0, chars.size() - 1); + ranges::generate(rv, [&] { return static_cast(dist(prng)); }); + return rv; + }(); + + // create the basename from the baseidices: + thread_local auto basename = [&] { + std::array rv{}; + ranges::transform(baseidx, rv.data(), [&](auto idx) { return chars[idx]; }); + return rv; + }(); + + fs::path tmpdir = fs::temp_directory_path(); + + constexpr auto num_tries = 10'000; + + for (int i = 0; i < num_tries; ++i) { // try a few different filenames + // generate the next basename: + size_t idx = length_of_basename; + + do { + --idx; + baseidx[idx] = (baseidx[idx] + 1) % chars.size(); + basename[idx] = chars[baseidx[idx]]; + } while (idx && baseidx[idx] == 0); + + // ...and create a full path: + fs::path tmpfile_path = tmpdir / basename.data(); + + // < C++23, so ios::noreplace is not available + if (!fs::exists(tmpfile_path)) { + return tmpfile_path; + } + } + // NOLINTEND + + LOG_FATAL("Failed to generate a unique filename tmpdir={} | num_tries={} | baseidx={} | basename={}", tmpdir, + num_tries, baseidx, basename); + throw std::runtime_error("Failed to generate a unique filename"); +} + +TempFile::FileInfo TempFile::generate_unique_file() { + TempFile::FileInfo ret{}; + + // FIXME: technically could have a race condition + auto path = unique_path(); + + return ret; +} + +Expected<> TempFile::remove(const fs::path& path) { + std::error_code err; + fs::remove(path, err); + + if (err != std::error_code{}) { + LOG_ERROR("Failed remove file {} : {}", path, err); + return err; + } + + return {}; +} + +} // namespace asmgrader diff --git a/src/api/test_context.cpp b/src/api/test_context.cpp index 7dff727..af73d1d 100644 --- a/src/api/test_context.cpp +++ b/src/api/test_context.cpp @@ -2,6 +2,7 @@ #include "api/asm_buffer.hpp" #include "api/metadata.hpp" +#include "api/process_statistics.hpp" #include "api/registers_state.hpp" #include "api/requirement.hpp" #include "api/test_base.hpp" @@ -32,6 +33,7 @@ #include #include #include +#include #include #include @@ -92,7 +94,7 @@ void TestContext::send_stdin(std::string_view input) { TRY_OR_THROW(prog_.get_subproc().send_stdin(input), "failed to write to stdin"); } -RunResult TestContext::run() { +Result TestContext::run() { int exit_code{}; auto res = prog_.run_until([&exit_code](const SyscallRecord& syscall) { @@ -112,7 +114,29 @@ RunResult TestContext::run() { return RunResult::make_exited(exit_code); } - return TRY_OR_THROW(res, "failed to run program"); + return res; +} + +Result TestContext::run_until(u64 syscallnr) { + std::optional exit_code{}; + + auto res = prog_.run_until([&exit_code, syscallnr](const SyscallRecord& syscall) { + if (syscall.num == SYS_exit || syscall.num == SYS_exit_group) { + exit_code = std::get(syscall.args.at(0)); + return true; + } + return syscall.num == syscallnr; + }); + + if (exit_code.has_value()) { + return RunResult::make_exited(*exit_code); + } + + return res; +} + +ProcessStats TestContext::stats() { + return ProcessStats{prog_.get_subproc().get_pid()}; } void TestContext::restart_program() { diff --git a/src/output/plaintext_serializer.cpp b/src/output/plaintext_serializer.cpp index 7052b15..0d4b582 100644 --- a/src/output/plaintext_serializer.cpp +++ b/src/output/plaintext_serializer.cpp @@ -109,6 +109,10 @@ void PlainTextSerializer::on_test_result(const TestResult& data) { if (!should_output_test(verbosity_)) { return; } + if (data.error.has_value()) { + sink_.write(style_str("Internal test context error: ", ERROR_STYLE)); + sink_.write(fmt::to_string(*data.error) + '\n'); + } // Potentially output a msg for an empty test if (data.num_total == 0) { diff --git a/src/subprocess/run_result.cpp b/src/subprocess/run_result.cpp deleted file mode 100644 index 0926487..0000000 --- a/src/subprocess/run_result.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "subprocess/run_result.hpp" - -namespace asmgrader { - -RunResult::RunResult(Kind kind, int code) - : kind_{kind} - , code_{code} {} - -RunResult RunResult::make_exited(int code) { - return {Kind::Exited, code}; -} - -RunResult RunResult::make_killed(int code) { - return {Kind::Killed, code}; -} - -RunResult RunResult::make_signal_caught(int code) { - return {Kind::SignalCaught, code}; -} - -RunResult::Kind RunResult::get_kind() const { - return kind_; -} - -int RunResult::get_code() const { - return code_; -} - -} // namespace asmgrader diff --git a/src/subprocess/subprocess.cpp b/src/subprocess/subprocess.cpp index 7ba7cf4..260ded8 100644 --- a/src/subprocess/subprocess.cpp +++ b/src/subprocess/subprocess.cpp @@ -14,7 +14,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -237,6 +240,10 @@ Result Subprocess::create(const std::string& exec, const std::vector Subprocess::init_child() { TRYE(linux::close(stdin_pipe_.write_fd), SyscallFailure); TRYE(linux::close(stdout_pipe_.read_fd), SyscallFailure); + namespace fs = std::filesystem; + + for (const auto& entry : fs::directory_iterator("/proc/self/fd")) { + int fd = std::stoi(entry.path().filename().string()); + + // skip stdin, stdout, stderr + if (fd <= 2) { + continue; + } + + // auto res = linux::close(fd); + // + // // If close(2) failed for a reason other than the fd not existing, return an error + // if (!res && res != linux::make_error_code(EBADF)) { + // return ErrorKind::SyscallFailure; + // } + } + return {}; } @@ -286,4 +311,18 @@ Result Subprocess::init_parent() { return {}; } +Expected<> Subprocess::mark_cloexec_all() const { + namespace fs = std::filesystem; + for (const auto& entry : fs::directory_iterator("/proc/self/fd")) { + int fd = std::stoi(entry.path().filename().string()); + + if (fd > 2) { + int flags = TRY(linux::fcntl(fd, F_GETFD)); + TRY(linux::fcntl(fd, F_SETFD, flags | FD_CLOEXEC)); + } + } + + return {}; +} + } // namespace asmgrader diff --git a/src/subprocess/traced_subprocess.cpp b/src/subprocess/traced_subprocess.cpp index ab0bbba..3b35e52 100644 --- a/src/subprocess/traced_subprocess.cpp +++ b/src/subprocess/traced_subprocess.cpp @@ -7,6 +7,8 @@ #include "subprocess/syscall_record.hpp" #include "subprocess/tracer.hpp" +#include + #include #include #include @@ -28,6 +30,7 @@ TracedSubprocess::~TracedSubprocess() { } LOG_DEBUG("Processed {} syscalls", tracer_.get_records().size()); + LOG_TRACE("Syscalls: {:?}", fmt::join(tracer_.get_records(), "\n")); if (is_alive()) { std::ignore = kill(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4f789d5..f2af051 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ set( test_registers_state.cpp test_file_searcher.cpp test_byte_ranges.cpp + test_tempfile.cpp ) ##### Simple assembly executable diff --git a/tests/test_tempfile.cpp b/tests/test_tempfile.cpp new file mode 100644 index 0000000..49d2da9 --- /dev/null +++ b/tests/test_tempfile.cpp @@ -0,0 +1,77 @@ +#include "catch2_custom.hpp" + +#include "api/tempfile.hpp" +#include "common/aliases.hpp" + +#include +#include + +namespace fs = std::filesystem; + +TEST_CASE("temp file creation and removal") { + fs::path path = [] { + asmgrader::TempFile tmp{}; + REQUIRE(fs::exists(tmp.path())); + return tmp.path(); + }(); + + REQUIRE_FALSE(fs::exists(path)); + + auto paths = [] { + asmgrader::TempFile tmp1{}; + REQUIRE(fs::exists(tmp1.path())); + + asmgrader::TempFile tmp2{}; + REQUIRE(fs::exists(tmp2.path())); + + asmgrader::TempFile tmp3{}; + REQUIRE(fs::exists(tmp3.path())); + + return std::vector{tmp1.path(), tmp2.path(), tmp3.path()}; + }(); + + for (const auto& p : paths) { + REQUIRE_FALSE(fs::exists(p)); + } + + path = asmgrader::TempFile{}.path(); + REQUIRE_FALSE(fs::exists(path)); +} + +TEST_CASE("temp files with specified permissions") { + auto verify_perms = [](asmgrader::u16 perms) { + asmgrader::TempFile tmp1{perms}; + REQUIRE(static_cast(fs::status(tmp1.path()).permissions()) == perms); + }; + + verify_perms(0000); + verify_perms(0777); + verify_perms(0700); + verify_perms(0770); + verify_perms(0666); + verify_perms(0660); + verify_perms(0606); + verify_perms(0066); + verify_perms(0006); +} + +TEST_CASE("temp file reading and writing") { + using Catch::Matchers::IsEmpty; + + asmgrader::TempFile tmp{}; + + REQUIRE_THAT(tmp.read_all(), IsEmpty()); + tmp.truncate(); + REQUIRE_THAT(tmp.read_all(), IsEmpty()); + tmp.write(""); + REQUIRE_THAT(tmp.read_all(), IsEmpty()); + + tmp.write("hello, world"); + REQUIRE(tmp.read_all() == "hello, world"); + + tmp.write("\n123"); + REQUIRE(tmp.read_all() == "hello, world\n123"); + + tmp.truncate(); + REQUIRE_THAT(tmp.read_all(), IsEmpty()); +} From f340896beb3367d9ae080a4e2737f0e8318e807c Mon Sep 17 00:00:00 2001 From: Matt <38063055+Terracom12@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:13:10 -0700 Subject: [PATCH 06/41] fix: capture stderr for subprocesses (#64) * feat: add very basic temporary file API * fix: ensure open fds are marked as FD_CLOEXEC in parent proc * chore: refactor run result * feat: add api for process statistics and minor api and logging tweaks * feat: add TempFile::remove * add str impl for RunResult * add more functionality to tempfile api; various fixes and logging improvements * add Expected::transform specialization for when T is void * capture stderr with Subprocess; update API accordingly * add test for reading from captured stderr * add api for reading from stderr to test_context * fix aarch64 test asm program --- include/asmgrader/api/test_context.hpp | 4 + include/asmgrader/common/expected.hpp | 11 ++ include/asmgrader/subprocess/subprocess.hpp | 90 ++++++++-- src/api/test_context.cpp | 13 +- src/subprocess/subprocess.cpp | 173 +++++++++++++------- tests/resources/simple_asm_aarch64.s | 22 ++- tests/resources/simple_asm_x86_64.s | 21 ++- tests/test_program.cpp | 28 +++- tests/test_subprocess.cpp | 4 +- 9 files changed, 284 insertions(+), 82 deletions(-) diff --git a/include/asmgrader/api/test_context.hpp b/include/asmgrader/api/test_context.hpp index d07a5af..260266a 100644 --- a/include/asmgrader/api/test_context.hpp +++ b/include/asmgrader/api/test_context.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,9 @@ class TestContext /// Get all stdout from since the beginning of the test invokation std::string get_full_stdout(); + Subprocess::OutputResult get_output(Subprocess::WhichOutput which = Subprocess::WhichOutput::StdoutAndStderr); + Subprocess::OutputResult get_full_output(Subprocess::WhichOutput which = Subprocess::WhichOutput::StdoutAndStderr); + /// Flushes any reamaining unread data in the stdin buffer /// Returns: number of bytes flushed, or error kind if failure occured std::size_t flush_stdin(); diff --git a/include/asmgrader/common/expected.hpp b/include/asmgrader/common/expected.hpp index cf6e852..d7561ab 100644 --- a/include/asmgrader/common/expected.hpp +++ b/include/asmgrader/common/expected.hpp @@ -160,6 +160,7 @@ class [[nodiscard]] Expected } template + requires(!std::is_void_v) constexpr Expected, E> transform(const Func& func) { if (!has_value()) { return error(); @@ -168,6 +169,16 @@ class [[nodiscard]] Expected return func(value()); } + template + requires(std::is_void_v) + constexpr Expected, E> transform(const Func& func) { + if (!has_value()) { + return error(); + } + + return func(); + } + private: template struct ExpectedData diff --git a/include/asmgrader/subprocess/subprocess.hpp b/include/asmgrader/subprocess/subprocess.hpp index 69fdc63..ec8c940 100644 --- a/include/asmgrader/subprocess/subprocess.hpp +++ b/include/asmgrader/subprocess/subprocess.hpp @@ -1,10 +1,13 @@ #pragma once +#include #include #include #include #include +#include + #include #include #include @@ -28,17 +31,35 @@ class Subprocess : NonCopyable Subprocess(Subprocess&&) noexcept; Subprocess& operator=(Subprocess&&) noexcept; + /// Which output file descriptor to read from + enum class WhichOutput : u8 { None = 0, Stdout = 1, Stderr = 2, StdoutAndStderr = Stdout | Stderr }; + + /// stdout_str is only valid if WhichOutput::Stdout was included in the request + /// stderr_str is only valid if WhichOutput::Stderr was included in the request + struct OutputResult + { + std::string stdout_str; + std::string stderr_str; + }; + + /// Read buffered output since the last call to this function + OutputResult read_output(WhichOutput which = WhichOutput::StdoutAndStderr); + + /// Get all output since the program has launched + OutputResult read_full_output(WhichOutput which = WhichOutput::StdoutAndStderr); + template - Result read_stdout(const std::chrono::duration& timeout) { - return read_stdout_poll_impl(std::chrono::duration_cast(timeout).count()); + [[deprecated]] Result read_stdout(const std::chrono::duration& timeout) { + read_pipe_poll(stdout_, std::chrono::duration_cast(timeout).count()); + return new_output(WhichOutput::Stdout).stdout_str; } - Result read_stdout(); + /// Updates output result based on cursor positions + /// If the cursor is located before the end of the string, then a substring is used + /// (starting at the cursor position) + void get_new_output(OutputResult& res); - /// Get all stdout since the program has launched - const std::string& get_full_stdout(); - - Result send_stdin(std::string_view str); + Result send_stdin(std::string_view str) const; // Forks the current process to start a new subprocess as specified virtual Result start(); @@ -78,12 +99,20 @@ class Subprocess : NonCopyable /// pipes to communicate with subprocess' stdout and stdin respectively /// The parent process will only make use of the write end of stdin_pipe_, and the read end of stdout_pipe_ linux::Pipe stdin_pipe_{}; - linux::Pipe stdout_pipe_{}; - std::string stdout_buffer_; - std::size_t stdout_cursor_{}; + struct OutputPipe + { + linux::Pipe pipe; + std::string buffer; + std::size_t cursor; + }; - Result read_stdout_poll_impl(int timeout_ms); + OutputPipe stdout_{}; + OutputPipe stderr_{}; + + /// Marks all open fds (other than 0,1,2) as FD_CLOEXEC so that they get closed in the child proc + /// Run in the PARENT process. + Expected<> mark_cloexec_all() const; /// Marks all open fds (other than 0,1,2) as FD_CLOEXEC so that they get closed in the child proc /// Run in the PARENT process. @@ -92,10 +121,49 @@ class Subprocess : NonCopyable /// Reads any data on the stdout pipe to stdout_buffer_ Result read_stdout_impl(); + /// Reads any immediately available data available on the specified pipe's read end + /// Writes any new data to that OutputPipe's buffer + /// Throws a std::logic_error if any syscalls fail + static void read_pipe_nonblock(OutputPipe& pipe); + + /// Polls the pipe's read end for timeout_ms millis, or until available input arrives + /// Writes any new data to that OutputPipe's buffer + /// \returns true if a successful read occurred, false if timed out + static bool read_pipe_poll(OutputPipe& pipe, int timeout_ms); + + /// Obtain new output based on cursor positions and buffers, + /// and set cursors to the end of their resp. buffers + OutputResult new_output(WhichOutput which); + std::optional exit_code_; std::string exec_; std::vector args_; }; +// TODO: macro for bitfield enums + +constexpr std::string_view format_as(const Subprocess::WhichOutput& from) { + switch (from) { + case Subprocess::WhichOutput::None: + return "none"; + case Subprocess::WhichOutput::Stdout: + return "stdout"; + case Subprocess::WhichOutput::Stderr: + return "stderr"; + case Subprocess::WhichOutput::StdoutAndStderr: + return "stdout&stderr"; + default: + return ""; + } +} + +constexpr Subprocess::WhichOutput operator&(const Subprocess::WhichOutput& lhs, const Subprocess::WhichOutput& rhs) { + return static_cast(fmt::underlying(lhs) & fmt::underlying(rhs)); +} + +constexpr Subprocess::WhichOutput operator|(const Subprocess::WhichOutput& lhs, const Subprocess::WhichOutput& rhs) { + return static_cast(fmt::underlying(lhs) | fmt::underlying(rhs)); +} + } // namespace asmgrader diff --git a/src/api/test_context.cpp b/src/api/test_context.cpp index af73d1d..7404412 100644 --- a/src/api/test_context.cpp +++ b/src/api/test_context.cpp @@ -17,6 +17,7 @@ #include "logging.hpp" #include "program/program.hpp" #include "subprocess/run_result.hpp" +#include "subprocess/subprocess.hpp" #include "subprocess/syscall_record.hpp" #include @@ -83,11 +84,19 @@ std::string_view TestContext::get_name() const { } std::string TestContext::get_stdout() { - return TRY_OR_THROW(prog_.get_subproc().read_stdout(), "failed to read stdout"); + return get_output(Subprocess::WhichOutput::Stdout).stdout_str; } std::string TestContext::get_full_stdout() { - return prog_.get_subproc().get_full_stdout(); + return get_full_output(Subprocess::WhichOutput::Stdout).stdout_str; +} + +Subprocess::OutputResult TestContext::get_output(Subprocess::WhichOutput which) { + return prog_.get_subproc().read_output(which); +} + +Subprocess::OutputResult TestContext::get_full_output(Subprocess::WhichOutput which) { + return prog_.get_subproc().read_full_output(which); } void TestContext::send_stdin(std::string_view input) { diff --git a/src/subprocess/subprocess.cpp b/src/subprocess/subprocess.cpp index 260ded8..1511352 100644 --- a/src/subprocess/subprocess.cpp +++ b/src/subprocess/subprocess.cpp @@ -9,16 +9,16 @@ #include #include -#include #include -#include #include #include #include +#include #include #include #include #include +#include #include #include @@ -129,15 +129,20 @@ Result Subprocess::wait_for_exit(std::chrono::microseconds timeout) { Result Subprocess::close_pipes() { // Make sure all available data is read before pipes are closed - std::ignore = read_stdout_impl(); + read_pipe_nonblock(stdout_); + read_pipe_nonblock(stderr_); if (stdin_pipe_.write_fd != -1) { TRYE(linux::close(stdin_pipe_.write_fd), SyscallFailure); stdin_pipe_.write_fd = -1; } - if (stdout_pipe_.read_fd != -1) { - TRYE(linux::close(stdout_pipe_.read_fd), SyscallFailure); - stdout_pipe_.read_fd = -1; + if (stdout_.pipe.read_fd != -1) { + TRYE(linux::close(stdout_.pipe.read_fd), SyscallFailure); + stdout_.pipe.read_fd = -1; + } + if (stderr_.pipe.read_fd != -1) { + TRYE(linux::close(stderr_.pipe.read_fd), SyscallFailure); + stderr_.pipe.read_fd = -1; } return {}; @@ -146,16 +151,14 @@ Result Subprocess::close_pipes() { Subprocess::Subprocess(Subprocess&& other) noexcept : child_pid_{std::exchange(other.child_pid_, 0)} , stdin_pipe_{std::exchange(other.stdin_pipe_, {})} - , stdout_pipe_{std::exchange(other.stdout_pipe_, {})} - , stdout_buffer_{std::exchange(other.stdout_buffer_, {})} - , stdout_cursor_{std::exchange(other.stdout_cursor_, 0)} {} + , stdout_{std::exchange(other.stdout_, {})} + , stderr_{std::exchange(other.stderr_, {})} {} Subprocess& Subprocess::operator=(Subprocess&& rhs) noexcept { child_pid_ = std::exchange(rhs.child_pid_, 0); stdin_pipe_ = std::exchange(rhs.stdin_pipe_, {}); - stdout_pipe_ = std::exchange(rhs.stdout_pipe_, {}); - stdout_buffer_ = std::exchange(rhs.stdout_buffer_, {}); - stdout_cursor_ = std::exchange(rhs.stdout_cursor_, 0); + stdout_ = std::exchange(rhs.stdout_, {}); + stderr_ = std::exchange(rhs.stderr_, {}); return *this; } @@ -164,72 +167,89 @@ bool Subprocess::is_alive() const { return linux::kill(child_pid_, 0) != std::make_error_code(std::errc::no_such_process); } -Result Subprocess::read_stdout_poll_impl(int timeout_ms) { - // If the pipe is already closed, all we can do is try reading from the buffer - if (stdout_pipe_.read_fd == -1) { - return read_stdout(); +Subprocess::OutputResult Subprocess::read_output(WhichOutput which) { + if ((which & WhichOutput::Stdout) != WhichOutput::None) { + read_pipe_nonblock(stdout_); + } + if ((which & WhichOutput::Stderr) != WhichOutput::None) { + read_pipe_nonblock(stderr_); } - struct pollfd poll_struct = {.fd = stdout_pipe_.read_fd, .events = POLLIN, .revents = 0}; + return new_output(which); +} - // TODO: Create wrapper in linux.hpp - int res = poll(&poll_struct, 1, timeout_ms); - // Error - if (res == -1) { - LOG_WARN("Error polling for read from stdout pipe: '{}'", get_err_msg()); - return ErrorKind::SyscallFailure; +Subprocess::OutputResult Subprocess::read_full_output(WhichOutput which) { + if ((which & WhichOutput::Stdout) != WhichOutput::None) { + read_pipe_nonblock(stdout_); } - // Timeout occured - if (res == 0) { - return ""; + if ((which & WhichOutput::Stderr) != WhichOutput::None) { + read_pipe_nonblock(stderr_); } - return read_stdout(); + return OutputResult{.stdout_str = stdout_.buffer, .stderr_str = stderr_.buffer}; } -Result Subprocess::read_stdout() { - TRY(read_stdout_impl()); +void Subprocess::read_pipe_nonblock(OutputPipe& pipe) { + std::size_t num_bytes_avail = 0; - // Cursor is still at the end of the buffer -> no data was read - if (stdout_cursor_ == stdout_buffer_.size()) { - return ""; + if (pipe.pipe.read_fd == -1) { + LOG_TRACE("Attempted to read from a fd that's already closed ({})", pipe.pipe.read_fd); + return; } - auto res = stdout_buffer_.substr(stdout_cursor_); - stdout_cursor_ = stdout_buffer_.size(); + if (!linux::ioctl(pipe.pipe.read_fd, FIONREAD, &num_bytes_avail)) { + throw std::logic_error("ioctl for pipe failed"); + } - return res; -} + LOG_DEBUG("{} bytes available from fd ({})", num_bytes_avail, pipe.pipe.read_fd); -const std::string& Subprocess::get_full_stdout() { - std::ignore = read_stdout_impl(); + if (num_bytes_avail == 0) { + return; + } - return stdout_buffer_; + if (auto res = linux::read(pipe.pipe.read_fd, num_bytes_avail)) { + pipe.buffer += res.value(); + } else { + throw std::logic_error("read from pipe failed"); + } } -Result Subprocess::read_stdout_impl() { - std::size_t num_bytes_avail = 0; +bool Subprocess::read_pipe_poll(Subprocess::OutputPipe& pipe, int timeout_ms) { + struct pollfd poll_struct = {.fd = pipe.pipe.read_fd, .events = POLLIN, .revents = 0}; - if (stdout_pipe_.read_fd == -1) { - return {}; + // TODO: Create wrapper in linux.hpp + int res = poll(&poll_struct, 1, timeout_ms); + // Syscall error + if (res == -1) { + throw std::logic_error("poll for pipe failed"); + } + // Timeout occured + if (res == 0) { + return false; } - TRYE(linux::ioctl(stdout_pipe_.read_fd, FIONREAD, &num_bytes_avail), SyscallFailure); - - LOG_DEBUG("{} bytes available from stdout_pipe", num_bytes_avail); + read_pipe_nonblock(pipe); - if (num_bytes_avail == 0) { - return {}; - } + return true; +} - std::string res = TRYE(linux::read(stdout_pipe_.read_fd, num_bytes_avail), SyscallFailure); +Subprocess::OutputResult Subprocess::new_output(WhichOutput which) { + OutputResult res; - stdout_buffer_ += res; + // Update res and cursor positions + if ((which & WhichOutput::Stdout) != WhichOutput::None && stdout_.cursor < stdout_.buffer.size()) { + res.stdout_str = stdout_.buffer.substr(stdout_.cursor); + stdout_.cursor = stdout_.buffer.size(); + } + if ((which & WhichOutput::Stderr) != WhichOutput::None && stderr_.cursor < stderr_.buffer.size()) { + res.stderr_str = stderr_.buffer.substr(stderr_.cursor); + stderr_.cursor = stderr_.buffer.size(); + } - return {}; + return res; } -Result Subprocess::send_stdin(std::string_view str) { +Result Subprocess::send_stdin(std::string_view str) const { // TODO: more abstract write wrapper that ensures all bytes were sent TRYE(linux::write(stdin_pipe_.write_fd, str), SyscallFailure); @@ -237,8 +257,13 @@ Result Subprocess::send_stdin(std::string_view str) { } Result Subprocess::create(const std::string& exec, const std::vector& args) { - stdout_pipe_ = TRYE(linux::pipe2(), SyscallFailure); stdin_pipe_ = TRYE(linux::pipe2(), SyscallFailure); + stdout_.pipe = TRYE(linux::pipe2(), SyscallFailure); + stderr_.pipe = TRYE(linux::pipe2(), SyscallFailure); + + if (!mark_cloexec_all()) { + LOG_WARN("Failed to set flags for fds; some fds will likely remain open in child proc"); + } if (!mark_cloexec_all()) { LOG_WARN("Failed to set flags for fds; some fds will likely remain open in child proc"); @@ -264,13 +289,33 @@ Result Subprocess::create(const std::string& exec, const std::vector Subprocess::init_child() { TRYE(linux::dup2(stdin_pipe_.read_fd, STDIN_FILENO), SyscallFailure); - TRYE(linux::dup2(stdout_pipe_.write_fd, STDOUT_FILENO), SyscallFailure); + TRYE(linux::dup2(stdout_.pipe.write_fd, STDOUT_FILENO), SyscallFailure); + TRYE(linux::dup2(stderr_.pipe.write_fd, STDERR_FILENO), SyscallFailure); // Close the pipe ends not being used in the child proc // - read end for stdout - // - write end for stdin + // - write end for stdin and stderr TRYE(linux::close(stdin_pipe_.write_fd), SyscallFailure); - TRYE(linux::close(stdout_pipe_.read_fd), SyscallFailure); + TRYE(linux::close(stdout_.pipe.read_fd), SyscallFailure); + TRYE(linux::close(stderr_.pipe.read_fd), SyscallFailure); + + namespace fs = std::filesystem; + + for (const auto& entry : fs::directory_iterator("/proc/self/fd")) { + int fd = std::stoi(entry.path().filename().string()); + + // skip stdin, stdout, stderr + if (fd <= 2) { + continue; + } + + // auto res = linux::close(fd); + // + // // If close(2) failed for a reason other than the fd not existing, return an error + // if (!res && res != linux::make_error_code(EBADF)) { + // return ErrorKind::SyscallFailure; + // } + } namespace fs = std::filesystem; @@ -296,16 +341,20 @@ Result Subprocess::init_child() { Result Subprocess::init_parent() { // Close the pipe ends being used in the parent proc // - write end for stdout - // - read end for stdin + // - read end for stdin and stderr TRYE(linux::close(stdin_pipe_.read_fd), SyscallFailure); - TRYE(linux::close(stdout_pipe_.write_fd), SyscallFailure); + TRYE(linux::close(stdout_.pipe.write_fd), SyscallFailure); + TRYE(linux::close(stderr_.pipe.write_fd), SyscallFailure); // stdin_pipefd_ = stdin_pipe.write_fd; // write end of stdin pipe // stdout_pipefd_ = stdout_pipe.read_fd; // read end of stdout pipe - // Make reading from stdout non-blocking - int pre_flags = TRYE(linux::fcntl(stdout_pipe_.read_fd, F_GETFL), SyscallFailure); + // Make reading from stdout and stderr non-blocking + int pre_flags_stdout = TRYE(linux::fcntl(stdout_.pipe.read_fd, F_GETFL), SyscallFailure); + int pre_flags_stderr = TRYE(linux::fcntl(stderr_.pipe.read_fd, F_GETFL), SyscallFailure); - TRYE(linux::fcntl(stdout_pipe_.read_fd, F_SETFL, pre_flags | O_NONBLOCK), // NOLINT + TRYE(linux::fcntl(stdout_.pipe.read_fd, F_SETFL, pre_flags_stdout | O_NONBLOCK), // NOLINT + SyscallFailure); + TRYE(linux::fcntl(stderr_.pipe.read_fd, F_SETFL, pre_flags_stderr | O_NONBLOCK), // NOLINT SyscallFailure); return {}; diff --git a/tests/resources/simple_asm_aarch64.s b/tests/resources/simple_asm_aarch64.s index 29d1c80..12c191d 100644 --- a/tests/resources/simple_asm_aarch64.s +++ b/tests/resources/simple_asm_aarch64.s @@ -32,7 +32,7 @@ sum: /// x0 + x1 written to stdout as 8 bytes. sum_and_write: add x0, x0, x1 // x0 += x1 - STR x0, [sp, -8]! + str x0, [sp, -8]! // save x0 onto the stack mov x8, 64 // SYS_write mov x0, 1 // fd param = stdout @@ -43,6 +43,26 @@ sum_and_write: add sp, sp, 8 // pop x0 off of the stack ret + +/// write_to +/// sums two numbers and writes the result to stdout. Overflow may occur. +/// Parameters: +/// x0 (const char*) - string to write +/// x1 (int) - the fd to write to +/// x2 (size_t) - length of the string +/// Result: +/// length bytes of string written to fd +write_to: + mov x3, x0 // save x0 (str) into x3 + + mov x8, 64 // SYS_write + mov x0, x1 // fd param = fd + mov x1, x3 // str param = string + // len param [already set by param] + svc 0 // SYS_write + + ret + /// This subroutine will timeout in an infinitely recurring loop timeout_fn: b timeout_fn diff --git a/tests/resources/simple_asm_x86_64.s b/tests/resources/simple_asm_x86_64.s index 7f3eb61..8473bcd 100644 --- a/tests/resources/simple_asm_x86_64.s +++ b/tests/resources/simple_asm_x86_64.s @@ -13,7 +13,6 @@ _start: mov rdi, 42 # retcode syscall - /// sum /// sums two numbers and returns the result /// Parameters: @@ -46,6 +45,26 @@ sum_and_write: pop rsi # pop rsi off the stack ret +/// write_to +/// sums two numbers and writes the result to specified fd. Overflow may occur. +/// Parameters: +/// rdi (const char*) - string to write +/// rsi (int) - the fd to write to +/// rdx (size_t) - length of the string +/// Result: +/// length bytes of string written to fd +write_to: + mov r10, rdi # save rdi (str) into r10 + + mov rax, 1 # SYS_write + mov rdi, rsi # fd + mov rsi, r10 # rsi = str + mov rdx, rdx # rdx (len) = length + syscall # SYS_write + + ret + + /// This subroutine will timeout in an infinitely recurring loop timeout_fn: jmp timeout_fn diff --git a/tests/test_program.cpp b/tests/test_program.cpp index d27d884..e85d5cb 100644 --- a/tests/test_program.cpp +++ b/tests/test_program.cpp @@ -3,14 +3,18 @@ #include "common/aliases.hpp" #include "common/error_types.hpp" #include "program/program.hpp" +#include "subprocess/subprocess.hpp" #include #include +#include + using namespace asmgrader::aliases; using sum = u64(std::uint64_t, std::uint64_t); using sum_and_write = void(u64, std::uint64_t); +using write_to = void(const char*, int, size_t); using timeout_fn = void(); using segfaulting_fn = void(); using exiting_fn = void(u64); @@ -46,16 +50,34 @@ TEST_CASE("Call sum_and_write function") { asmgrader::Program prog(ASM_TESTS_EXEC, {}); REQUIRE(prog.call_function("sum_and_write", 0, 0)); - REQUIRE(prog.get_subproc().read_stdout() == std::string{"\0\0\0\0\0\0\0\0", 8}); + REQUIRE(prog.get_subproc().read_output(asmgrader::Subprocess::WhichOutput::Stdout).stdout_str == + std::string{"\0\0\0\0\0\0\0\0", 8}); REQUIRE(prog.call_function("sum_and_write", 'a', 5)); // 'a' + 5 = 'f' - REQUIRE(prog.get_subproc().read_stdout() == std::string{"f\0\0\0\0\0\0\0", 8}); + REQUIRE(prog.get_subproc().read_output(asmgrader::Subprocess::WhichOutput::Stdout).stdout_str == + std::string{"f\0\0\0\0\0\0\0", 8}); REQUIRE(prog.call_function("sum_and_write", 0x1010101010101010, 0x1010101010101010)); static_assert(' ' == 0x10 + 0x10, "Somehow not ASCII encoded???"); // 0x10 + 0x10 = 0x20 (space ' ') - REQUIRE(prog.get_subproc().read_stdout() == " "); + REQUIRE(prog.get_subproc().read_output(asmgrader::Subprocess::WhichOutput::Stdout).stdout_str == " "); +} + +TEST_CASE("Call write_to function") { + asmgrader::Program prog(ASM_TESTS_EXEC, {}); + + std::string test_str = "I am a test string\nNOPE\n123897g51%%~"; + + REQUIRE(prog.call_function("write_to", test_str, STDOUT_FILENO, test_str.size())); + REQUIRE(prog.get_subproc().read_output().stdout_str == test_str); + + REQUIRE(prog.call_function("write_to", test_str, STDERR_FILENO, test_str.size())); + REQUIRE(prog.get_subproc().read_output().stderr_str == test_str); + + REQUIRE(prog.call_function("write_to", test_str, 0, test_str.size())); + REQUIRE(prog.get_subproc().read_output().stdout_str == ""); + REQUIRE(prog.get_subproc().read_output().stderr_str == ""); } TEST_CASE("Test that timeouts are handled properly with timeout_fn") { diff --git a/tests/test_subprocess.cpp b/tests/test_subprocess.cpp index b8fdf7d..22e0086 100644 --- a/tests/test_subprocess.cpp +++ b/tests/test_subprocess.cpp @@ -40,7 +40,7 @@ TEST_CASE("Read /bin/echo stdout") { proc.wait_for_exit(); - REQUIRE(proc.read_stdout() == "Hello world!"); + REQUIRE(proc.read_output(asmgrader::Subprocess::WhichOutput::Stdout).stdout_str == "Hello world!"); } TEST_CASE("Interact with /bin/cat") { @@ -68,7 +68,7 @@ TEST_CASE("Get results of asm program") { REQUIRE(run_res->get_code() == 42); REQUIRE(proc.get_exit_code() == 42); - REQUIRE(proc.read_stdout() == "Hello, from assembly!\n"); + REQUIRE(proc.read_output(asmgrader::Subprocess::WhichOutput::Stdout).stdout_str == "Hello, from assembly!\n"); auto syscall_records = proc.get_tracer().get_records(); From 8c7ca30d7c4d841b08e8583931238760fe3f4378 Mon Sep 17 00:00:00 2001 From: Matt <38063055+Terracom12@users.noreply.github.com> Date: Sun, 26 Oct 2025 11:20:35 -0700 Subject: [PATCH 07/41] fix: enable -Werror and fix all compiler warnings (#66) * feat: add very basic temporary file API * fix: ensure open fds are marked as FD_CLOEXEC in parent proc * chore: refactor run result * feat: add api for process statistics and minor api and logging tweaks * feat: add TempFile::remove * add str impl for RunResult * add more functionality to tempfile api; various fixes and logging improvements * add Expected::transform specialization for when T is void * capture stderr with Subprocess; update API accordingly * add test for reading from captured stderr * add api for reading from stderr to test_context * fix aarch64 test asm program * ASMGRADER_WARNINGS_AS_ERRORS now sets -Werror properly * fix: deprecation warnings (gcc) * fix: sign conversion warning (clang) * fix: unused variable/param warnings (clang) * fix: unused lambda capture (clang) * fix: millis chrono type too large for poll(2) * fix: brace-elision extension in init-list (clang) * fix: ctor field init ordering (clang) * fix: parentheses suggested in decomposition expr (gcc) * link warnings to test execs; fix various occurences * workaround gcc bug (?) where warnings are emitted for system headers --- cmake/CompilerWarnings.cmake | 22 ++++++++++++++----- cmake/ProjectOptions.cmake | 1 + cmake/pch_preamble.hpp | 10 +++++++++ include/asmgrader/api/asm_function.hpp | 2 +- .../asmgrader/api/expression_inspection.hpp | 14 +++++++++--- include/asmgrader/api/requirement.hpp | 6 ++--- include/asmgrader/api/test_macros.hpp | 8 +++---- include/asmgrader/common/byte_vector.hpp | 2 +- .../asmgrader/common/formatters/aggregate.hpp | 2 +- include/asmgrader/common/formatters/enum.hpp | 2 ++ include/asmgrader/subprocess/subprocess.hpp | 14 +++++++----- src/api/test_context.cpp | 2 +- tests/CMakeLists.txt | 6 +++++ tests/dumb_assignment.cpp | 22 +++++++++---------- tests/test_subprocess.cpp | 6 +++++ 15 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 cmake/pch_preamble.hpp diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake index 85959c9..be63659 100644 --- a/cmake/CompilerWarnings.cmake +++ b/cmake/CompilerWarnings.cmake @@ -34,6 +34,22 @@ function(asmgrader_set_target_warnings target_name WARNINGS_AS_ERRORS) -Wsuggest-override # warn if an overridden member function is not marked 'override' or 'final' ) + if(WARNINGS_AS_ERRORS) + message(TRACE "Warnings are treated as errors") + list(APPEND CLANG_WARNINGS -Werror) + + if(CMAKE_BUILD_TYPE MATCHES ".*Rel.*") + message(WARNING + "Disabling -Werror and removing -Wmaybe-uninitialized and -Wnull-dereference to work around gcc bug emitting warnings for system includes; " + "build in Debug mode to see these warnings enabled") + list(REMOVE_ITEM GCC_WARNINGS -Wmaybe-uninitialized -Wnull-dereference) + list(APPEND GCC_WARNINGS -Wno-maybe-uninitialized -Wno-null-dereference) + else() + list(APPEND GCC_WARNINGS -Werror) + endif() + + endif() + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") set(TARGET_WARNINGS_CXX ${CLANG_WARNINGS}) elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -42,12 +58,6 @@ function(asmgrader_set_target_warnings target_name WARNINGS_AS_ERRORS) message(AUTHOR_WARNING "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") endif() - if(WARNINGS_AS_ERRORS) - message(TRACE "Warnings are treated as errors") - list(APPEND CLANG_WARNINGS -Werror) - list(APPEND GCC_WARNINGS -Werror) - endif() - target_compile_options( ${target_name} INTERFACE diff --git a/cmake/ProjectOptions.cmake b/cmake/ProjectOptions.cmake index 4629206..0ebb408 100644 --- a/cmake/ProjectOptions.cmake +++ b/cmake/ProjectOptions.cmake @@ -156,6 +156,7 @@ macro(asmgrader_local_options) target_precompile_headers( asmgrader_options INTERFACE + "cmake/pch_preamble.hpp" diff --git a/cmake/pch_preamble.hpp b/cmake/pch_preamble.hpp new file mode 100644 index 0000000..de51618 --- /dev/null +++ b/cmake/pch_preamble.hpp @@ -0,0 +1,10 @@ +#pragma once + +#ifndef __clang__ + +// gcc has been giving some false positives for stdlib headers + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + +#endif // ! __clang__ diff --git a/include/asmgrader/api/asm_function.hpp b/include/asmgrader/api/asm_function.hpp index 23224d2..b00d332 100644 --- a/include/asmgrader/api/asm_function.hpp +++ b/include/asmgrader/api/asm_function.hpp @@ -52,7 +52,7 @@ class AsmFunctionResult : public Result std::tuple...> args; std::string_view function_name; - std::string repr(std::span tokens, std::string_view raw_str) const { + std::string repr(std::span /*tokens*/, std::string_view /*raw_str*/) const { // auto split_arg_tokens_fn = [open_groupings = std::stack{}](const inspection::Token& tok) mutable { // using inspection::Token::Kind::Grouping; // diff --git a/include/asmgrader/api/expression_inspection.hpp b/include/asmgrader/api/expression_inspection.hpp index e72f686..bf79199 100644 --- a/include/asmgrader/api/expression_inspection.hpp +++ b/include/asmgrader/api/expression_inspection.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -1686,9 +1688,15 @@ class Tokenizer ranges::copy(tokens_.begin() + start, tokens_.begin() + start + len, result.tokens_.begin()); result.num_tokens_ = len; - auto str_start = result.tokens_.front().str.data() - original_.data(); - auto str_len = (result[len - 1].str.data() + result[len - 1].str.size()) - result.tokens_.front().str.data(); - result.original_ = original_.substr(str_start, str_len); + auto sum_token_lens = [](ranges::range auto&& rng) { + return ranges::fold_left( + rng | ranges::views::transform([](const Token& token) { return token.str.size(); }), 0, std::plus<>{}); + }; + + std::size_t removed_str_prefix = sum_token_lens(tokens_ | ranges::views::take(start)); + std::size_t str_len = sum_token_lens(result.tokens_); + + result.original_ = original_.substr(removed_str_prefix, str_len); return result; } diff --git a/include/asmgrader/api/requirement.hpp b/include/asmgrader/api/requirement.hpp index d5de1ba..51dbbbb 100644 --- a/include/asmgrader/api/requirement.hpp +++ b/include/asmgrader/api/requirement.hpp @@ -241,8 +241,8 @@ class Requirement explicit Requirement(Op op, std::string description) : op_{op} - , description_{std::move(description)} - , res_{op.eval()} {} + , res_{op.eval()} + , description_{std::move(description)} {} std::string get_description() const { return description_; } @@ -266,7 +266,7 @@ class Requirement return exprs::ExpressionRepr{.expression = make_expr_value(std::get<0>(op_.args), arg0_str)}; } else { using ResultT = decltype(res_); - exprs::ExpressionRepr::Repr repr{.repr = "==", // + exprs::ExpressionRepr::Repr repr{.repr = {"=="}, // .str = stringize::str(res_), .raw_str = Op::raw_str, .raw_str_tokens = diff --git a/include/asmgrader/api/test_macros.hpp b/include/asmgrader/api/test_macros.hpp index 5633624..ce62c24 100644 --- a/include/asmgrader/api/test_macros.hpp +++ b/include/asmgrader/api/test_macros.hpp @@ -53,12 +53,12 @@ #endif // PROFESSOR_VERSION #define REQUIRE_IMPL(unq_ident, condition, condition_raw_str, ...) \ - do { /* NOLINT(cppcoreguidelines-avoid-do-while) */ \ - /*NOLINTNEXTLINE(bugprone-chained-comparison)*/ \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wparentheses\"") /*NOLINTNEXTLINE(bugprone-chained-comparison)*/ \ const auto& unq_ident = ::asmgrader::Requirement{asmgrader::Decomposer{} <= condition, \ {condition_raw_str} __VA_OPT__(, ONLY_FIRST(__VA_ARGS__))}; \ - bool CONCAT(bool_, unq_ident) = ctx.require(unq_ident); \ - } while (false); + _Pragma("GCC diagnostic pop") ctx.require(unq_ident) /*premits usage in `if` \ + conditionals*/ #define REQUIRE(condition, ...) REQUIRE_IMPL(CONCAT(require_unq_, __COUNTER__), condition, #condition, __VA_ARGS__) diff --git a/include/asmgrader/common/byte_vector.hpp b/include/asmgrader/common/byte_vector.hpp index ff406a2..31b1631 100644 --- a/include/asmgrader/common/byte_vector.hpp +++ b/include/asmgrader/common/byte_vector.hpp @@ -132,7 +132,7 @@ class ByteVector std::tuple...> bytes; std::apply( - [&bytes, iter = begin()](auto&&... elems) mutable { + [iter = begin()](auto&&... elems) mutable { ((ranges::copy_n(std::exchange(iter, iter + sizeof(Types)), sizeof(Types), elems.begin())), ...); }, bytes); diff --git a/include/asmgrader/common/formatters/aggregate.hpp b/include/asmgrader/common/formatters/aggregate.hpp index 18711be..989f86e 100644 --- a/include/asmgrader/common/formatters/aggregate.hpp +++ b/include/asmgrader/common/formatters/aggregate.hpp @@ -63,7 +63,7 @@ struct AggregateFormatter const std::tuple fields{get_named_fields(from)}; // Transform fields to printable output` - auto field_writer = [&, this, first = true](const auto& pair) mutable { + auto field_writer = [&, first = true](const auto& pair) mutable { if (!first) { ranges::copy(sep, ctx_iter); } diff --git a/include/asmgrader/common/formatters/enum.hpp b/include/asmgrader/common/formatters/enum.hpp index 2f1c587..a70fccf 100644 --- a/include/asmgrader/common/formatters/enum.hpp +++ b/include/asmgrader/common/formatters/enum.hpp @@ -38,6 +38,8 @@ struct EnumFormatter constexpr auto get_enumerator(const Enum& from) const { auto val = [from] { + // from is unused when the list of enumerators is empty + (void)from; // [[maybe_unused]] is unsupported in capture list std::optional<::asmgrader::pair> res; ((Enumerators.second == from ? res = Enumerators : res), ...); return res; diff --git a/include/asmgrader/subprocess/subprocess.hpp b/include/asmgrader/subprocess/subprocess.hpp index ec8c940..33c64ce 100644 --- a/include/asmgrader/subprocess/subprocess.hpp +++ b/include/asmgrader/subprocess/subprocess.hpp @@ -7,16 +7,18 @@ #include #include +#include +#include #include #include +#include #include #include #include #include #include -#include #include namespace asmgrader { @@ -50,7 +52,11 @@ class Subprocess : NonCopyable template [[deprecated]] Result read_stdout(const std::chrono::duration& timeout) { - read_pipe_poll(stdout_, std::chrono::duration_cast(timeout).count()); + auto millis = std::chrono::duration_cast(timeout).count(); + + ASSERT(millis <= std::numeric_limits::max(), "poll(2) is defined to accept an int parameter for millis"); + + read_pipe_poll(stdout_, gsl::narrow_cast(millis)); return new_output(WhichOutput::Stdout).stdout_str; } @@ -114,10 +120,6 @@ class Subprocess : NonCopyable /// Run in the PARENT process. Expected<> mark_cloexec_all() const; - /// Marks all open fds (other than 0,1,2) as FD_CLOEXEC so that they get closed in the child proc - /// Run in the PARENT process. - Expected<> mark_cloexec_all() const; - /// Reads any data on the stdout pipe to stdout_buffer_ Result read_stdout_impl(); diff --git a/src/api/test_context.cpp b/src/api/test_context.cpp index 7404412..9b20a38 100644 --- a/src/api/test_context.cpp +++ b/src/api/test_context.cpp @@ -72,7 +72,7 @@ TestResult TestContext::finalize() { } bool TestContext::require(bool condition, RequirementResult::DebugInfo debug_info) { - return require(condition, "", debug_info); + return require_impl(condition, "", std::nullopt, debug_info); } bool TestContext::require(bool condition, const std::string& msg, RequirementResult::DebugInfo debug_info) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f2af051..0646c74 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -75,6 +75,7 @@ target_link_libraries( PRIVATE Catch2::Catch2 asmgrader_core_interface + asmgrader_warnings ) # TODO: Migh want to look into changing this later. @@ -94,6 +95,7 @@ target_link_libraries( PRIVATE Catch2::Catch2WithMain asmgrader_core_interface + asmgrader_warnings ) set(asm_tests_weird_name_exec "${CMAKE_CURRENT_BINARY_DIR}/(asm_tests () weird \\(name) ~`") @@ -129,8 +131,10 @@ target_compile_definitions( ) target_link_libraries( asmgrader_dumb_cli + PRIVATE asmgrader_core_with_main + asmgrader_warnings ) add_executable( @@ -146,8 +150,10 @@ target_compile_definitions( ) target_link_libraries( asmgrader_dumb_profcli + PRIVATE asmgrader_prof_core_with_main + asmgrader_warnings ) # Provide a simple smoke test to make sure that the CLI works and can display a --help message diff --git a/tests/dumb_assignment.cpp b/tests/dumb_assignment.cpp index c2e3932..8af644e 100644 --- a/tests/dumb_assignment.cpp +++ b/tests/dumb_assignment.cpp @@ -14,30 +14,30 @@ TEST("sum function") { AsmFunction sum = ctx.find_function("putch"); AsmFunction sum_and_write = ctx.find_function("sum_and_write"); - REQUIRE(sum(0, 0) == 0); - REQUIRE(sum(1, 1) == 2); - REQUIRE(sum(10000, 9999) == 19999); + REQUIRE(sum(0, 0) == 0u, "0 + 0 = 0"); + REQUIRE(sum(1, 1) == 2u, "1 + 1 = 2"); + REQUIRE(sum(10000, 9999) == 19999u, "10000 + 9999 = 19999"); - REQUIRE(sum_and_write(0xAB, 0xCD)); - REQUIRE(ctx.get_stdout() == "\xAB\0\0\0\0\0\0\0\0\xCD\0\0\0\0\0\0\0\0"); + REQUIRE(sum_and_write(0xAB, 0xCD), "function runs!"); + REQUIRE(ctx.get_stdout() == "\xAB\0\0\0\0\0\0\0\0\xCD\0\0\0\0\0\0\0\0", "stdout's cool"); - REQUIRE(sum_and_write(0x0123456789ABCDEF, 0xFEDCBA9876543210)); - REQUIRE(ctx.get_stdout() == "\x01\x23\x45\x67\x89\xAB\xCD\xEF\xFE\xDC\xBA\x98\x76\x54\x32\x10"); + REQUIRE(sum_and_write(0x0123456789ABCDEF, 0xFEDCBA9876543210), "function runs!"); + REQUIRE(ctx.get_stdout() == "\x01\x23\x45\x67\x89\xAB\xCD\xEF\xFE\xDC\xBA\x98\x76\x54\x32\x10", "stdout's cool"); auto syscalls = ctx.get_syscall_records(); - REQUIRE(syscalls.size() == 2); + REQUIRE(syscalls.size() == 2u, "2 syscalls!"); } TEST("exiting_fn") { AsmFunction exiting_fn = ctx.find_function("putch"); - REQUIRE(exiting_fn(0) == ErrorKind::UnexpectedReturn); + REQUIRE(exiting_fn(0) == ErrorKind::UnexpectedReturn, "We exited"); } TEST("symbols") { AsmSymbol strHello = ctx.find_symbol("strHello"); AsmSymbol strGoodbye = ctx.find_symbol("strGoodbye"); - REQUIRE(*strHello == "Hello, from assembly!\n"); - REQUIRE(*strGoodbye == "Goodbye, :(\n"); + REQUIRE(*strHello == "Hello, from assembly!\n", "symbol check"); + REQUIRE(*strGoodbye == "Goodbye, :(\n", "~cymbal~ symbol check!"); } diff --git a/tests/test_subprocess.cpp b/tests/test_subprocess.cpp index 22e0086..4521ee9 100644 --- a/tests/test_subprocess.cpp +++ b/tests/test_subprocess.cpp @@ -43,6 +43,10 @@ TEST_CASE("Read /bin/echo stdout") { REQUIRE(proc.read_output(asmgrader::Subprocess::WhichOutput::Stdout).stdout_str == "Hello world!"); } +// rational: even though the function is deprecated, it should still be tested +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + TEST_CASE("Interact with /bin/cat") { using namespace std::chrono_literals; @@ -58,6 +62,8 @@ TEST_CASE("Interact with /bin/cat") { REQUIRE(proc.read_stdout(100ms) == "Du Du DUHHH"); } +#pragma GCC diagnostic pop + TEST_CASE("Get results of asm program") { asmgrader::TracedSubprocess proc(ASM_TESTS_EXEC, {}); REQUIRE(proc.start()); From aa813bc21c400b5bafcdd0afb6d619ed618c801c Mon Sep 17 00:00:00 2001 From: Matt <38063055+Terracom12@users.noreply.github.com> Date: Tue, 25 Nov 2025 23:00:23 -0800 Subject: [PATCH 08/41] feat: more version info (#67) * add cpptrace as dependency * reimplement version build info from scratch * add facilitaty to continue a program that timed out * set default students database location to be in same dir as executable * add parse_int function * add conversions.hpp to main header * fix asmsymbol to work with move-only types * split up version.hpp into source file * force cmake to rebuild version.cpp for up-to-date build timestamp --- CMakeLists.txt | 28 +-- cmake/ConfigureVersionInfo.cmake | 25 ++ cmake/SetupDependencies.cmake | 65 +++++- cmake/in/version_macros.hpp.in | 19 ++ include/asmgrader/api/asm_symbol.hpp | 2 +- include/asmgrader/api/test_context.hpp | 4 + include/asmgrader/app_mode.hpp | 39 ++++ include/asmgrader/asmgrader.hpp | 1 + include/asmgrader/common/conversions.hpp | 33 +++ include/asmgrader/grading_session.hpp | 38 +-- include/asmgrader/version.hpp | 124 ++++++++++ src/CMakeLists.txt | 11 + src/api/test_context.cpp | 7 + src/output/plaintext_serializer.cpp | 8 +- src/output/verbosity.hpp | 10 +- src/subprocess/subprocess.cpp | 6 + src/subprocess/tracer.cpp | 12 +- src/test_runner.cpp | 3 +- src/user/cl_args.cpp | 52 ++--- src/user/program_options.hpp | 15 +- src/version.cpp | 283 +++++++++++++++++++++++ src/version.hpp.in | 54 ----- 22 files changed, 671 insertions(+), 168 deletions(-) create mode 100644 cmake/ConfigureVersionInfo.cmake create mode 100644 cmake/in/version_macros.hpp.in create mode 100644 include/asmgrader/app_mode.hpp create mode 100644 include/asmgrader/common/conversions.hpp create mode 100644 include/asmgrader/version.hpp create mode 100644 src/version.cpp delete mode 100644 src/version.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index c7603ec..8702148 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,15 +31,24 @@ project(AsmGrader enable_language(ASM) -##### Project configuration source file -# For now, just substitutes version numbers as defined in root CMake project() decl - -configure_file(src/version.hpp.in asmgrader/version.hpp @ONLY) - # Set up CMake project options (ASMGRADER_* args) include(cmake/ProjectOptions.cmake) asmgrader_setup_options() + +asmgrader_global_options() + +# Set up project dependencies +# CPM (CMake package manager) is used for easy dependency management +include(cmake/SetupDependencies.cmake) +asmgrader_setup_dependencies() + +asmgrader_local_options() + +##### Project configuration source file +include(cmake/ConfigureVersionInfo.cmake) + + # Set up docs subproject, if option is specified if(${ASMGRADER_BUILD_DOCS}) include(cmake/Doxygen.cmake) @@ -54,15 +63,6 @@ if(${ASMGRADER_BUILD_DOCS}) endif() -asmgrader_global_options() - -# Set up project dependencies -# CPM (CMake package manager) is used for easy dependency management -include(cmake/SetupDependencies.cmake) -asmgrader_setup_dependencies() - -asmgrader_local_options() - add_subdirectory(src) include(CTest) diff --git a/cmake/ConfigureVersionInfo.cmake b/cmake/ConfigureVersionInfo.cmake new file mode 100644 index 0000000..9cbbd03 --- /dev/null +++ b/cmake/ConfigureVersionInfo.cmake @@ -0,0 +1,25 @@ +# Set up version info source and header files +# These include project version, git hash, and library versions (as available) +# Also includes parent "implementation" project's version, if defined + +function(asmgrader_get_dependency_versions outvar) + set(dep_versions "") + foreach(pkg IN LISTS CPM_PACKAGES) + if(DEFINED CPM_PACKAGE_${pkg}_VERSION) + list(APPEND dep_versions "${pkg} ${CPM_PACKAGE_${pkg}_VERSION}") + else() + list(APPEND dep_versions "${pkg} (version unknown)") + endif() + endforeach() + + # list(TRANSFORM dep_versions PREPEND [["]]) + # list(TRANSFORM dep_versions APPEND [["]]) + + list(JOIN dep_versions " " dep_versions_str) + + set(${outvar} ${dep_versions_str} PARENT_SCOPE) +endfunction() + +asmgrader_get_dependency_versions(ASMGRADER_DEPENDENCY_VERSIONS_STR) + +configure_file(cmake/in/version_macros.hpp.in asmgrader/version_macros.hpp) diff --git a/cmake/SetupDependencies.cmake b/cmake/SetupDependencies.cmake index 633fb60..ef985f0 100644 --- a/cmake/SetupDependencies.cmake +++ b/cmake/SetupDependencies.cmake @@ -60,18 +60,20 @@ macro(asmgrader_setup_dependencies) # fmt::fmt # spdlog # argparse - # Boost::endian - # Boost::type_index - # Boost::mp11 - # Boost::pfr - # Boost::describe - # Boost::stacktrace - # Boost::preprocessor + # Boost:: + # endian + # type_index + # mp11 + # pfr + # describe + # stacktrace + # preprocessor # range-v3 # Microsoft.GSL::GSL # nlohmann_json::nlohmann_json # elfio::elfio # Catch2::Catch2WithMain + # cpptrace::cpptrace # libassert::assert # For each dependency, see if it's @@ -156,7 +158,7 @@ macro(asmgrader_setup_dependencies) GITHUB_REPOSITORY "microsoft/GSL" VERSION 4.2.0 GIT_SHALLOW TRUE - OPTIONS "GSL_MSVC_STATIC_ANALYZER=OFF" + OPTIONS "GSL_MSVC_STATIC_ANALYZER OFF" SYSTEM TRUE ) _force_system_includes(Microsoft.GSL::GSL) @@ -177,17 +179,62 @@ macro(asmgrader_setup_dependencies) NAME elfio GITHUB_REPOSITORY "serge1/ELFIO" GIT_TAG Release_3.12 + VERSION 3.12 ) _force_system_includes(elfio::elfio) endif() + if(NOT TARGET zstd) + # zstd as a dependant of cpptrace + CPMAddPackage( + NAME zstd + GITHUB_REPOSITORY "facebook/zstd" + VERSION 1.5.7 + SOURCE_SUBDIR build/cmake + ) + _force_system_includes(zstd) + endif() + + + if(NOT TARGET libdwarf::dwarf) + # libdwarf as a dependant of cpptrace + CPMAddPackage( + NAME libdwarf + GITHUB_REPOSITORY "jeremy-rifkin/libdwarf-lite" + VERSION 2.1.0 + OPTIONS "BUILD_DWARFDUMP OFF" + ) + # _force_system_includes(libdwarf::dwarf) + endif() + + if(NOT TARGET cpptrace::cpptrace) + # cpptrace as a dependant of libassert and a useful library for nice backtraces + CPMAddPackage( + NAME cpptrace + GITHUB_REPOSITORY "jeremy-rifkin/cpptrace" + VERSION 1.0.4 + OPTIONS "CPPTRACE_UNWIND_WITH_EXECINFO ON" + "CPPTRACE_USE_EXTERNAL_LIBDWARF ON" + "CPPTRACE_USE_EXTERNAL_ZSTD ON" # this option does not seem to be properly documented + + ) + _force_system_includes(cpptrace::cpptrace) + endif() + + if(NOT TARGET libassert::assert) # libassert for fancy, overengineered assertions - CPMAddPackage("gh:jeremy-rifkin/libassert@2.2.1") + CPMAddPackage( + NAME libassert + GITHUB_REPOSITORY "jeremy-rifkin/libassert" + VERSION 2.2.1 + OPTIONS "LIBASSERT_USE_EXTERNAL_CPPTRACE ON" + ) _force_system_includes(libassert::assert) endif() + if(NOT TARGET Catch2::Catch2WithMain) CPMAddPackage("gh:catchorg/Catch2@3.8.1") _force_system_includes(Catch2::Catch2WithMain) diff --git a/cmake/in/version_macros.hpp.in b/cmake/in/version_macros.hpp.in new file mode 100644 index 0000000..12531d4 --- /dev/null +++ b/cmake/in/version_macros.hpp.in @@ -0,0 +1,19 @@ +#pragma once + +// IWYU pragma: private, include "version.hpp" + +// @ident@ expressions are substituted by CMake -- clang-format tries to reformat +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// clang-format off +#define ASMGRADER_VERSION_MAJOR @AsmGrader_VERSION_MAJOR@ +#define ASMGRADER_VERSION_MINOR @AsmGrader_VERSION_MINOR@ +#define ASMGRADER_VERSION_PATCH @AsmGrader_VERSION_PATCH@ +#define ASMGRADER_VERSION_GIT_HASH_STR "@ASMGRADER_VERSION_GIT_HASH@" +#define ASMGRADER_VERSION_BUILD_TYPE_STR "@CMAKE_BUILD_TYPE@" +/// A string for version info for a given implementation using this library +/// Should be of the form " " where the only ' ' (space) token is between and +/// Example: "CS150Grader 1.2.3" +#define ASMGRADER_IMPL_VERSION_INFO "@ASMGRADER_IMPL_VERSION_INFO@" +#define ASMGRADER_DEPENDENCY_VERSIONS_STR "@ASMGRADER_DEPENDENCY_VERSIONS_STR@" +// clang-format on +// NOLINTEND(cppcoreguidelines-macro-usage) diff --git a/include/asmgrader/api/asm_symbol.hpp b/include/asmgrader/api/asm_symbol.hpp index 8a142b7..76eeb5d 100644 --- a/include/asmgrader/api/asm_symbol.hpp +++ b/include/asmgrader/api/asm_symbol.hpp @@ -87,7 +87,7 @@ AsmSymbolResult AsmSymbol::get_value() const { } LOG_DEBUG("Read value {} for symbol {:?} @ 0x{:X}", val_str, name_, AsmData::get_address()); - res.set_result(value); + res.set_result(std::move(value)); return res; } diff --git a/include/asmgrader/api/test_context.hpp b/include/asmgrader/api/test_context.hpp index 260266a..3efb2c1 100644 --- a/include/asmgrader/api/test_context.hpp +++ b/include/asmgrader/api/test_context.hpp @@ -114,6 +114,10 @@ class TestContext /// Run the program normally from `_start`, stopping at the first exit(2) or exit_group(2) syscall invocation Result run(); + /// Send SIGCONT to subprocess in case it was stopped for any reason, + /// then execute \ref run + Result cont(); + /// Run the program from `_start`, stopping at the first syscall matching syscallnr /// OR the first exit(2) or exit_group(2) syscall invocation [whichever happens first] Result run_until(u64 syscallnr); diff --git a/include/asmgrader/app_mode.hpp b/include/asmgrader/app_mode.hpp new file mode 100644 index 0000000..ab06154 --- /dev/null +++ b/include/asmgrader/app_mode.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace asmgrader { + +enum AppMode { Student, Professor }; + +consteval AppMode get_builtin_app_mode() { +#ifdef PROFESSOR_VERSION + return AppMode::Professor; +#else + return AppMode::Student; +#endif +} + +// TODO: mode setting for prof +consteval bool is_student_mode() { + return get_builtin_app_mode() == AppMode::Student; +} + +// TODO: mode setting for prof +consteval bool is_prof_mode() { + return get_builtin_app_mode() == AppMode::Professor; +} + +constexpr std::string_view format_as(const AppMode& from) { + using enum AppMode; + switch (from) { + case Student: + return "Student"; + case Professor: + return "Professor"; + default: + return ""; + } +}; + +} // namespace asmgrader diff --git a/include/asmgrader/asmgrader.hpp b/include/asmgrader/asmgrader.hpp index 8b3f230..2778f1c 100644 --- a/include/asmgrader/asmgrader.hpp +++ b/include/asmgrader/asmgrader.hpp @@ -10,6 +10,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export +#include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export diff --git a/include/asmgrader/common/conversions.hpp b/include/asmgrader/common/conversions.hpp new file mode 100644 index 0000000..a582bf6 --- /dev/null +++ b/include/asmgrader/common/conversions.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace asmgrader { + +enum class IntParseError { Unknown, InvalidInput, OutOfRange }; + +template +Expected parse_int(std::string_view str, int base = 10) { + IntType res; + std::from_chars_result conv_res = std::from_chars(str.data(), str.data() + str.size(), res, base); + + if (conv_res.ec == std::errc{}) { + return res; + } + if (conv_res.ec == std::errc::invalid_argument) { + return IntParseError::InvalidInput; + } + if (conv_res.ec == std::errc::result_out_of_range) { + return IntParseError::OutOfRange; + } + + return IntParseError::Unknown; +} + +} // namespace asmgrader diff --git a/include/asmgrader/grading_session.hpp b/include/asmgrader/grading_session.hpp index 77d1772..50965c0 100644 --- a/include/asmgrader/grading_session.hpp +++ b/include/asmgrader/grading_session.hpp @@ -31,44 +31,18 @@ namespace asmgrader { -struct CompilerInfo -{ - enum Vendor { Unknown, GCC, Clang } kind; - - int major_version; - int minor_version; - int patch_version; -}; - -static consteval CompilerInfo get_compiler_info() { - CompilerInfo compiler_info{}; - -#if defined(__GNUC__) && !defined(__clang__) - compiler_info.kind = CompilerInfo::GCC; - compiler_info.major_version = __GNUC__; - compiler_info.minor_version = __GNUC_MINOR__; - compiler_info.patch_version = __GNUC_PATCHLEVEL__; -#elif defined(__clang__) - compiler_info.kind = CompilerInfo::Clang; - compiler_info.major_version = __clang_major__; - compiler_info.minor_version = __clang_minor__; - compiler_info.patch_version = __clang_patchlevel__; -#endif - - return compiler_info; -} - +// FIXME: Remove build metadata from this struct struct RunMetadata { - int version = get_version(); - std::string_view version_string = ASMGRADER_VERSION_STRING; - std::string_view git_hash = BOOST_PP_STRINGIZE(ASMGRADER_VERSION_GIT_HASH); + int version = -1; + std::string version_string = buildinfo::get_version_str(); + std::string_view git_hash = ASMGRADER_VERSION_GIT_HASH_STR; std::chrono::time_point start_time = std::chrono::system_clock::now(); decltype(__cplusplus) cpp_standard = __cplusplus; - CompilerInfo compiler_info = get_compiler_info(); + buildinfo::CompilerInfo compiler_info = buildinfo::get_build_info().compiler_info; }; struct RequirementResult @@ -181,8 +155,6 @@ struct MultiStudentResult } // namespace asmgrader // I'm crying, please give me reflection :( -FMT_SERIALIZE_CLASS(::asmgrader::CompilerInfo, kind, major_version, minor_version); -FMT_SERIALIZE_ENUM(::asmgrader::CompilerInfo::Vendor, Unknown, GCC, Clang); FMT_SERIALIZE_CLASS(::asmgrader::RunMetadata, version, version_string, git_hash, start_time, cpp_standard, compiler_info); FMT_SERIALIZE_CLASS(::asmgrader::RequirementResult, passed, description, expression_repr, debug_info); diff --git a/include/asmgrader/version.hpp b/include/asmgrader/version.hpp new file mode 100644 index 0000000..ef670c9 --- /dev/null +++ b/include/asmgrader/version.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include // IWYU pragma: export + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace asmgrader::buildinfo { + +struct Dependency +{ + std::string_view name; + std::string_view version; +}; + +struct CompilerInfo +{ + /// Compiler vendor (GCC or Clang) + enum Vendor { Unknown, GCC, Clang } vendor; + + /// Major version number + int major; + /// Minor version number + int minor; + /// Patch version number + int patch; +}; + +struct BuildInfo +{ + /// Major version number + int major; + + /// Minor version number + int minor; + + /// Patch version number + int patch; + + /// Hex characters of the latest git commit's hash + std::string_view git_hash; + + std::string_view build_type; + + ProcessorKind system_processor; + EndiannessKind endianness; + + CompilerInfo compiler_info; + + AppMode app_mode; + + /// Name of the implementation using the library + std::string_view impl_name; + /// Version of the implementation using the library + std::string_view impl_version; + + /// C++ standard we compiled with (__cplusplus) + long cpp_standard; // NOLINT(google-runtime-int) + + /// Date of compilation (__DATE__) + std::string_view date; + /// Time of compilation (__TIME__) + std::string_view time; + + /// Library dependencies of the project + /// Use \ref get_dependencies for a more structured form of dependency info + /// Consists of a number of space seperated " " strings + /// Example: "fmt 11.1.3 spdlog 1.15.1" ... + std::string_view dependencies; +}; + +std::string_view format_as(const AppMode& from); + +std::string_view format_as(const CompilerInfo::Vendor& from); + +buildinfo::BuildInfo get_build_info(); + +/// \returns std::vector for all of the dependencies found +/// in \ref get_build_info().dependencies +std::vector get_dependencies(); + +const char* get_plain_version_str(); + +std::string get_version_str(); + +} // namespace asmgrader::buildinfo + +/// "{}" spec gives a human-readable output; "{:?}" spec gives structured output +template <> +struct fmt::formatter : asmgrader::DebugFormatter +{ + fmt::appender format(const asmgrader::buildinfo::Dependency& from, format_context& ctx) const; +}; + +/// "{}" spec gives a human-readable output; "{:?}" spec gives structured output +template <> +struct fmt::formatter : asmgrader::DebugFormatter +{ + fmt::appender format(const asmgrader::buildinfo::CompilerInfo& from, format_context& ctx) const; +}; + +/// "{}" spec gives a human-readable output; "{:?}" spec gives structured output +template <> +struct fmt::formatter : asmgrader::DebugFormatter +{ + fmt::appender format(const asmgrader::buildinfo::BuildInfo& from, format_context& ctx) const; +}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fb5ed3c..268c185 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -68,6 +68,8 @@ set( app/professor_app.cpp app/student_app.cpp + + version.cpp ) set( @@ -158,6 +160,15 @@ endfunction() ##### Student version create_target(asmgrader) +# Courtesy of triclosan +# CC BY-SA 4.0 +# https://stackoverflow.com/a/61206137 +add_custom_command( + TARGET asmgrader_core + PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CMAKE_CURRENT_SOURCE_DIR}/version.cpp +) + ##### Professor version create_target(asmgrader_prof) target_compile_definitions( diff --git a/src/api/test_context.cpp b/src/api/test_context.cpp index 9b20a38..63192b4 100644 --- a/src/api/test_context.cpp +++ b/src/api/test_context.cpp @@ -10,6 +10,7 @@ #include "common/bit_casts.hpp" #include "common/byte_array.hpp" #include "common/error_types.hpp" +#include "common/linux.hpp" #include "common/macros.hpp" #include "common/unreachable.hpp" #include "exceptions.hpp" @@ -126,6 +127,12 @@ Result TestContext::run() { return res; } +Result TestContext::cont() { + TRYE(linux::kill(prog_.get_subproc().get_pid(), SIGCONT), SyscallFailure); + + return run(); +} + Result TestContext::run_until(u64 syscallnr) { std::optional exit_code{}; diff --git a/src/output/plaintext_serializer.cpp b/src/output/plaintext_serializer.cpp index 0d4b582..03d480d 100644 --- a/src/output/plaintext_serializer.cpp +++ b/src/output/plaintext_serializer.cpp @@ -4,6 +4,8 @@ #include "api/requirement.hpp" #include "api/stringize.hpp" #include "api/syntax_highlighter.hpp" +#include "app/app.hpp" +#include "app_mode.hpp" #include "common/overloaded.hpp" #include "common/terminal_checks.hpp" #include "common/time.hpp" @@ -144,7 +146,7 @@ void PlainTextSerializer::on_assignment_result(const AssignmentResult& data) { std::string out; // We don't want to repeatedly output the same assignment name for every student in prof mode - if (APP_MODE == AppMode::Student) { + if (is_student_mode()) { out = fmt::format("{0}\nAssignment: {1}\n{0}\n", LINE_DIVIDER_EM(terminal_width_), data.name); } else { out = LINE_DIVIDER(terminal_width_) + "\n"; @@ -188,7 +190,7 @@ void PlainTextSerializer::on_assignment_result(const AssignmentResult& data) { out += fmt::format("{}\n{}\n", tests_line, requirements_line); // Extra line - if (APP_MODE == AppMode::Professor) { + if (is_prof_mode()) { // FIXME: ??? } @@ -320,7 +322,7 @@ void PlainTextSerializer::on_run_metadata(const RunMetadata& data) { std::string version_text = fmt::format("{}-g{}", data.version_string, data.git_hash); - if (APP_MODE == AppMode::Professor) { + if (is_prof_mode()) { version_text += " (Professor)"; } else { version_text += " (Student)"; diff --git a/src/output/verbosity.hpp b/src/output/verbosity.hpp index d1dd5b1..d7ff5cb 100644 --- a/src/output/verbosity.hpp +++ b/src/output/verbosity.hpp @@ -1,6 +1,6 @@ #pragma once -#include "version.hpp" +#include "app_mode.hpp" namespace asmgrader { @@ -33,16 +33,16 @@ constexpr bool should_output_test(VerbosityLevel level) { constexpr bool should_output_student_summary(VerbosityLevel level) { using enum VerbosityLevel; - return (APP_MODE == AppMode::Student && level >= Quiet) // - || // - (APP_MODE == AppMode::Professor && level >= Summary); // + return (get_builtin_app_mode() == AppMode::Student && level >= Quiet) // + || // + (get_builtin_app_mode() == AppMode::Professor && level >= Summary); // } /// See \ref VerbosityLevel constexpr bool should_output_grade_percentage(VerbosityLevel level) { using enum VerbosityLevel; - return (APP_MODE == AppMode::Professor && level >= Quiet); + return (get_builtin_app_mode() == AppMode::Professor && level >= Quiet); } /// See \ref VerbosityLevel diff --git a/src/subprocess/subprocess.cpp b/src/subprocess/subprocess.cpp index 1511352..7857a41 100644 --- a/src/subprocess/subprocess.cpp +++ b/src/subprocess/subprocess.cpp @@ -93,6 +93,12 @@ Result Subprocess::restart() { TRY(kill()); } + // Clear all old stdout+stderr records + stdout_.buffer.clear(); + stdout_.cursor = 0; + stderr_.buffer.clear(); + stderr_.cursor = 0; + TRY(start()); return {}; diff --git a/src/subprocess/tracer.cpp b/src/subprocess/tracer.cpp index b10d83e..25c5496 100644 --- a/src/subprocess/tracer.cpp +++ b/src/subprocess/tracer.cpp @@ -58,6 +58,10 @@ using namespace std::chrono_literals; Result Tracer::begin(pid_t pid) { pid_ = pid; + // Remove all OLD syscall records + // relevant when using Subrocess::restart + syscall_records_.clear(); + assert_invariants(); // TODO: Extract this @@ -398,11 +402,13 @@ Result Tracer::run_until(const std::function& pr // trapped by a signal (such as by a SEGFAULT) if (waitid_data.type == CLD_TRAPPED) { - // FIXME: better macro, or abstracted registers -#ifndef ASMGRADER_AARCH64 LOG_TRACE("Child proc trapped by signal ({}). Regs state: {}", *waitid_data.signal_num, format_or_unknown(get_registers())); -#endif + + if (*waitid_data.signal_num == SIGCONT) { + LOG_TRACE("Ignoring SIGCONT in child proc"); + continue; + } return RunResult::make_signal_caught(*waitid_data.signal_num); } diff --git a/src/test_runner.cpp b/src/test_runner.cpp index e21c4e0..e9982b2 100644 --- a/src/test_runner.cpp +++ b/src/test_runner.cpp @@ -3,6 +3,7 @@ #include "api/assignment.hpp" #include "api/test_base.hpp" #include "api/test_context.hpp" +#include "app_mode.hpp" #include "exceptions.hpp" #include "grading_session.hpp" #include "logging.hpp" @@ -49,7 +50,7 @@ AssignmentResult AssignmentTestRunner::run_all(std::optionalget_tests() | maybe_tests_filter) { // Skip tests that are marked as professor-only if we're not in professor mode - if (test.get_is_prof_only() && APP_MODE != AppMode::Professor) { + if (test.get_is_prof_only() && is_prof_mode()) { continue; } const std::string_view assignment_name = test.get_assignment().get_name(); diff --git a/src/user/cl_args.cpp b/src/user/cl_args.cpp index c29878b..f306b0b 100644 --- a/src/user/cl_args.cpp +++ b/src/user/cl_args.cpp @@ -1,6 +1,7 @@ #include "cl_args.hpp" #include "api/assignment.hpp" +#include "app_mode.hpp" #include "common/expected.hpp" #include "common/os.hpp" #include "common/static_string.hpp" @@ -32,7 +33,8 @@ namespace asmgrader { CommandLineArgs::CommandLineArgs(std::span args) - : arg_parser_{get_basename(args[0]), /*unused*/ ASMGRADER_VERSION_STRING, argparse::default_arguments::help} + : arg_parser_{get_basename(args[0]), /*unused*/ buildinfo::get_plain_version_str(), + argparse::default_arguments::help} , args_{args.begin(), args.end()} { // Add parser arguments setup_parser(); @@ -53,13 +55,7 @@ void CommandLineArgs::setup_parser() { const auto assignment_names = GlobalRegistrar::get().for_each_assignment([&](const Assignment& assignment) { return assignment.get_name(); }); - static constexpr auto VERSION_STR = static_format<"AsmGrader v{}-g{}{}{}{}">( - ASMGRADER_VERSION_STRING, ASMGRADER_VERSION_GIT_HASH_STRING, - APP_MODE == AppMode::Professor ? " (Professor's Version)" : " (Student's Version)", - std::string_view{ASMGRADER_EXTRA_VERSION_INFO}.empty() ? "" : "\n", - std::string_view{ASMGRADER_EXTRA_VERSION_INFO}); - - arg_parser_.add_description(VERSION_STR.str()); + arg_parser_.add_description(buildinfo::get_version_str()); // FIXME: argparse is kind of annoying. Behavior is dependant upon ORDER of chained fn calls. // maybe want to switch to another lib, or just do it myself. Need arg choices in help. @@ -91,33 +87,7 @@ void CommandLineArgs::setup_parser() { .implicit_value(true) .nargs(0) .action([&](const auto & /*unused*/) { - // FIXME: This is BUILD info - RunMetadata run_info{}; - - fmt::println("{}\n", std::string_view{VERSION_STR}); - - std::string_view compiler_str = ""; - - if (run_info.compiler_info.kind == CompilerInfo::GCC) { - compiler_str = "GCC"; - } else if (run_info.compiler_info.kind == CompilerInfo::Clang) { - compiler_str = "Clang"; - } - - fmt::println("Build Type: " -#if defined(DEBUG) - "Debug" -#elif defined(RELEASE) - "Release" -#else - "" -#endif - ); - fmt::println("Target: {}, {}", SYSTEM_PROCESSOR, EndiannessKind::Native); - fmt::println("Compiler: {} v{}.{}.{}", compiler_str, run_info.compiler_info.major_version, run_info.compiler_info.minor_version, run_info.compiler_info.patch_version); - - fmt::println(""); - fmt::println("Build Time: {} at {}", __DATE__, __TIME__); + fmt::println("{}", asmgrader::buildinfo::get_build_info()); std::exit(0); }) @@ -210,14 +180,20 @@ void CommandLineArgs::setup_parser() { }) .help("RegEx to match files for a given student and assignment.\nSee docs for syntax details."); + // Path of THIS program + // See proc_pid(5) for info on "/proc/PID/exe" + + const auto exec_path = std::filesystem::canonical("/proc/self/exe").parent_path(); + const std::string default_database_path = (exec_path / ProgramOptions::DEFAULT_DATABASE_NAME).string(); + opts_buffer_.database_path = default_database_path; arg_parser_.add_argument("-db", "--database") - .default_value(std::string{ProgramOptions::DEFAULT_DATABASE_PATH}) + .default_value(default_database_path) .nargs(1) .metavar("FILE") .action([this] (const std::string& opt) { opts_buffer_.database_path = opt; }) - .help("CSV database file with student names. If not specified, " + .help("CSV database file with student names. If not specified nor the default found, " "will attempt to find student submissions recursively using heuristics.\nSee docs for format spec."); arg_parser_.add_argument("-p", "--search-path") @@ -235,7 +211,7 @@ void CommandLineArgs::setup_parser() { .action([this] (const std::string& opt) { opts_buffer_.file_name = opt; }) - .help(APP_MODE == AppMode::Professor ? + .help(get_builtin_app_mode() == AppMode::Professor ? "The *individual* file to run tests on. No other files are searched for, nor is the database read.\n" "This argument's behavior overrides any usage of --file-matcher, --search-path, and --database." : // professor help msg "The file to run tests on." // student help msg diff --git a/src/user/program_options.hpp b/src/user/program_options.hpp index 0361856..655b653 100644 --- a/src/user/program_options.hpp +++ b/src/user/program_options.hpp @@ -3,12 +3,13 @@ #include #include +#include "app_mode.hpp" #include "common/error_types.hpp" #include "common/expected.hpp" +#include "logging.hpp" #include "output/verbosity.hpp" #include "program/program.hpp" #include "user/assignment_file_searcher.hpp" -#include "version.hpp" #include #include @@ -56,12 +57,12 @@ struct ProgramOptions // PROFESSOR_VERSION only std::string file_matcher = std::string{DEFAULT_FILE_MATCHER}; - std::filesystem::path database_path = DEFAULT_DATABASE_PATH; + std::filesystem::path database_path = DEFAULT_DATABASE_NAME; std::filesystem::path search_path = DEFAULT_SEARCH_PATH; // ###### Argument defaults - static constexpr std::string_view DEFAULT_DATABASE_PATH = "students.csv"; + static constexpr std::string_view DEFAULT_DATABASE_NAME = "students.csv"; static constexpr std::string_view DEFAULT_SEARCH_PATH = "."; static constexpr std::string_view DEFAULT_FILE_MATCHER = AssignmentFileSearcher::DEFAULT_REGEX; static constexpr auto DEFAULT_VERBOSITY_LEVEL = VerbosityLevel::Summary; @@ -118,8 +119,8 @@ struct ProgramOptions // Only check the database path if it's not the default // non-existance will be handled properly in ProfessorApp - if (database_path != DEFAULT_DATABASE_PATH) { - TRY(ensure_is_regular_file(database_path, "Database file {:?}")); + if (auto res = ensure_is_regular_file(database_path, "Database file {:?}"); !res) { + LOG_WARN(res.error()); } // If the assignment name is empty, we're going to be attempting to infer it elsewhere @@ -132,7 +133,7 @@ struct ProgramOptions auto assignment = TRYE(GlobalRegistrar::get().get_assignment(assignment_name), fmt::format("Error locating assignment {}", assignment_name)); - if (APP_MODE != AppMode::Professor) { + if (get_builtin_app_mode() != AppMode::Professor) { // TODO: A more friendly diagnostic for non-existant file std::string exec_file_name = file_name.value_or(assignment.get().get_exec_path()); @@ -157,7 +158,7 @@ struct fmt::formatter<::asmgrader::ProgramOptions> : ::asmgrader::DebugFormatter fmt::underlying(from.verbosity), from.assignment_name, fmt::underlying(from.stop_option), fmt::underlying(from.colorize_option), from.file_name)); - if (asmgrader::APP_MODE == asmgrader::AppMode::Professor) { + if (asmgrader::get_builtin_app_mode() == asmgrader::AppMode::Professor) { return fmt::format_to(ctx.out(), " file_matcher={}, database_path={}, search_path={}", from.file_matcher, from.database_path, from.search_path); } diff --git a/src/version.cpp b/src/version.cpp new file mode 100644 index 0000000..cdcfe1d --- /dev/null +++ b/src/version.cpp @@ -0,0 +1,283 @@ +// ccache:disable +// Rational: __DATE__ and __TIME__ macros should correspond to actual build date and time +#include "version.hpp" + +#include "common/os.hpp" +#include "common/static_string.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace asmgrader::buildinfo { + +std::string_view format_as(const CompilerInfo::Vendor& from) { + using enum CompilerInfo::Vendor; + switch (from) { + case GCC: + return "GCC"; + case Clang: + return "Clang"; + case Unknown: + default: + return ""; + } +}; + +/// Implementation details for creating a canonical BuildInfo instance +namespace detail { + +consteval CompilerInfo make_compiler_info() { + CompilerInfo compiler_info{}; + +#if defined(__GNUC__) && !defined(__clang__) + compiler_info.vendor = CompilerInfo::GCC; + compiler_info.major = __GNUC__; + compiler_info.minor = __GNUC_MINOR__; + compiler_info.patch = __GNUC_PATCHLEVEL__; +#elif defined(__clang__) + compiler_info.vendor = CompilerInfo::Clang; + compiler_info.major = __clang_major__; + compiler_info.minor = __clang_minor__; + compiler_info.patch = __clang_patchlevel__; +#endif + + return compiler_info; +} + +consteval AppMode make_app_mode() { + return get_builtin_app_mode(); +} + +constexpr StaticString static_deps_str{ASMGRADER_DEPENDENCY_VERSIONS_STR}; + +consteval std::string_view make_impl_name() { + std::string_view impl_version_str = ASMGRADER_IMPL_VERSION_INFO; + + std::size_t space_pos = impl_version_str.find(' '); + + if (space_pos == std::string_view::npos) { + return "(name unknown)"; + } + + return impl_version_str.substr(0, space_pos); +} + +consteval std::string_view make_impl_version() { + std::string_view impl_version_str = ASMGRADER_IMPL_VERSION_INFO; + + std::size_t space_pos = impl_version_str.find(' '); + + if (space_pos == std::string_view::npos) { + return "(version unknown)"; + } + + return impl_version_str.substr(space_pos + 1); +} + +template +consteval auto convert_deps_str() { + // Consists of a number of space seperated " " strings + // Example: "fmt 11.1.3 spdlog 1.15.1" ... + + // As by spec above: count => number of spaces / 2 + 1 + constexpr auto num_deps = gsl::narrow((ranges::count(DepsStr, ' ') / 2) + 1); + + std::array result{}; + + // many range-v3 views are not constexpr :( + + // raw loop because the stdlib 12 implementation for aarch64 cross compilation seems + // to have bugs with considering std::string_view::find constexpr + + std::string_view deps_sv = DepsStr; + std::size_t last_start = 0; + auto res_iter = result.begin(); + bool found_name = false; + + for (std::size_t i = 0; i < deps_sv.size(); i++) { + // Only do processing at ' ' + if (deps_sv.at(i) != ' ') { + continue; + } + + if (!found_name) { + res_iter->name = deps_sv.substr(last_start, i - last_start); + } else { + res_iter->version = deps_sv.substr(last_start, i - last_start); + ++res_iter; + } + + found_name = !found_name; + + last_start = i + 1; + } + + // set the last version field which was not processed + result.back().version = deps_sv.substr(last_start); + + return result; +} + +consteval BuildInfo make_build_info() { + return BuildInfo{ + .major = ASMGRADER_VERSION_MAJOR, + .minor = ASMGRADER_VERSION_MINOR, + .patch = ASMGRADER_VERSION_PATCH, + .git_hash = ASMGRADER_VERSION_GIT_HASH_STR, + .build_type = ASMGRADER_VERSION_BUILD_TYPE_STR, + .system_processor = SYSTEM_PROCESSOR, + .endianness = EndiannessKind::Native, + .compiler_info = make_compiler_info(), + .app_mode = make_app_mode(), + .impl_name = make_impl_name(), + .impl_version = make_impl_version(), + .cpp_standard = __cplusplus, + .date = __DATE__, + .time = __TIME__, + .dependencies = static_deps_str + // + }; +} + +} // namespace detail + +buildinfo::BuildInfo get_build_info() { + return buildinfo::detail::make_build_info(); +} + +/// \returns std::array for all of the dependencies found +/// in \ref get_build_info().dependencies +std::vector get_dependencies() { + return detail::convert_deps_str() | ranges::to(); +} + +const char* get_plain_version_str() { + return BOOST_PP_STRINGIZE(ASMGRADER_VERSION_MAJOR) "." BOOST_PP_STRINGIZE(ASMGRADER_VERSION_MINOR) "." BOOST_PP_STRINGIZE(ASMGRADER_VERSION_PATCH); +} + +// NOLINTBEGIN(readability-avoid-nested-conditional-operator) +// Adapted from https://stackoverflow.com/a/70567530 +// Courtesy of Lundin +constexpr std::array date_iso8601 = std::to_array({ + // YYYY year + __DATE__[7], __DATE__[8], __DATE__[9], __DATE__[10], + + // hyphen seperator + '-', + + // First month letter, Oct Nov Dec = '1' otherwise '0' + (__DATE__[0] == 'O' || __DATE__[0] == 'N' || __DATE__[0] == 'D') ? '1' : '0', + + // Second month letter + (__DATE__[0] == 'J') ? ((__DATE__[1] == 'a') ? '1' : // Jan, Jun or Jul + ((__DATE__[2] == 'n') ? '6' : '7')) + : (__DATE__[0] == 'F') ? '2' + : // Feb + (__DATE__[0] == 'M') ? (__DATE__[2] == 'r') ? '3' : '5' + : // Mar or May + (__DATE__[0] == 'A') ? (__DATE__[1] == 'p') ? '4' : '8' + : // Apr or Aug + (__DATE__[0] == 'S') ? '9' + : // Sep + (__DATE__[0] == 'O') ? '0' + : // Oct + (__DATE__[0] == 'N') ? '1' + : // Nov + (__DATE__[0] == 'D') ? '2' + : // Dec + 0, + + // hyphen seperator + '-', + + // First day number / space, replace space with digit + __DATE__[4] == ' ' ? '0' : __DATE__[4], + + // Second day number + __DATE__[5], + + '\0' // null terminate +}); + +// NOLINTEND(readability-avoid-nested-conditional-operator) + +consteval std::string_view get_date_iso8601() { + std::string_view str = {date_iso8601.begin(), date_iso8601.end()}; + // do not include '\0' in string_view + str.remove_suffix(1); + return str; +} + +std::string get_version_str() { + auto build_info = get_build_info(); + + return fmt::format("AsmGrader v{}.{}.{}-g{:.12} ({})\n", build_info.major, build_info.minor, build_info.patch, + build_info.git_hash, build_info.app_mode) + + fmt::format("[Implementation] {} v{}", build_info.impl_name, build_info.impl_version); +} + +} // namespace asmgrader::buildinfo + +/// "{}" spec gives a human-readable output; "{:?}" spec gives structured output +fmt::appender fmt::formatter<::asmgrader::buildinfo::Dependency>::format(const asmgrader::buildinfo::Dependency& from, + format_context& ctx) const { + if (is_debug_format) { + return fmt::format_to(ctx.out(), "Dependency{{.name = {:?}, .version = {:?}}}", from.name, from.version); + } + return fmt::format_to(ctx.out(), "{} {}", from.name, from.version); +} + +/// "{}" spec gives a human-readable output; "{:?}" spec gives structured output +fmt::appender fmt::formatter::format(const asmgrader::buildinfo::CompilerInfo& from, + format_context& ctx) const { + if (is_debug_format) { + return fmt::format_to(ctx.out(), "CompilerInfo{{.vendor = {:?}, .major = {}, .minor = {}, .patch = {}}}", + from.vendor, from.major, from.minor, from.patch); + } + return fmt::format_to(ctx.out(), "{} v{}.{}.{}", from.vendor, from.major, from.minor, from.patch); +} + +/// "{}" spec gives a human-readable output; "{:?}" spec gives structured output + +fmt::appender fmt::formatter::format(const asmgrader::buildinfo::BuildInfo& from, + format_context& ctx) const { + if (is_debug_format) { + return fmt::format_to( + ctx.out(), + "BuildInfo{{.major = {}, .minor = {}, .patch = {}, .git_hash = {:?}, .build_type = {:?}, .system_processor " + "= {}, .endianness = {}, .compiler_info = {:?}, .app_mode = {:?}, " + ".impl_name = {:?}, .impl_version = {:?}, .cpp_standard = {}, .date = {:?} [iso8601 = {:?}], .time = " + "{:?}, .dependencies = {:?} [processed = {::?}]}}", + from.major, from.minor, from.patch, from.git_hash, from.build_type, from.system_processor, from.endianness, + from.compiler_info, from.app_mode, from.impl_name, from.impl_version, from.cpp_standard, from.date, + asmgrader::buildinfo::get_date_iso8601(), from.time, from.dependencies, + asmgrader::buildinfo::get_dependencies()); + } + + // Only include first 12 chars from git hash + ctx.advance_to(fmt::format_to(ctx.out(), "AsmGrader v{}.{}.{}-g{:.12} ({})\n", from.major, from.minor, from.patch, + from.git_hash, from.app_mode)); + ctx.advance_to(fmt::format_to(ctx.out(), "[Implementation] {} v{}\n", from.impl_name, from.impl_version)); + *ctx.out()++ = '\n'; + ctx.advance_to(fmt::format_to(ctx.out(), "Build Type: {}\n", from.build_type)); + ctx.advance_to(fmt::format_to(ctx.out(), "Target: {}, {}\n", from.system_processor, from.endianness)); + ctx.advance_to(fmt::format_to(ctx.out(), "Compiler: {} (C++ {})\n", from.compiler_info, from.cpp_standard)); + *ctx.out()++ = '\n'; + ctx.advance_to(fmt::format_to(ctx.out(), "Dependencies:\n\t{}\n", + fmt::join(asmgrader::buildinfo::get_dependencies(), "\n\t"))); + *ctx.out()++ = '\n'; + ctx.advance_to(fmt::format_to(ctx.out(), "Built: {} {} ({})\n", asmgrader::buildinfo::get_date_iso8601(), from.time, + from.date)); + + return ctx.out(); +} diff --git a/src/version.hpp.in b/src/version.hpp.in deleted file mode 100644 index c9b0bb8..0000000 --- a/src/version.hpp.in +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include -#include - -// @ident@ expressions are substituted by CMake -// clang-format off -#define ASMGRADER_VERSION_MAJOR @AsmGrader_VERSION_MAJOR@ -#define ASMGRADER_VERSION_MINOR @AsmGrader_VERSION_MINOR@ -#define ASMGRADER_VERSION_PATCH @AsmGrader_VERSION_PATCH@ -#define ASMGRADER_VERSION_GIT_HASH @ASMGRADER_VERSION_GIT_HASH@ -// clang-format on - -#define ASMGRADER_VERSION_STRING \ - STRINGIFY(ASMGRADER_VERSION_MAJOR) "." STRINGIFY(ASMGRADER_VERSION_MINOR) "." STRINGIFY(ASMGRADER_VERSION_PATCH) -#define ASMGRADER_VERSION_GIT_HASH_STRING STRINGIFY(ASMGRADER_VERSION_GIT_HASH) - -#ifndef ASMGRADER_EXTRA_VERSION_INFO -/// A string with more version info defined by the library user -/// Most useful for defining a secondary "implementation version" seperate from the library version -#define ASMGRADER_EXTRA_VERSION_INFO "" -#endif - -namespace asmgrader { -// The maximum value of any MAJOR / MINOR / PATCH field -constexpr auto MAX_VERSION_FIELD = 99; - -consteval unsigned int get_version() { - static_assert(ASMGRADER_VERSION_MAJOR <= MAX_VERSION_FIELD); - static_assert(ASMGRADER_VERSION_MINOR <= MAX_VERSION_FIELD); - static_assert(ASMGRADER_VERSION_PATCH <= MAX_VERSION_FIELD); - - constexpr auto MINOR_MULTIPLIER = MAX_VERSION_FIELD + 1; - constexpr auto MAJOR_MULTIPLIER = MINOR_MULTIPLIER * MINOR_MULTIPLIER; - - return ASMGRADER_VERSION_MAJOR * MAJOR_MULTIPLIER + ASMGRADER_VERSION_MINOR * MINOR_MULTIPLIER + - ASMGRADER_VERSION_PATCH; -} - -enum class AppMode { Student, Professor }; - -#ifdef PROFESSOR_VERSION -constexpr auto APP_MODE = AppMode::Professor; -#else -constexpr auto APP_MODE = AppMode::Student; -#endif - -} // namespace asmgrader From 42d25c5d61a6c7243ded7fa62a1189723d2784dc Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Tue, 25 Nov 2025 23:02:17 -0800 Subject: [PATCH 09/41] update .clangd --- .clangd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clangd b/.clangd index 9c216ce..6743a81 100644 --- a/.clangd +++ b/.clangd @@ -1,7 +1,7 @@ CompileFlags: Add: -DPROFESSOR_VERSION Remove: -fconcepts-diagnostics-depth=* - CompilationDatabase: build/unixlike-gcc-debug + CompilationDatabase: build/unixlike-gcc-ninja-debug InlayHints: BlockEnd: No From 7cb660d17c065392842b911850b9c971ce097fe0 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 15:14:51 -0800 Subject: [PATCH 10/41] begin adding meson.build files --- include/meson.build | 1 + meson.build | 117 ++++++++++++++++++ src/meson.build | 78 ++++++++++++ subprojects/.gitignore | 2 + subprojects/argparse.wrap | 13 ++ subprojects/boost.wrap | 14 +++ subprojects/catch2.wrap | 11 ++ subprojects/elfio.wrap | 9 ++ subprojects/fmt.wrap | 13 ++ subprojects/libassert.wrap | 10 ++ subprojects/microsoft-gsl.wrap | 13 ++ subprojects/packagefiles/range-v3/meson.build | 14 +++ subprojects/range-v3.wrap | 9 ++ subprojects/spdlog.wrap | 13 ++ 14 files changed, 317 insertions(+) create mode 100644 include/meson.build create mode 100644 meson.build create mode 100644 src/meson.build create mode 100644 subprojects/.gitignore create mode 100644 subprojects/argparse.wrap create mode 100644 subprojects/boost.wrap create mode 100644 subprojects/catch2.wrap create mode 100644 subprojects/elfio.wrap create mode 100644 subprojects/fmt.wrap create mode 100644 subprojects/libassert.wrap create mode 100644 subprojects/microsoft-gsl.wrap create mode 100644 subprojects/packagefiles/range-v3/meson.build create mode 100644 subprojects/range-v3.wrap create mode 100644 subprojects/spdlog.wrap diff --git a/include/meson.build b/include/meson.build new file mode 100644 index 0000000..2b3ca86 --- /dev/null +++ b/include/meson.build @@ -0,0 +1 @@ +asmgrader_public_inc = include_directories('.') diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..094f6a9 --- /dev/null +++ b/meson.build @@ -0,0 +1,117 @@ +# asmgrader top-level project setup +# ============================================================================== + +project( + 'asmgrader', + ['cpp'], + license : 'MIT', + version : '0.4.4', + meson_version : '>=0.55.0', + default_options : [ + 'warning_level=3', + 'default_library=static' + ], +) + +# ============================================================================== +# Dependencies +# ============================================================================== + +# ========= +# Boost +# ...is *special* +# ========= + +boost_module_names = [ + 'endian', + 'type_index', + 'describe', + 'mp11', + 'pfr', + # 'stacktrace', + 'preprocessor', +] +cmake = import('cmake') +boost_opts = cmake.subproject_options() +boost_opts.add_cmake_defines({ + 'BOOST_SHARED_LIBS': 'OFF', + 'BOOST_SKIP_INSTALL_RULES': 'ON', + 'BOOST_INCLUDE_LIBRARIES': ';'.join(boost_module_names), +}) +boost_subproj = cmake.subproject('boost', + options: boost_opts +) +boost_dep = [] +foreach mod : boost_module_names + boost_dep += [boost_subproj.dependency('boost_' + mod)] +endforeach + +# ========= +# argparse +# +# for command line argument parsing +# ========= +argparse_dep = dependency('argparse') + +# ========= +# spdlog +# +# for fast runtime logging +# ========= +spdlog_dep = dependency('spdlog') + +# ========= +# fmt +# +# for friendly string interpolation +# ========= +fmt_dep = dependency('fmt') + +# ========= +# gsl (guidelines support library) +# +# for better code conformance +# ========= +gsl_dep = dependency('microsoft_gsl') + +# ========= +# range-v3 +# +# for "modern" range-based patterns +# ========= +rangev3_dep = dependency('range-v3') + +# ========= +# elfio +# +# for parsing of ELF files +# ========= +elfio_dep = dependency('elfio') + +# ========= +# cpptrace +# +# for human-readable backtraces +# ========= +cpptrace_dep = dependency('cpptrace') + +# ========= +# libassert +# +# for human-readable assertions with tons of info +# ========= +libasssert_dep = dependency('libassert') + + +# ============================================================================== +# Sub-Directories +# ============================================================================== + +subdir('include') +subdir('src') + +# project_target = executable( +# meson.project_name(), +# sources : project_sources, +# dependencies: [rangev3_dep, fmt_dep], +# ) diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..3ffb32c --- /dev/null +++ b/src/meson.build @@ -0,0 +1,78 @@ +core_link_libs_public = ['fmt::fmt', 'spdlog', 'Boost::endian', 'Boost::type_index', 'Boost::mp11', 'Boost::pfr', 'Boost::describe', 'Boost::stacktrace', 'Boost::preprocessor', 'range-v3', 'Microsoft.GSL::GSL', 'libassert::assert'] +# core_link_libs_private = ['asmgrader_options', 'asmgrader_warnings', 'argparse', 'nlohmann_json::nlohmann_json', 'elfio::elfio'] + +asmgrader_private_inc = include_directories('.') + +asmgrader_deps = [ + boost_dep, + argparse_dep, + spdlog_dep, + fmt_dep, + gsl_dep, + rangev3_dep, + elfio_dep, + cpptrace_dep, + libasssert_dep, +] + +asmgrader_sources = [ + 'subprocess/subprocess.cpp', + 'subprocess/traced_subprocess.cpp', + 'subprocess/tracer.cpp', + 'subprocess/memory/memory_io_base.cpp', + 'subprocess/memory/ptrace_memory_io.cpp', + 'common/terminal_checks.cpp', + 'output/plaintext_serializer.cpp', + 'output/stdout_sink.cpp', + 'registrars/global_registrar.cpp', + 'test_runner.cpp', + 'multi_student_runner.cpp', + 'symbols/elf_reader.cpp', + 'symbols/symbol_table.cpp', + 'program/program.cpp', + 'user/cl_args.cpp', + 'api/tempfile.cpp', + 'api/process_statistics.cpp', + 'api/assignment.cpp', + 'api/test_context.cpp', + 'api/syntax_highlighter.cpp', + 'api/stringize.cpp', + 'database_reader.cpp', + 'user/file_searcher.cpp', + 'user/assignment_file_searcher.cpp', + 'app/professor_app.cpp', + 'app/student_app.cpp', + 'version.cpp' +] + +asmgrader_with_main_sources = ['main.cpp'] + +##### Helper function to create all core targets. Used to create a near-duplicate target for professor mode +# function(['create_target', 'basename']) +#### Interface (just for internal includes and libraries) + +# ============================================================================== +# Project and dependency setup +# ============================================================================== + +asmgrader_target = library( + 'asmgrader', + sources: asmgrader_sources, + include_directories: [asmgrader_public_inc, asmgrader_private_inc], + dependencies: asmgrader_deps, +) +asmgrader_dep = declare_dependency( + include_directories: asmgrader_public_inc, + link_with: asmgrader_target, +) + +asmgrader_with_main_target = library( + 'asmgrader_with_main', + sources: asmgrader_sources, + include_directories: [asmgrader_public_inc, asmgrader_private_inc], + dependencies: [asmgrader_deps, asmgrader_dep], +) +asmgrader_with_main_dep = declare_dependency( + include_directories: asmgrader_public_inc, + link_with: asmgrader_with_main_target, +) diff --git a/subprojects/.gitignore b/subprojects/.gitignore new file mode 100644 index 0000000..faa2502 --- /dev/null +++ b/subprojects/.gitignore @@ -0,0 +1,2 @@ +/*/ +!packagefiles/ diff --git a/subprojects/argparse.wrap b/subprojects/argparse.wrap new file mode 100644 index 0000000..bd9e645 --- /dev/null +++ b/subprojects/argparse.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = argparse-3.2 +source_url = https://github.com/p-ranav/argparse/archive/refs/tags/v3.2.tar.gz +source_filename = argparse-3.2.tar.gz +source_hash = 9dcb3d8ce0a41b2a48ac8baa54b51a9f1b6a2c52dd374e28cc713bab0568ec98 +patch_filename = argparse_3.2-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/argparse_3.2-2/get_patch +patch_hash = 46a7f697beacf0ecdd56d430bd0707dea0311fa3ff8e0ebfe9e6188d48e6a96b +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/argparse_3.2-2/argparse-3.2.tar.gz +wrapdb_version = 3.2-2 + +[provide] +argparse = argparse_dep diff --git a/subprojects/boost.wrap b/subprojects/boost.wrap new file mode 100644 index 0000000..2300881 --- /dev/null +++ b/subprojects/boost.wrap @@ -0,0 +1,14 @@ +[wrap-file] +directory = boost-1.88.0 +source_url = https://github.com/boostorg/boost/releases/download/boost-1.88.0/boost-1.88.0-cmake.tar.xz +source_filename = boost-1.88.0.tar.gz +source_hash = f48b48390380cfb94a629872346e3a81370dc498896f16019ade727ab72eb1ec +method = cmake + +; [provide] +; boost_endian = boost_endian_dep +; boost_type_index = boost_type_index_dep +; boost_mp11 = boost_mp11_dep +; boost_pfr = boost_pfr_dep +; boost_describe = boost_describe_dep +; boost_preprocessor = boost_preprocessor_dep diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap new file mode 100644 index 0000000..96c1e10 --- /dev/null +++ b/subprojects/catch2.wrap @@ -0,0 +1,11 @@ +[wrap-file] +directory = Catch2-3.11.0 +source_url = https://github.com/catchorg/Catch2/archive/v3.11.0.tar.gz +source_filename = Catch2-3.11.0.tar.gz +source_hash = 82fa1cb59dc28bab220935923f7469b997b259eb192fb9355db62da03c2a3137 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.11.0-1/Catch2-3.11.0.tar.gz +wrapdb_version = 3.11.0-1 + +[provide] +catch2 = catch2_dep +catch2-with-main = catch2_with_main_dep diff --git a/subprojects/elfio.wrap b/subprojects/elfio.wrap new file mode 100644 index 0000000..6f1af5f --- /dev/null +++ b/subprojects/elfio.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = elfio-3.12 +source_url = https://github.com/serge1/ELFIO/releases/download/Release_3.12/elfio-3.12.tar.gz +source_filename = elfio-3.12.tar.gz +source_hash = caf49f3bf55a9c99c98ebea4b05c79281875783802e892729eea0415505f68c4 +method = cmake + +[provide] +elfio = elfio_dep diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap new file mode 100644 index 0000000..7e9c5c4 --- /dev/null +++ b/subprojects/fmt.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = fmt-12.0.0 +source_url = https://github.com/fmtlib/fmt/archive/12.0.0.tar.gz +source_filename = fmt-12.0.0.tar.gz +source_hash = aa3e8fbb6a0066c03454434add1f1fc23299e85758ceec0d7d2d974431481e40 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_12.0.0-1/fmt-12.0.0.tar.gz +patch_filename = fmt_12.0.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_12.0.0-1/get_patch +patch_hash = 307f288ebf3850abf2f0c50ac1fb07de97df9538d39146d802f3c0d6cada8998 +wrapdb_version = 12.0.0-1 + +[provide] +dependency_names = fmt diff --git a/subprojects/libassert.wrap b/subprojects/libassert.wrap new file mode 100644 index 0000000..f6d6fbc --- /dev/null +++ b/subprojects/libassert.wrap @@ -0,0 +1,10 @@ +[wrap-file] +directory = libassert-2.2.1 +source_url = https://github.com/jeremy-rifkin/libassert/archive/refs/tags/v2.2.1.tar.gz +source_filename = libassert-2.2.1.tar.gz +source_hash = a7882ff2922c6d57f955f5ea9418014619e0855e936eb92e5443914dd1a8f724 +method = cmake + +[provide] +libassert = libassert_lib_dep +cpptrace = cpptrace_lib_dep diff --git a/subprojects/microsoft-gsl.wrap b/subprojects/microsoft-gsl.wrap new file mode 100644 index 0000000..852253b --- /dev/null +++ b/subprojects/microsoft-gsl.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = GSL-4.2.0 +source_url = https://github.com/microsoft/GSL/archive/v4.2.0.zip +source_filename = GSL-4.2.0.zip +source_hash = f694e7b0f3debc0c55fe138efb5dd9f5c402989c0dc04e8a74afc9c155cb66b9 +patch_filename = microsoft-gsl_4.2.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/microsoft-gsl_4.2.0-1/get_patch +patch_hash = d08afc6735731bb3dd10d84395423feebb6865ef6c9e78e522c8764fb9f0d9d1 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/microsoft-gsl_4.2.0-1/GSL-4.2.0.zip +wrapdb_version = 4.2.0-1 + +[provide] +microsoft_gsl = microsoft_gsl_dep diff --git a/subprojects/packagefiles/range-v3/meson.build b/subprojects/packagefiles/range-v3/meson.build new file mode 100644 index 0000000..5754474 --- /dev/null +++ b/subprojects/packagefiles/range-v3/meson.build @@ -0,0 +1,14 @@ +# Modified from mesonbuild/WrapDB to disclude tests, cause why would I want those + +project( + 'range-v3', + 'cpp', + version: '0.12.0', + license: 'Boost, libc++, Stepanov and McJones "Elements of Programming", SGI STL', +) + +range_inc = include_directories('include') + +range_dep = declare_dependency( + include_directories: range_inc, +) diff --git a/subprojects/range-v3.wrap b/subprojects/range-v3.wrap new file mode 100644 index 0000000..90213c4 --- /dev/null +++ b/subprojects/range-v3.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = range-v3-0.12.0 +source_url = https://github.com/ericniebler/range-v3/archive/0.12.0.tar.gz +source_filename = range-v3-0.12.0.tar.gz +source_hash = 015adb2300a98edfceaf0725beec3337f542af4915cec4d0b89fa0886f4ba9cb +patch_directory = range-v3 + +[provide] +range-v3 = range_dep diff --git a/subprojects/spdlog.wrap b/subprojects/spdlog.wrap new file mode 100644 index 0000000..950b2f7 --- /dev/null +++ b/subprojects/spdlog.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = spdlog-1.15.3 +source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.15.3.tar.gz +source_filename = spdlog-1.15.3.tar.gz +source_hash = 15a04e69c222eb6c01094b5c7ff8a249b36bb22788d72519646fb85feb267e67 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.15.3-5/spdlog-1.15.3.tar.gz +patch_filename = spdlog_1.15.3-5_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.15.3-5/get_patch +patch_hash = 5e0eaf0002ff589cd8dac58e1b38c297422e7a0404d7d47ff0d2e285ed18169c +wrapdb_version = 1.15.3-5 + +[provide] +dependency_names = spdlog From 857e51cb7be4722f1273668d44ef654cbe3aaa76 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 15:15:10 -0800 Subject: [PATCH 11/41] update .clang-format to permit angled for "main" header --- .clang-format | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.clang-format b/.clang-format index d776995..aba8d06 100644 --- a/.clang-format +++ b/.clang-format @@ -29,6 +29,9 @@ AllowShortFunctionsOnASingleLine: Inline SortIncludes: CaseInsensitive +# Permit quotes ("") or angled brackets (<>) for the "main" include header +MainIncludeChar: Any + # Categories: # Main header (top) # Project headers From d9e1d05638ffe7784999e2351ac864def781ec5a Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 15:22:47 -0800 Subject: [PATCH 12/41] add CLEANUP.md for todos --- CLEANUP.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CLEANUP.md diff --git a/CLEANUP.md b/CLEANUP.md new file mode 100644 index 0000000..1e5eecd --- /dev/null +++ b/CLEANUP.md @@ -0,0 +1,7 @@ +## Meson To-Do's + +- subprojects should be system includes +- libassert should make use of in-project cpptrace +- add tests directory +- get cross-compilation working + - with **docker** From 7e6481148fb79c4a3b76e16df7b90fe33480d7b9 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 15:23:07 -0800 Subject: [PATCH 13/41] set default c++ standard to 20 --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 094f6a9..546077e 100644 --- a/meson.build +++ b/meson.build @@ -9,7 +9,8 @@ project( meson_version : '>=0.55.0', default_options : [ 'warning_level=3', - 'default_library=static' + 'default_library=static', + 'cpp_std=c++20', ], ) From c49875054ecd133473f7ae16717b413b9b1ce6ed Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 16:28:44 -0800 Subject: [PATCH 14/41] meson.build now functional; add meson_options.txt --- CLEANUP.md | 4 ++- include/asmgrader/config.hpp.in | 19 +++++++++++ include/asmgrader/meson.build | 11 ++++++ include/meson.build | 3 ++ meson.build | 60 ++++++++++++++++++++++----------- meson_options.txt | 10 ++++++ 6 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 include/asmgrader/config.hpp.in create mode 100644 include/asmgrader/meson.build create mode 100644 meson_options.txt diff --git a/CLEANUP.md b/CLEANUP.md index 1e5eecd..3b1705f 100644 --- a/CLEANUP.md +++ b/CLEANUP.md @@ -1,7 +1,9 @@ ## Meson To-Do's -- subprojects should be system includes +- subprojects should be system includes DONE - libassert should make use of in-project cpptrace - add tests directory - get cross-compilation working - with **docker** +- generate version_macros.hpp DONE + - should rename to version_config DONE diff --git a/include/asmgrader/config.hpp.in b/include/asmgrader/config.hpp.in new file mode 100644 index 0000000..4e56b69 --- /dev/null +++ b/include/asmgrader/config.hpp.in @@ -0,0 +1,19 @@ +#pragma once + +// IWYU pragma: private, include "version.hpp" + +// @ident@ expressions are substituted by CMake -- clang-format tries to reformat +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// clang-format off +#define ASMGRADER_VERSION_MAJOR @ASMGRADER_VERSION_MAJOR@ +#define ASMGRADER_VERSION_MINOR @ASMGRADER_VERSION_MINOR@ +#define ASMGRADER_VERSION_PATCH @ASMGRADER_VERSION_PATCH@ +#define ASMGRADER_VERSION_GIT_HASH_STR "@ASMGRADER_VERSION_GIT_HASH@" +#define ASMGRADER_VERSION_BUILD_TYPE_STR "@ASMGRADER_BUILD_TYPE@" +/// A string for version info for a given implementation using this library +/// Should be of the form " " where the only ' ' (space) token is between and +/// Example: "CS150Grader 1.2.3" +#define ASMGRADER_IMPL_VERSION_INFO "@ASMGRADER_IMPL_VERSION_INFO@" +#define ASMGRADER_DEPENDENCY_VERSIONS_STR "@ASMGRADER_DEPENDENCY_VERSIONS_STR@" +// clang-format on +// NOLINTEND(cppcoreguidelines-macro-usage) diff --git a/include/asmgrader/meson.build b/include/asmgrader/meson.build new file mode 100644 index 0000000..9c25f45 --- /dev/null +++ b/include/asmgrader/meson.build @@ -0,0 +1,11 @@ +# ============================================================================== +# Configure version info +# +# This file's sole purpose is to generate config.hpp from config.hpp.in +# ============================================================================== + +configure_file( + input: 'config.hpp.in', + output: 'config.hpp', + configuration: asmgrader_conf_data +) diff --git a/include/meson.build b/include/meson.build index 2b3ca86..5dbd081 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1 +1,4 @@ asmgrader_public_inc = include_directories('.') + +# This subdir's only purpose is to generate config.hpp +subdir('asmgrader') diff --git a/meson.build b/meson.build index 546077e..8acf730 100644 --- a/meson.build +++ b/meson.build @@ -44,7 +44,7 @@ boost_subproj = cmake.subproject('boost', ) boost_dep = [] foreach mod : boost_module_names - boost_dep += [boost_subproj.dependency('boost_' + mod)] + boost_dep += [boost_subproj.dependency('boost_' + mod, include_type: 'system')] endforeach # ========= @@ -52,57 +52,85 @@ endforeach # # for command line argument parsing # ========= -argparse_dep = dependency('argparse') +argparse_dep = dependency('argparse', include_type: 'system') # ========= -# spdlog +# fmt # -# for fast runtime logging +# for friendly string interpolation # ========= -spdlog_dep = dependency('spdlog') +fmt_dep = dependency('fmt', include_type: 'system') # ========= -# fmt +# spdlog # -# for friendly string interpolation +# for fast runtime logging # ========= -fmt_dep = dependency('fmt') +spdlog_dep = dependency( + 'spdlog', + default_options: ['std_format=disabled'] +) + # ========= # gsl (guidelines support library) # # for better code conformance # ========= -gsl_dep = dependency('microsoft_gsl') +gsl_dep = dependency('microsoft_gsl', include_type: 'system') # ========= # range-v3 # # for "modern" range-based patterns # ========= -rangev3_dep = dependency('range-v3') +rangev3_dep = dependency('range-v3', include_type: 'system') # ========= # elfio # # for parsing of ELF files # ========= -elfio_dep = dependency('elfio') +elfio_dep = dependency('elfio', include_type: 'system') # ========= # cpptrace # # for human-readable backtraces # ========= -cpptrace_dep = dependency('cpptrace') +cpptrace_dep = dependency('cpptrace', include_type: 'system') # ========= # libassert # # for human-readable assertions with tons of info # ========= -libasssert_dep = dependency('libassert') +libasssert_dep = dependency('libassert', include_type: 'system') +# ============================================================================== +# Configuration data +# ============================================================================== + +split_version = meson.project_version().split('.') +git_hash_cmd = run_command('git', 'log', '-n1', '--format=%H', capture: true, check: false) + +if git_hash_cmd.returncode() == 0 + git_hash_str = git_hash_cmd.stdout().strip() +else + git_hash_str = '' +endif + +# Used to define macros in the file version_config.hpp.in under include/ + +asmgrader_conf_data = configuration_data({ + 'ASMGRADER_VERSION_MAJOR': split_version[0], + 'ASMGRADER_VERSION_MINOR': split_version[1], + 'ASMGRADER_VERSION_PATCH': split_version[2], + 'ASMGRADER_VERSION_GIT_HASH': git_hash_str, + 'ASMGRADER_BUILD_TYPE': get_option('buildtype'), + 'ASMGRADER_IMPL_VERSION_INFO': get_option('impl_version_info'), + 'ASMGRADER_DEPENDENCY_VERSIONS_STR': '' # TODO +}) # ============================================================================== # Sub-Directories @@ -110,9 +138,3 @@ libasssert_dep = dependency('libassert') subdir('include') subdir('src') - -# project_target = executable( -# meson.project_name(), -# sources : project_sources, -# dependencies: [rangev3_dep, fmt_dep], -# ) diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..d99160c --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,10 @@ +option( + 'professor_mode', + type: 'boolean', + value: false, +) +option( + 'impl_version_info', + type: 'string', + value: '', +) From 4068ce5eb048a761745b4811821f92030470043b Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 16:29:40 -0800 Subject: [PATCH 15/41] adjust include paths to always use public paths --- include/asmgrader/version.hpp | 2 +- src/api/assignment.cpp | 4 +- src/api/process_statistics.cpp | 4 +- src/api/stringize.cpp | 4 +- src/api/syntax_highlighter.cpp | 6 +-- src/api/tempfile.cpp | 8 ++-- src/api/test_context.cpp | 44 +++++++++++----------- src/app/app.hpp | 3 +- src/app/professor_app.cpp | 11 +++--- src/app/professor_app.hpp | 3 +- src/app/student_app.cpp | 11 +++--- src/app/student_app.hpp | 3 +- src/app/trace_exception.hpp | 4 +- src/common/terminal_checks.cpp | 6 +-- src/common/terminal_checks.hpp | 2 +- src/common/time.hpp | 8 ++-- src/database_reader.cpp | 6 +-- src/database_reader.hpp | 4 +- src/main.cpp | 2 +- src/multi_student_runner.cpp | 4 +- src/multi_student_runner.hpp | 5 ++- src/output/plaintext_serializer.cpp | 24 ++++++------ src/output/plaintext_serializer.hpp | 7 ++-- src/output/serializer.hpp | 5 ++- src/output/verbosity.hpp | 2 +- src/program/program.cpp | 23 +++++------ src/registrars/global_registrar.cpp | 4 +- src/subprocess/memory/memory_io_base.cpp | 12 +++--- src/subprocess/memory/ptrace_memory_io.cpp | 9 +++-- src/subprocess/memory/ptrace_memory_io.hpp | 6 +-- src/subprocess/subprocess.cpp | 12 +++--- src/subprocess/traced_subprocess.cpp | 16 ++++---- src/subprocess/tracer.cpp | 33 ++++++++-------- src/symbols/elf_reader.cpp | 6 +-- src/symbols/elf_reader.hpp | 4 +- src/symbols/symbol_table.cpp | 4 +- src/test_runner.cpp | 19 +++++----- src/test_runner.hpp | 5 ++- src/user/assignment_file_searcher.cpp | 9 +++-- src/user/assignment_file_searcher.hpp | 5 ++- src/user/cl_args.cpp | 18 ++++----- src/user/cl_args.hpp | 3 +- src/user/file_searcher.cpp | 2 +- src/user/program_options.hpp | 10 ++--- src/version.cpp | 6 +-- 45 files changed, 203 insertions(+), 185 deletions(-) diff --git a/include/asmgrader/version.hpp b/include/asmgrader/version.hpp index ef670c9..f54cf13 100644 --- a/include/asmgrader/version.hpp +++ b/include/asmgrader/version.hpp @@ -5,7 +5,7 @@ #include #include #include -#include // IWYU pragma: export +#include // IWYU pragma: export #include #include diff --git a/src/api/assignment.cpp b/src/api/assignment.cpp index 82e1900..de83f32 100644 --- a/src/api/assignment.cpp +++ b/src/api/assignment.cpp @@ -1,6 +1,6 @@ -#include "api/assignment.hpp" +#include -#include "api/test_base.hpp" +#include #include #include diff --git a/src/api/process_statistics.cpp b/src/api/process_statistics.cpp index 1bad891..0269b1b 100644 --- a/src/api/process_statistics.cpp +++ b/src/api/process_statistics.cpp @@ -1,6 +1,6 @@ -#include "api/process_statistics.hpp" +#include -#include "logging.hpp" +#include #include diff --git a/src/api/stringize.cpp b/src/api/stringize.cpp index 7ff1060..4e35747 100644 --- a/src/api/stringize.cpp +++ b/src/api/stringize.cpp @@ -1,6 +1,6 @@ -#include "api/stringize.hpp" +#include -#include "api/syntax_highlighter.hpp" +#include #include diff --git a/src/api/syntax_highlighter.cpp b/src/api/syntax_highlighter.cpp index 5f54e8c..5c459f2 100644 --- a/src/api/syntax_highlighter.cpp +++ b/src/api/syntax_highlighter.cpp @@ -1,7 +1,7 @@ -#include "api/syntax_highlighter.hpp" +#include -#include "api/expression_inspection.hpp" -#include "logging.hpp" +#include +#include #include #include diff --git a/src/api/tempfile.cpp b/src/api/tempfile.cpp index d264ac5..fae099a 100644 --- a/src/api/tempfile.cpp +++ b/src/api/tempfile.cpp @@ -1,8 +1,8 @@ -#include "api/tempfile.hpp" +#include -#include "common/aliases.hpp" -#include "common/expected.hpp" -#include "logging.hpp" +#include +#include +#include #include #include diff --git a/src/api/test_context.cpp b/src/api/test_context.cpp index 63192b4..db088db 100644 --- a/src/api/test_context.cpp +++ b/src/api/test_context.cpp @@ -1,25 +1,25 @@ -#include "api/test_context.hpp" - -#include "api/asm_buffer.hpp" -#include "api/metadata.hpp" -#include "api/process_statistics.hpp" -#include "api/registers_state.hpp" -#include "api/requirement.hpp" -#include "api/test_base.hpp" -#include "common/aliases.hpp" -#include "common/bit_casts.hpp" -#include "common/byte_array.hpp" -#include "common/error_types.hpp" -#include "common/linux.hpp" -#include "common/macros.hpp" -#include "common/unreachable.hpp" -#include "exceptions.hpp" -#include "grading_session.hpp" -#include "logging.hpp" -#include "program/program.hpp" -#include "subprocess/run_result.hpp" -#include "subprocess/subprocess.hpp" -#include "subprocess/syscall_record.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/app/app.hpp b/src/app/app.hpp index 201fa66..c14ff08 100644 --- a/src/app/app.hpp +++ b/src/app/app.hpp @@ -1,7 +1,8 @@ #pragma once +#include + #include "app/trace_exception.hpp" -#include "common/class_traits.hpp" #include "user/program_options.hpp" #include diff --git a/src/app/professor_app.cpp b/src/app/professor_app.cpp index 4e5bbab..361f677 100644 --- a/src/app/professor_app.cpp +++ b/src/app/professor_app.cpp @@ -1,16 +1,17 @@ #include "app/professor_app.hpp" +#include +#include // IWYU pragma: keep +#include +#include +#include + #include "app/student_app.hpp" -#include "common/expected.hpp" -#include "common/extra_formatters.hpp" // IWYU pragma: keep #include "database_reader.hpp" -#include "grading_session.hpp" -#include "logging.hpp" #include "multi_student_runner.hpp" #include "output/plaintext_serializer.hpp" #include "output/stdout_sink.hpp" #include "output/verbosity.hpp" -#include "registrars/global_registrar.hpp" #include "user/assignment_file_searcher.hpp" #include "user/program_options.hpp" diff --git a/src/app/professor_app.hpp b/src/app/professor_app.hpp index bc606f6..07dc968 100644 --- a/src/app/professor_app.hpp +++ b/src/app/professor_app.hpp @@ -1,7 +1,8 @@ #pragma once +#include + #include "app/app.hpp" // IWYU pragma: export -#include "grading_session.hpp" #include #include diff --git a/src/app/student_app.cpp b/src/app/student_app.cpp index 391ce0d..93102c5 100644 --- a/src/app/student_app.cpp +++ b/src/app/student_app.cpp @@ -1,13 +1,14 @@ #include "app/student_app.hpp" -#include "api/assignment.hpp" -#include "common/linux.hpp" -#include "grading_session.hpp" -#include "logging.hpp" +#include +#include +#include +#include +#include + #include "output/plaintext_serializer.hpp" #include "output/stdout_sink.hpp" #include "output/verbosity.hpp" -#include "registrars/global_registrar.hpp" #include "test_runner.hpp" #include "user/program_options.hpp" diff --git a/src/app/student_app.hpp b/src/app/student_app.hpp index eabdf7a..68df7c0 100644 --- a/src/app/student_app.hpp +++ b/src/app/student_app.hpp @@ -1,6 +1,7 @@ #pragma once -#include "api/assignment.hpp" +#include + #include "app/app.hpp" // IWYU pragma: export namespace asmgrader { diff --git a/src/app/trace_exception.hpp b/src/app/trace_exception.hpp index 1060520..15c65f5 100644 --- a/src/app/trace_exception.hpp +++ b/src/app/trace_exception.hpp @@ -1,8 +1,8 @@ #pragma once -#include "common/extra_formatters.hpp" // IWYU pragma: keep +#include // IWYU pragma: keep -#include +// #include #include #include #include diff --git a/src/common/terminal_checks.cpp b/src/common/terminal_checks.cpp index 75f2370..2905a41 100644 --- a/src/common/terminal_checks.cpp +++ b/src/common/terminal_checks.cpp @@ -1,8 +1,8 @@ #include "common/terminal_checks.hpp" -#include "common/error_types.hpp" -#include "common/expected.hpp" -#include "common/linux.hpp" +#include +#include +#include #include diff --git a/src/common/terminal_checks.hpp b/src/common/terminal_checks.hpp index 49089d7..1836397 100644 --- a/src/common/terminal_checks.hpp +++ b/src/common/terminal_checks.hpp @@ -1,6 +1,6 @@ #pragma once -#include "common/expected.hpp" +#include #include diff --git a/src/common/time.hpp b/src/common/time.hpp index 794bd6a..8f0f2d9 100644 --- a/src/common/time.hpp +++ b/src/common/time.hpp @@ -1,7 +1,7 @@ #pragma once -#include "common/expected.hpp" -#include "logging.hpp" +#include +#include #include #include @@ -14,8 +14,8 @@ namespace asmgrader { __attribute__((format(strftime, 2, 0))) // help the compiler check `format` for validity -inline Expected -to_localtime_string(std::chrono::system_clock::time_point time_point, const char* format = "%Y-%m-%d %H:%M:%S") { +inline Expected to_localtime_string(std::chrono::system_clock::time_point time_point, + const char* format = "%Y-%m-%d %H:%M:%S") { // Convert to time_t (seconds since epoch) std::time_t time = std::chrono::system_clock::to_time_t(time_point); diff --git a/src/database_reader.cpp b/src/database_reader.cpp index a47ca5c..d26cdfe 100644 --- a/src/database_reader.cpp +++ b/src/database_reader.cpp @@ -1,8 +1,8 @@ #include "database_reader.hpp" -#include "common/expected.hpp" -#include "grading_session.hpp" -#include "logging.hpp" +#include +#include +#include #include #include diff --git a/src/database_reader.hpp b/src/database_reader.hpp index f85e8db..a22331b 100644 --- a/src/database_reader.hpp +++ b/src/database_reader.hpp @@ -1,7 +1,7 @@ #pragma once -#include "common/expected.hpp" -#include "grading_session.hpp" +#include +#include #include #include diff --git a/src/main.cpp b/src/main.cpp index f60fc8c..d9c10bd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,7 @@ #include "app/student_app.hpp" #endif // PROFESSOR_VERSION -#include "logging.hpp" +#include #include "user/cl_args.hpp" #include "user/program_options.hpp" diff --git a/src/multi_student_runner.cpp b/src/multi_student_runner.cpp index ded2d64..9f6e94b 100644 --- a/src/multi_student_runner.cpp +++ b/src/multi_student_runner.cpp @@ -1,7 +1,7 @@ #include "multi_student_runner.hpp" -#include "api/assignment.hpp" -#include "grading_session.hpp" +#include +#include #include "output/serializer.hpp" #include "test_runner.hpp" diff --git a/src/multi_student_runner.hpp b/src/multi_student_runner.hpp index d423da8..32849dc 100644 --- a/src/multi_student_runner.hpp +++ b/src/multi_student_runner.hpp @@ -1,7 +1,8 @@ #pragma once -#include "api/assignment.hpp" -#include "grading_session.hpp" +#include +#include + #include "output/serializer.hpp" #include diff --git a/src/output/plaintext_serializer.cpp b/src/output/plaintext_serializer.cpp index 03d480d..9abf764 100644 --- a/src/output/plaintext_serializer.cpp +++ b/src/output/plaintext_serializer.cpp @@ -1,21 +1,23 @@ #include "output/plaintext_serializer.hpp" -#include "api/expression_inspection.hpp" -#include "api/requirement.hpp" -#include "api/stringize.hpp" -#include "api/syntax_highlighter.hpp" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + #include "app/app.hpp" -#include "app_mode.hpp" -#include "common/overloaded.hpp" -#include "common/terminal_checks.hpp" #include "common/time.hpp" -#include "grading_session.hpp" -#include "logging.hpp" +#include "common/terminal_checks.hpp" +#include "user/program_options.hpp" #include "output/serializer.hpp" #include "output/sink.hpp" #include "output/verbosity.hpp" -#include "user/program_options.hpp" -#include "version.hpp" #include #include diff --git a/src/output/plaintext_serializer.hpp b/src/output/plaintext_serializer.hpp index 8dd9360..65cb8cd 100644 --- a/src/output/plaintext_serializer.hpp +++ b/src/output/plaintext_serializer.hpp @@ -1,8 +1,9 @@ #pragma once -#include "api/requirement.hpp" -#include "api/stringize.hpp" -#include "grading_session.hpp" +#include +#include +#include + #include "output/serializer.hpp" #include "output/sink.hpp" #include "output/verbosity.hpp" diff --git a/src/output/serializer.hpp b/src/output/serializer.hpp index 084072e..5b42ab6 100644 --- a/src/output/serializer.hpp +++ b/src/output/serializer.hpp @@ -1,7 +1,8 @@ #pragma once -#include "common/class_traits.hpp" -#include "grading_session.hpp" +#include +#include + #include "output/sink.hpp" #include "output/verbosity.hpp" #include "user/program_options.hpp" diff --git a/src/output/verbosity.hpp b/src/output/verbosity.hpp index d7ff5cb..896965b 100644 --- a/src/output/verbosity.hpp +++ b/src/output/verbosity.hpp @@ -1,6 +1,6 @@ #pragma once -#include "app_mode.hpp" +#include namespace asmgrader { diff --git a/src/program/program.cpp b/src/program/program.cpp index 5e67e26..68ad38a 100644 --- a/src/program/program.cpp +++ b/src/program/program.cpp @@ -1,15 +1,16 @@ -#include "program/program.hpp" - -#include "common/error_types.hpp" -#include "common/expected.hpp" -#include "common/os.hpp" -#include "logging.hpp" -#include "subprocess/run_result.hpp" -#include "subprocess/syscall_record.hpp" -#include "subprocess/traced_subprocess.hpp" -#include "subprocess/tracer.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "symbols/elf_reader.hpp" -#include "symbols/symbol_table.hpp" #include #include diff --git a/src/registrars/global_registrar.cpp b/src/registrars/global_registrar.cpp index d405005..3087020 100644 --- a/src/registrars/global_registrar.cpp +++ b/src/registrars/global_registrar.cpp @@ -1,6 +1,6 @@ -#include "registrars/global_registrar.hpp" +#include -#include "api/assignment.hpp" +#include #include #include diff --git a/src/subprocess/memory/memory_io_base.cpp b/src/subprocess/memory/memory_io_base.cpp index cc48322..76fbe4e 100644 --- a/src/subprocess/memory/memory_io_base.cpp +++ b/src/subprocess/memory/memory_io_base.cpp @@ -1,8 +1,8 @@ -#include "subprocess/memory/memory_io_base.hpp" +#include -#include "common/byte.hpp" -#include "common/byte_vector.hpp" -#include "common/error_types.hpp" +#include +#include +#include #include @@ -34,8 +34,8 @@ Result MemoryIOBase::read_until(std::uintptr_t address, const } Result MemoryIOBase::read_until(std::uintptr_t address, - const std::function)>& predicate, - std::size_t block_size) { + const std::function)>& predicate, + std::size_t block_size) { ASSERT(block_size > 0); NativeByteVector result; diff --git a/src/subprocess/memory/ptrace_memory_io.cpp b/src/subprocess/memory/ptrace_memory_io.cpp index 84af775..7fc6003 100644 --- a/src/subprocess/memory/ptrace_memory_io.cpp +++ b/src/subprocess/memory/ptrace_memory_io.cpp @@ -1,8 +1,8 @@ #include "subprocess/memory/ptrace_memory_io.hpp" -#include "common/byte_vector.hpp" -#include "common/error_types.hpp" -#include "common/linux.hpp" +#include +#include +#include #include #include @@ -84,7 +84,8 @@ Result PtraceMemoryIO::write_block_impl(std::uintptr_t address, const Nati } // TODO: Consolidate repeated code in this and other read fn -// NativeByteVector PtraceMemoryIO::read_until_impl(std::uintptr_t address, const std::function& predicate) { +// NativeByteVector PtraceMemoryIO::read_until_impl(std::uintptr_t address, const std::function& +// predicate) { // constexpr std::size_t MAX_SIZE = 10 * std::mega::num; // 10 MB // constexpr std::size_t INIT_SIZE = 128; // // TODO: Detetermine whether alignment logic is necessary diff --git a/src/subprocess/memory/ptrace_memory_io.hpp b/src/subprocess/memory/ptrace_memory_io.hpp index be93fc8..e437463 100644 --- a/src/subprocess/memory/ptrace_memory_io.hpp +++ b/src/subprocess/memory/ptrace_memory_io.hpp @@ -1,8 +1,8 @@ #pragma once -#include "common/byte_vector.hpp" -#include "common/error_types.hpp" -#include "subprocess/memory/memory_io_base.hpp" +#include +#include +#include #include #include diff --git a/src/subprocess/subprocess.cpp b/src/subprocess/subprocess.cpp index 7857a41..4ecd73e 100644 --- a/src/subprocess/subprocess.cpp +++ b/src/subprocess/subprocess.cpp @@ -1,10 +1,10 @@ -#include "subprocess/subprocess.hpp" +#include -#include "common/error_types.hpp" -#include "common/expected.hpp" -#include "common/linux.hpp" -#include "logging.hpp" -#include "subprocess/tracer_types.hpp" +#include +#include +#include +#include +#include #include #include diff --git a/src/subprocess/traced_subprocess.cpp b/src/subprocess/traced_subprocess.cpp index 3b35e52..8fa5453 100644 --- a/src/subprocess/traced_subprocess.cpp +++ b/src/subprocess/traced_subprocess.cpp @@ -1,11 +1,11 @@ -#include "subprocess/traced_subprocess.hpp" - -#include "common/error_types.hpp" -#include "logging.hpp" -#include "subprocess/run_result.hpp" -#include "subprocess/subprocess.hpp" -#include "subprocess/syscall_record.hpp" -#include "subprocess/tracer.hpp" +#include + +#include +#include +#include +#include +#include +#include #include diff --git a/src/subprocess/tracer.cpp b/src/subprocess/tracer.cpp index 25c5496..236f5f0 100644 --- a/src/subprocess/tracer.cpp +++ b/src/subprocess/tracer.cpp @@ -1,20 +1,21 @@ -#include "subprocess/tracer.hpp" - -#include "common/aliases.hpp" -#include "common/bit_casts.hpp" -#include "common/byte_vector.hpp" -#include "common/error_types.hpp" -#include "common/expected.hpp" -#include "common/extra_formatters.hpp" // IWYU pragma: keep -#include "common/linux.hpp" -#include "common/os.hpp" -#include "common/unreachable.hpp" -#include "logging.hpp" +#include + +#include +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include + #include "subprocess/memory/ptrace_memory_io.hpp" -#include "subprocess/run_result.hpp" -#include "subprocess/syscall.hpp" -#include "subprocess/syscall_record.hpp" -#include "subprocess/tracer_types.hpp" #include #include diff --git a/src/symbols/elf_reader.cpp b/src/symbols/elf_reader.cpp index 2ab4783..e6156e7 100644 --- a/src/symbols/elf_reader.cpp +++ b/src/symbols/elf_reader.cpp @@ -1,8 +1,8 @@ #include "symbols/elf_reader.hpp" -#include "logging.hpp" -#include "symbols/symbol.hpp" -#include "symbols/symbol_table.hpp" +#include +#include +#include #include #include diff --git a/src/symbols/elf_reader.hpp b/src/symbols/elf_reader.hpp index 609dbb6..8cc0162 100644 --- a/src/symbols/elf_reader.hpp +++ b/src/symbols/elf_reader.hpp @@ -1,7 +1,7 @@ #pragma once -#include "symbols/symbol.hpp" -#include "symbols/symbol_table.hpp" +#include +#include #include diff --git a/src/symbols/symbol_table.cpp b/src/symbols/symbol_table.cpp index e59f385..4867b17 100644 --- a/src/symbols/symbol_table.cpp +++ b/src/symbols/symbol_table.cpp @@ -1,6 +1,6 @@ -#include "symbols/symbol_table.hpp" +#include -#include "logging.hpp" +#include #include #include diff --git a/src/test_runner.cpp b/src/test_runner.cpp index e9982b2..4797b73 100644 --- a/src/test_runner.cpp +++ b/src/test_runner.cpp @@ -1,15 +1,16 @@ #include "test_runner.hpp" -#include "api/assignment.hpp" -#include "api/test_base.hpp" -#include "api/test_context.hpp" -#include "app_mode.hpp" -#include "exceptions.hpp" -#include "grading_session.hpp" -#include "logging.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "output/serializer.hpp" -#include "program/program.hpp" -#include "version.hpp" #include #include diff --git a/src/test_runner.hpp b/src/test_runner.hpp index d837a24..84e5dff 100644 --- a/src/test_runner.hpp +++ b/src/test_runner.hpp @@ -1,7 +1,8 @@ #pragma once -#include "api/test_base.hpp" -#include "grading_session.hpp" +#include +#include + #include "output/serializer.hpp" #include diff --git a/src/user/assignment_file_searcher.cpp b/src/user/assignment_file_searcher.cpp index 02b7fd1..632238f 100644 --- a/src/user/assignment_file_searcher.cpp +++ b/src/user/assignment_file_searcher.cpp @@ -1,9 +1,10 @@ #include "user/assignment_file_searcher.hpp" -#include "api/assignment.hpp" -#include "common/cconstexpr.hpp" -#include "grading_session.hpp" -#include "logging.hpp" +#include +#include +#include +#include + #include "user/file_searcher.hpp" #include diff --git a/src/user/assignment_file_searcher.hpp b/src/user/assignment_file_searcher.hpp index 9922846..aaa8945 100644 --- a/src/user/assignment_file_searcher.hpp +++ b/src/user/assignment_file_searcher.hpp @@ -1,7 +1,8 @@ #pragma once -#include "api/assignment.hpp" -#include "grading_session.hpp" +#include +#include + #include "user/file_searcher.hpp" #include diff --git a/src/user/cl_args.cpp b/src/user/cl_args.cpp index f306b0b..330f329 100644 --- a/src/user/cl_args.cpp +++ b/src/user/cl_args.cpp @@ -1,17 +1,17 @@ #include "cl_args.hpp" -#include "api/assignment.hpp" -#include "app_mode.hpp" -#include "common/expected.hpp" -#include "common/os.hpp" -#include "common/static_string.hpp" +#include +#include +#include +#include +#include #include "common/terminal_checks.hpp" -#include "grading_session.hpp" -#include "logging.hpp" +#include +#include #include "output/verbosity.hpp" -#include "registrars/global_registrar.hpp" +#include #include "user/program_options.hpp" -#include "version.hpp" +#include #include #include diff --git a/src/user/cl_args.hpp b/src/user/cl_args.hpp index 188fd61..fedb293 100644 --- a/src/user/cl_args.hpp +++ b/src/user/cl_args.hpp @@ -1,6 +1,7 @@ #pragma once -#include "common/expected.hpp" +#include + #include "user/program_options.hpp" #include diff --git a/src/user/file_searcher.cpp b/src/user/file_searcher.cpp index e28f205..14f331e 100644 --- a/src/user/file_searcher.cpp +++ b/src/user/file_searcher.cpp @@ -1,6 +1,6 @@ #include "user/file_searcher.hpp" -#include "logging.hpp" +#include #include diff --git a/src/user/program_options.hpp b/src/user/program_options.hpp index 655b653..57b4258 100644 --- a/src/user/program_options.hpp +++ b/src/user/program_options.hpp @@ -2,13 +2,13 @@ #include #include +#include +#include +#include +#include +#include -#include "app_mode.hpp" -#include "common/error_types.hpp" -#include "common/expected.hpp" -#include "logging.hpp" #include "output/verbosity.hpp" -#include "program/program.hpp" #include "user/assignment_file_searcher.hpp" #include diff --git a/src/version.cpp b/src/version.cpp index cdcfe1d..2f5b27a 100644 --- a/src/version.cpp +++ b/src/version.cpp @@ -1,9 +1,9 @@ // ccache:disable // Rational: __DATE__ and __TIME__ macros should correspond to actual build date and time -#include "version.hpp" +#include -#include "common/os.hpp" -#include "common/static_string.hpp" +#include +#include #include #include From 3ff2bafd8721f65eb3c7afbd855f4ab5929c84e1 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 17:57:46 -0800 Subject: [PATCH 16/41] update src/meson.build --- src/meson.build | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/meson.build b/src/meson.build index 3ffb32c..a97816d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,4 @@ -core_link_libs_public = ['fmt::fmt', 'spdlog', 'Boost::endian', 'Boost::type_index', 'Boost::mp11', 'Boost::pfr', 'Boost::describe', 'Boost::stacktrace', 'Boost::preprocessor', 'range-v3', 'Microsoft.GSL::GSL', 'libassert::assert'] +# core_link_libs_public = ['fmt::fmt', 'spdlog', 'Boost::endian', 'Boost::type_index', 'Boost::mp11', 'Boost::pfr', 'Boost::describe', 'Boost::stacktrace', 'Boost::preprocessor', 'range-v3', 'Microsoft.GSL::GSL', 'libassert::assert'] # core_link_libs_private = ['asmgrader_options', 'asmgrader_warnings', 'argparse', 'nlohmann_json::nlohmann_json', 'elfio::elfio'] asmgrader_private_inc = include_directories('.') @@ -64,6 +64,7 @@ asmgrader_target = library( asmgrader_dep = declare_dependency( include_directories: asmgrader_public_inc, link_with: asmgrader_target, + dependencies: asmgrader_deps, ) asmgrader_with_main_target = library( @@ -75,4 +76,5 @@ asmgrader_with_main_target = library( asmgrader_with_main_dep = declare_dependency( include_directories: asmgrader_public_inc, link_with: asmgrader_with_main_target, + dependencies: asmgrader_deps, ) From e441a7a2d9f1c19d0080e39ea94fcc7519f16eee Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 17:57:58 -0800 Subject: [PATCH 17/41] update catch2 wrap with older version --- subprojects/catch2.wrap | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap index 96c1e10..c46a2d0 100644 --- a/subprojects/catch2.wrap +++ b/subprojects/catch2.wrap @@ -1,10 +1,8 @@ [wrap-file] -directory = Catch2-3.11.0 -source_url = https://github.com/catchorg/Catch2/archive/v3.11.0.tar.gz -source_filename = Catch2-3.11.0.tar.gz -source_hash = 82fa1cb59dc28bab220935923f7469b997b259eb192fb9355db62da03c2a3137 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.11.0-1/Catch2-3.11.0.tar.gz -wrapdb_version = 3.11.0-1 +directory = Catch2-3.8.1 +source_url = https://github.com/catchorg/Catch2/archive/refs/tags/v3.8.1.tar.gz +source_filename = Catch2-3.8.1.tar.gz +source_hash = 18b3f70ac80fccc340d8c6ff0f339b2ae64944782f8d2fca2bd705cf47cadb79 [provide] catch2 = catch2_dep From c724abe35846fd61d2f5beeb70dc8ccd2afe8c0b Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 17:58:20 -0800 Subject: [PATCH 18/41] add tests subdir for meson --- meson.build | 14 ++++++- tests/meson.build | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 tests/meson.build diff --git a/meson.build b/meson.build index 8acf730..75b26d8 100644 --- a/meson.build +++ b/meson.build @@ -20,9 +20,9 @@ project( # ========= # Boost +# # ...is *special* # ========= - boost_module_names = [ 'endian', 'type_index', @@ -107,6 +107,14 @@ cpptrace_dep = dependency('cpptrace', include_type: 'system') # ========= libasssert_dep = dependency('libassert', include_type: 'system') +# ========= +# Catch2 +# +# for testing +# ========= +catch2_dep = dependency('catch2', include_type: 'system') + + # ============================================================================== # Configuration data # ============================================================================== @@ -138,3 +146,7 @@ asmgrader_conf_data = configuration_data({ subdir('include') subdir('src') + +if not meson.is_subproject() + subdir('tests') +endif diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..03911e1 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,96 @@ +tests_srcs = [ + 'test_static_expected.cpp', + 'test_static_formatters.cpp', + 'test_static_tuple_matcher.cpp', + 'test_expression_inspection.cpp', + 'test_syntax_highlighter.cpp', + 'test_cconstexpr.cpp', + 'test_static_string.cpp', + 'test_static_byte.cpp', + 'test_bit_casts.cpp', + 'test_metadata.cpp', + 'test_subprocess.cpp', + 'test_expected.cpp', + 'test_symbol_reader.cpp', + 'test_memory_io.cpp', + 'test_program.cpp', + 'test_database_reader.cpp', + 'test_registers_state.cpp', + 'test_file_searcher.cpp', + 'test_byte_ranges.cpp', + 'test_tempfile.cpp', +] + +static_tests_srcs = [] + +fs = import('fs') +foreach t : tests_srcs + file_content = fs.read(t, encoding: 'utf-8') + if file_content.contains('STATIC_REQUIRE') + static_tests_srcs += [t] + endif +endforeach + + +inc = include_directories('../src') + +# ##### Simple assembly executable +# # add_asm_arch_specific_impl(['asm_tests', 'resources/simple_asm']) +# +# asm_tests_weird_name_exec = '(asm_tests () weird (name) ~`' +# # add_custom_command(['TARGET', 'asm_tests', 'POST_BUILD', 'COMMAND', 'cp', '$', '${asm_tests_weird_name_exec}', 'VERBATIM']) +# ##### Test runner executable + +asmgrader_tests_target = executable( + 'asmgrader_tests', + sources: ['catch2_custom.hpp', tests_srcs, 'catch_main.cpp'], + dependencies: [catch2_dep, asmgrader_dep], + include_directories: inc, + cpp_args: ['-DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE'], +) + +# # add_dependencies(['asmgrader_tests', 'asm_tests']) +# # target_compile_definitions(['asmgrader_tests', 'PUBLIC', 'CATCH_CONFIG_RUNTIME_STATIC_REQUIRE', 'PRIVATE', 'ASM_TESTS_EXEC=\"$\"', 'RESOURCES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/resources\"', 'ASM_TESTS_WEIRD_NAME_EXEC=R\"abc(${asm_tests_weird_name_exec})abc\"']) +# # target_link_libraries(['asmgrader_tests', 'PUBLIC', 'asmgrader_core', 'PRIVATE', 'Catch2::Catch2', 'asmgrader_core_interface', 'asmgrader_warnings']) +# # TODO: Migh want to look into changing this later. +# # Just attempt to compile all tests in the constexpr version for simplicity, +# # since there really aren't that many. +# asmgrader_constexpr_tests_exe = executable('asmgrader_constexpr_tests', 'EXCLUDE_FROM_ALL', tests_srcs) +# # target_link_libraries(['asmgrader_constexpr_tests', 'PUBLIC', 'asmgrader_core', 'PRIVATE', 'Catch2::Catch2WithMain', 'asmgrader_core_interface', 'asmgrader_warnings']) +# asm_tests_weird_name_exec = '${CMAKE_CURRENT_BINARY_DIR}/(asm_tests () weird \\(name) ~`' +# # add_custom_command(['TARGET', 'asmgrader_tests', 'POST_BUILD', 'COMMAND', 'cp', '$', '${asm_tests_weird_name_exec}', 'VERBATIM']) +# # target_compile_definitions(['asmgrader_constexpr_tests', 'PRIVATE', 'ASM_TESTS_EXEC=\"$\"', 'RESOURCES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/resources\"', 'ASM_TESTS_WEIRD_NAME_EXEC=R\"abc(${asm_tests_weird_name_exec})abc\"']) +# ##### Built executables of the CLI library with no user-defined student tests +# # Used to check whether basic flags (--help and --version) work properly +# asmgrader_dumb_cli_exe = executable('asmgrader_dumb_cli', 'dumb_assignment.cpp') +# # target_compile_definitions(['asmgrader_dumb_cli', 'PRIVATE', 'ASM_TESTS_EXEC=\"$\"']) +# # target_link_libraries(['asmgrader_dumb_cli', 'PRIVATE', 'asmgrader_core_with_main', 'asmgrader_warnings']) +# asmgrader_dumb_profcli_exe = executable('asmgrader_dumb_profcli', 'dumb_assignment.cpp') +# # target_compile_definitions(['asmgrader_dumb_profcli', 'PRIVATE', 'ASM_TESTS_EXEC=\"$\"']) +# # target_link_libraries(['asmgrader_dumb_profcli', 'PRIVATE', 'asmgrader_prof_core_with_main', 'asmgrader_warnings']) +# # Provide a simple smoke test to make sure that the CLI works and can display a --help message +# test('NAME', 'cli.has_help', 'COMMAND', 'asmgrader_dumb_cli', '--help') +# test('NAME', 'prof_cli.has_help', 'COMMAND', 'asmgrader_dumb_profcli', '--help') +# # Provide a test to verify that the version being reported from the application +# # matches the version given to CMake. This will be important once you package +# # your program. Real world shows that this is the kind of simple mistake that is easy +# # to make, but also easy to test for. +# test('NAME', 'cli.version_matches', 'COMMAND', 'asmgrader_dumb_cli', '--version') +# test('NAME', 'prof_cli.version_matches', 'COMMAND', 'asmgrader_dumb_profcli', '--version') +# # set_tests_properties(['cli.version_matches', 'prof_cli.version_matches', 'PROPERTIES', 'PASS_REGULAR_EXPRESSION', '${ASMGRADER_VERSION}']) +# # Provide a simple smoke test to verify that the simple "dumb" tests all pass +# # Verify when both inferring file name and when specifying +# test('NAME', 'cli.dumb_tests_pass', 'COMMAND', 'asmgrader_dumb_cli', 'thing', '--file', '$') +# test('NAME', 'prof_cli.dumb_tests_pass', 'COMMAND', 'asmgrader_dumb_profcli', 'thing', '--file', '$') +# test('NAME', 'cli.infer_exec_name', 'COMMAND', 'asmgrader_dumb_cli', 'thing', 'WORKING_DIRECTORY', cmake_current_binary_dir) +# test('NAME', 'prof_cli.infer_exec_name', 'COMMAND', 'asmgrader_dumb_profcli', 'thing', 'WORKING_DIRECTORY', cmake_current_binary_dir) +# # Provide a simple smoke test to verify that the student mode is capable of inferring the lab name based on the exec +# test('NAME', 'cli.infer_assignment', 'COMMAND', 'asmgrader_dumb_cli', 'WORKING_DIRECTORY', cmake_current_binary_dir) +# # add_dependencies(['asmgrader_tests', 'asmgrader_dumb_cli', 'asmgrader_dumb_profcli']) +# # !!DISCLAIMER!! +# # This was adapted from the following source +# # cpp-best-practices/cmake_template +# # https://github.com/cpp-best-practices/cmake_template/tree/1015c6b88410df411c0cc0413e3e64c33d7a8331 +# # Courtesy of Jason Turner +# # catch_discover_tests(['asmgrader_tests', 'TEST_PREFIX', 'unittests.', 'REPORTER', 'JUnit', 'OUTPUT_DIR', asmgrader_source_dir, '/reports', 'OUTPUT_PREFIX', 'junit-unittests.', 'OUTPUT_SUFFIX', '.xml', 'EXTRA_ARGS', '--reporter console::out=-', '--invisibles']) +# test('NAME', 'asmgrader_constexpr_tests', 'COMMAND', cmake_make_program, 'asmgrader_constexpr_tests', 'WORKING_DIRECTORY', asmgrader_binary_dir) From b2000464b94a6c5f8bea9125311bbb8d1388e523 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 17:58:26 -0800 Subject: [PATCH 19/41] update cleanup.md --- CLEANUP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CLEANUP.md b/CLEANUP.md index 3b1705f..e3740ba 100644 --- a/CLEANUP.md +++ b/CLEANUP.md @@ -7,3 +7,4 @@ - with **docker** - generate version_macros.hpp DONE - should rename to version_config DONE +- add extra_warnings option From 45bcd329ea9160c5f57d5908b5e4c5af7c578891 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 23:10:29 -0800 Subject: [PATCH 20/41] add doxygen-awesome-css submodule for docs --- .gitmodules | 3 +++ docs/doxygen-awesome-css | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 docs/doxygen-awesome-css diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..29a7443 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/doxygen-awesome-css"] + path = docs/doxygen-awesome-css + url = https://github.com/jothepro/doxygen-awesome-css diff --git a/docs/doxygen-awesome-css b/docs/doxygen-awesome-css new file mode 160000 index 0000000..1f36200 --- /dev/null +++ b/docs/doxygen-awesome-css @@ -0,0 +1 @@ +Subproject commit 1f3620084ff75734ed192101acf40e9dff01d848 From cbb744586949a9fae6d31ca5c9d5889bebf5ebbd Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Fri, 28 Nov 2025 23:10:40 -0800 Subject: [PATCH 21/41] add meson.build for docs --- docs/Doxyfile.in | 2987 ++++++++++++++++++++++++++++++++++++++++++++++ docs/meson.build | 51 + 2 files changed, 3038 insertions(+) create mode 100644 docs/Doxyfile.in create mode 100644 docs/meson.build diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 0000000..3646a17 --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2987 @@ +# Doxyfile 1.14.0 + +# This file describes the settings to be used by the documentation system +# Doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = @DOXYGEN_PROJECT_NAME@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = @DOXYGEN_PROJECT_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewers a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where Doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@ + +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise cause +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by Doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which Doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where Doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful if your file system doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot, question mark or exclamation mark) of a +# Javadoc-style comment as the brief description. If set to NO, the Javadoc- +# style will behave just like regular Qt-style comments (thus requiring an +# explicit @brief command for a brief description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by Doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot, question mark or exclamation mark) of a Qt-style +# comment as the brief description. If set to NO, the Qt-style will behave just +# like regular Qt-style comments (thus requiring an explicit \brief command for +# a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = YES + +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make Doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 6 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. Words listed in the +# AUTOLINK_IGNORE_WORDS tag are excluded from automatic linking. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# This tag specifies a list of words that, when matching the start of a word in +# the documentation, will suppress auto links generation, if it is enabled via +# AUTOLINK_SUPPORT. This list does not affect links explicitly created using \# +# or the \link or commands. +# This tag requires that the tag AUTOLINK_SUPPORT is set to YES. + +AUTOLINK_IGNORE_WORDS = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let Doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# Doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then Doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run Doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_UNDOC_NAMESPACES tag is set to YES, Doxygen will hide all +# undocumented namespaces that are normally visible in the namespace hierarchy. +# If set to NO, these namespaces will be included in the various overviews. This +# option has no effect if EXTRACT_ALL is enabled. +# The default value is: YES. + +HIDE_UNDOC_NAMESPACES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# Doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by Doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by Doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents Doxygen's defaults, run Doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by Doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If WARN_LAYOUT_FILE option is set to YES, Doxygen will warn about issues found +# while parsing the user defined layout file, such as missing or wrong elements. +# See also LAYOUT_FILE for details. If set to NO, problems with the layout file +# will be suppressed. +# The default value is: YES. + +WARN_LAYOUT_FILE = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOXYGEN_INPUT@ + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.ccm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which Doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = @DOXYGEN_EXCLUDE_PATTERNS@ + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = @DOXYGEN_IMAGE_PATH@ + +# The INPUT_FILTER tag can be used to specify a program that Doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the Doxygen output. + +USE_MDFILE_AS_MAINPAGE = @DOXYGEN_USE_MDFILE_AS_MAINPAGE@ + +# If the IMPLICIT_DIR_DOCS tag is set to YES, any README.md file found in sub- +# directories of the project's root, is used as the documentation for that sub- +# directory, except when the README.md starts with a \dir, \page or \mainpage +# command. If set to NO, the README.md file needs to start with an explicit \dir +# command in order to be used as directory documentation. +# The default value is: YES. + +IMPLICIT_DIR_DOCS = YES + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables directly into the +# documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by Doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank Doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that Doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that Doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = @DOXYGEN_HTML_HEADER@ + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank Doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that Doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank Doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that Doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by Doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = @DOXYGEN_HTML_EXTRA_STYLESHEET@ + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = NO + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline (the HTML help workshop was already many +# years in maintenance mode). You can download the HTML help workshop from the +# web archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# Doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has more details information than the tab index, you +# could consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When GENERATE_TREEVIEW is set to YES, the PAGE_OUTLINE_PANEL option determines +# if an additional navigation panel is shown at the right hand side of the +# screen, displaying an outline of the contents of the main page, similar to +# e.g. https://developer.android.com/reference If GENERATE_TREEVIEW is set to +# NO, this option has no effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +PAGE_OUTLINE_PANEL = NO + +# When GENERATE_TREEVIEW is set to YES, the FULL_SIDEBAR option determines if +# the side bar is limited to only the treeview area (value NO) or if it should +# extend to the full height of the window (value YES). Setting this to YES gives +# a layout similar to e.g. https://docs.readthedocs.io with more room for +# contents, but less room for the project logo, title, and description. If +# GENERATE_TREEVIEW is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# Doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# Doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the JavaScript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /