From 85e17d724d927cfc03c738a44447c00e805957e9 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sat, 18 Oct 2025 17:24:14 -0700 Subject: [PATCH 01/12] chore: huge improvements to build scripts; Makefile deprecated --- .gitignore | 4 + CMakePresets.json | 671 ++++++++++++++++---- Dockerfile.aarch64 => Dockerfile | 3 +- Makefile | 126 ---- scripts/build | 160 +++++ scripts/debug | 408 ++++++++++++ scripts/debug.sh | 38 -- scripts/generate-presets | 211 ++++++ scripts/{install-hooks.sh => install-hooks} | 0 scripts/lint | 166 +++++ scripts/lint.sh | 63 -- scripts/stats | 10 + scripts/utest | 109 ++++ 13 files changed, 1617 insertions(+), 352 deletions(-) rename Dockerfile.aarch64 => Dockerfile (96%) 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/.gitignore b/.gitignore index f53ef56..f1997a2 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 +.direnv .env .venv env/ diff --git a/CMakePresets.json b/CMakePresets.json index 61565e2..9c3c63a 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": "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-debug", - "inherits": ["unixlike-gcc-debug", "aarch64-overlay"], - "displayName": "gcc Debug (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": "aarch64-gcc-release", - "inherits": ["unixlike-gcc-release", "aarch64-overlay"], - "displayName": "gcc Release (AArch64)" + "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,65 +230,75 @@ ], "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": "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": "unixlike-gcc-make-release", + "displayName": "Build: target Unix-like OS using gcc and make in release mode", + "configurePreset": "unixlike-gcc-make-release" + }, + { + "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-gcc-debug", - "inherits": "unixlike-gcc-debug", - "configurePreset": "aarch64-gcc-debug" + "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-gcc-release", - "inherits": "unixlike-gcc-release", - "configurePreset": "aarch64-gcc-release" + "name": "unixlike-clang-ninja-release", + "displayName": "Build: target Unix-like OS using clang and ninja in release mode", + "configurePreset": "unixlike-clang-ninja-release" }, { - "name": "aarch64-clang-debug", - "inherits": "unixlike-clang-debug", - "configurePreset": "aarch64-clang-debug" + "name": "unixlike-clang-make-debug", + "displayName": "Build: target Unix-like OS using clang and make in debug mode", + "configurePreset": "unixlike-clang-make-debug" }, { - "name": "aarch64-clang-release", - "inherits": "unixlike-clang-release", - "configurePreset": "aarch64-clang-release" + "name": "unixlike-clang-make-release", + "displayName": "Build: target Unix-like OS using clang and make in release mode", + "configurePreset": "unixlike-clang-make-release" }, { - "name": "aarch64-docs-only", - "inherits": "unixlike-docs-only", - "configurePreset": "unixlike-docs-only" + "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": { @@ -200,52 +309,366 @@ } }, { - "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-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": "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-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": "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": "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": "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": "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-release", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-gcc-release" + "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-clang-debug", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-clang-debug" + "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-clang-release", - "displayName": "Strict", - "description": "Enable output and stop on failure", - "inherits": "test-common", - "configurePreset": "unixlike-clang-release" + "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": "aarch64-gcc-debug", - "inherits": "test-common", - "configurePreset": "aarch64-gcc-debug" + "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": "aarch64-gcc-release", - "inherits": "test-common", - "configurePreset": "aarch64-gcc-release" + "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": "aarch64-clang-debug", - "inherits": "test-common", - "configurePreset": "aarch64-clang-debug" + "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": "aarch64-clang-release", - "inherits": "test-common", - "configurePreset": "aarch64-clang-release" + "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" + } + ] } ] -} +} \ No newline at end of file diff --git a/Dockerfile.aarch64 b/Dockerfile similarity index 96% rename from Dockerfile.aarch64 rename to Dockerfile index a5aa073..a5da81a 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 \ 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/scripts/build b/scripts/build new file mode 100755 index 0000000..53dc2aa --- /dev/null +++ b/scripts/build @@ -0,0 +1,160 @@ +#!/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" + ;; + *) + >&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="${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 ./build:/workspace/build"$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..19e7896 --- /dev/null +++ b/scripts/generate-presets @@ -0,0 +1,211 @@ +#!/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 + }, + "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" + } +] + +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) + ] +} + +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..f3d7674 --- /dev/null +++ b/scripts/utest @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +# +# Util script to simplify running tests +# + +usage() { + cat <&2 + +valid_build_types=("release" "debug") +valid_compilers=("gcc" "clang") +valid_generators=("ninja" "make") + +expand() { + eval "IFS='|'; echo \"\${$1[*]}\"" +} + +cmd="cmake" + +build_type="debug" +compiler="gcc" +generator="ninja" +fresh= +no_build= +jobs=${JOBS-$(nproc)} + +cmake_args=() + +# Process all args +until [[ -z $1 ]]; 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 + ;; + -j | --jobs) + if ! [[ $2 =~ ^[0-9]+$ ]]; then + >&2 echo 'Specified JOBS arg is invalid' + exit 1 + fi + jobs="$2" + shift + ;; + *) + if [[ $opt =~ $(expand valid_build_types) ]]; then + build_type="$opt" + elif [[ $opt =~ $(expand valid_compilers) ]]; then + compiler="$opt" + elif [[ $opt =~ $(expand valid_generators) ]]; then + generator="$opt" + else + >&2 echo 'Invalid option!' "($opt)" + usage + exit 1 + fi + ;; + 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: Sat, 18 Oct 2025 17:26:06 -0700 Subject: [PATCH 02/12] fix utest script --- scripts/utest | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/scripts/utest b/scripts/utest index f3d7674..3e0b55b 100755 --- a/scripts/utest +++ b/scripts/utest @@ -20,10 +20,6 @@ Options: EOF } >&2 -valid_build_types=("release" "debug") -valid_compilers=("gcc" "clang") -valid_generators=("ninja" "make") - expand() { eval "IFS='|'; echo \"\${$1[*]}\"" } @@ -40,7 +36,7 @@ jobs=${JOBS-$(nproc)} cmake_args=() # Process all args -until [[ -z $1 ]]; do +while (($#)); do opt=${1,,} # transform to all lowercase case "$opt" in -n | --dry-run) @@ -54,6 +50,7 @@ until [[ -z $1 ]]; do ;; -h | --help) usage + exit ;; -j | --jobs) if ! [[ $2 =~ ^[0-9]+$ ]]; then From db99564aab5de42d927a3f83c908695dd34c8710 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sat, 18 Oct 2025 17:27:34 -0700 Subject: [PATCH 03/12] fix pre-push git hook --- hooks/pre-push | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/pre-push b/hooks/pre-push index 54cb87a..1a24851 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) +>/dev/null 2>&1 "$REPO_ROOT/scripts/build" || (echo "[pre-push] Build failed!" && exit 1) +>/dev/null 2>&1 "$REPO_ROOT/scripts/utest" --no-build || (echo "[pre-push] Test(s) failed!" && exit 1) echo "[pre-push] Build and tests passed successfully" From 3cda4759f0808e2185bf0382487c0383fedbc0ae Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sat, 18 Oct 2025 17:28:04 -0700 Subject: [PATCH 04/12] fix .gitignore for envrc files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f1997a2..320da00 100644 --- a/.gitignore +++ b/.gitignore @@ -145,7 +145,7 @@ celerybeat.pid *.sage.py # Environments -.direnv +.envrc .env .venv env/ From 3c9c12a20ba253dfa588fcb1dfb25653559d5b25 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sat, 18 Oct 2025 17:29:27 -0700 Subject: [PATCH 05/12] more logging for pre-push script --- hooks/pre-push | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/pre-push b/hooks/pre-push index 1a24851..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 "$REPO_ROOT/scripts/build" || (echo "[pre-push] Build failed!" && exit 1) ->/dev/null 2>&1 "$REPO_ROOT/scripts/utest" --no-build || (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" From 7a4db73af0d7ad1de1bb276b9eb1879d6ff7b87d Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sat, 18 Oct 2025 17:38:56 -0700 Subject: [PATCH 06/12] chore: add docs only to workflows; integrate into build script --- CMakePresets.json | 14 ++++++++++++++ scripts/build | 7 +++++-- scripts/generate-presets | 19 ++++++++++++++++++- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 9c3c63a..4f80e5d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -669,6 +669,20 @@ "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/scripts/build b/scripts/build index 53dc2aa..63dd86b 100755 --- a/scripts/build +++ b/scripts/build @@ -14,7 +14,7 @@ Build project with preset according to options. Do so within a docker container if \`docker\` is specified. Arguments: - [preset-opts] build-type, compiler, generator + [preset-opts] build-type, compiler, generator OR docs[-only] default triple is: debug-gcc-ninja [docker] build takes place within a docker container defined by Dockerfile @@ -88,6 +88,9 @@ while (($#)); do ninja | make) generator="$opt" ;; + docs | docs-only) + preset='unixlike-docs-only' + ;; *) >&2 echo 'Invalid option!' "($opt)" usage @@ -102,7 +105,7 @@ if [[ $jobs -gt $(nproc) ]]; then jobs=$(nproc) fi -preset="${cross_compile}unixlike-${compiler}-${generator}-${build_type}" +preset="${preset-${cross_compile}unixlike-${compiler}-${generator}-${build_type}}" if [[ -n $deep_clean ]]; then rm -rf "./build/$preset" diff --git a/scripts/generate-presets b/scripts/generate-presets index 19e7896..93f41c0 100755 --- a/scripts/generate-presets +++ b/scripts/generate-presets @@ -110,6 +110,23 @@ build_docs_only = [ } ] +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": { @@ -189,7 +206,7 @@ presets = { ] } for (cc, _), (genname, _), (cfgname, _) in itertools.product(compilers, generators, configs) - ] + ] + workflow_docs_only } def main(): From 73781febb6629f7e8c6e8a246ddd136b492a4093 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sat, 18 Oct 2025 17:49:08 -0700 Subject: [PATCH 07/12] chore: output JUnit from ctest by default in presets --- CMakePresets.json | 3 ++- scripts/generate-presets | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index 4f80e5d..e4e9f6d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -302,7 +302,8 @@ "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" diff --git a/scripts/generate-presets b/scripts/generate-presets index 93f41c0..cc8c1a0 100755 --- a/scripts/generate-presets +++ b/scripts/generate-presets @@ -93,11 +93,12 @@ test_common = [ "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" - } + }, } ] From 87ff22e11e70387b7a57e2524ccf9fcb17d2d21c Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sun, 19 Oct 2025 16:08:49 -0700 Subject: [PATCH 08/12] chore: update workflow with docker --- Dockerfile | 4 ---- scripts/build | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index a5da81a..e5dd053 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,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" \ @@ -25,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/scripts/build b/scripts/build index 63dd86b..02e5a6d 100755 --- a/scripts/build +++ b/scripts/build @@ -123,7 +123,7 @@ if [[ -n $use_docker ]]; then fi ccache_dir="$(ccache -p | sed -En 's/^.*cache_dir =\s+(.*)/\1/p')" - docker_volumes=(--volume ./build:/workspace/build"$selinux_flag") + 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 @@ -138,6 +138,7 @@ $(for ((i = 0; i < ${#docker_volumes[@]} - 1; i += 2)); do done) --cpus="$jobs" \\ --env "CMAKE_BUILD_PARALLEL_LEVEL=$jobs" \\ + --workdir /workspace/asmgrader \\ aarch64-cross cmake ${cmake_args[@]} EOF else @@ -157,6 +158,7 @@ if [[ -n $use_docker ]]; then "${docker_volumes[@]}" \ --cpus="$jobs" \ --env "CMAKE_BUILD_PARALLEL_LEVEL=$jobs" \ + --workdir /workspace/asmgrader \ aarch64-cross cmake "${cmake_args[@]}" else CMAKE_BUILD_PARALLEL_LEVEL="$jobs" cmake "${cmake_args[@]}" From 626493df54aec2dd5d89ae45224cbca8b7d49567 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sun, 19 Oct 2025 16:15:47 -0700 Subject: [PATCH 09/12] chore: fix ci to use new workflow scripts --- .github/workflows/ci.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 From 88026a3dbf479f7991cde4b7da683e9eb943f211 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Sun, 19 Oct 2025 16:26:22 -0700 Subject: [PATCH 10/12] chore: fix utest script --- scripts/utest | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/utest b/scripts/utest index 3e0b55b..a8f68cf 100755 --- a/scripts/utest +++ b/scripts/utest @@ -14,8 +14,8 @@ Arguments: Options: -h, --help print this help message - -j JOBS, --jobs JOBS Number of threads to use. Fallbacks are env{JOBS} then nproc(1) -n, --dry-run print commands instead of executing them + -j JOBS, --jobs JOBS number of threads to use. Fallbacks are env{JOBS} then nproc(1) --no-build do not attempt to build EOF } >&2 @@ -31,6 +31,7 @@ compiler="gcc" generator="ninja" fresh= no_build= +report= jobs=${JOBS-$(nproc)} cmake_args=() @@ -60,18 +61,19 @@ while (($#)); do jobs="$2" shift ;; + release | debug) + build_type="$opt" + ;; + gcc | clang) + compiler="$opt" + ;; + ninja | make) + generator="$opt" + ;; *) - if [[ $opt =~ $(expand valid_build_types) ]]; then - build_type="$opt" - elif [[ $opt =~ $(expand valid_compilers) ]]; then - compiler="$opt" - elif [[ $opt =~ $(expand valid_generators) ]]; then - generator="$opt" - else - >&2 echo 'Invalid option!' "($opt)" - usage - exit 1 - fi + >&2 echo 'Invalid option!' "($opt)" + usage + exit 1 ;; esac shift From 4f10364664964d6e5086f9d212b216d8bf6da9b6 Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Mon, 20 Oct 2025 00:26:31 -0700 Subject: [PATCH 11/12] add more logging to linux syscall wrappers --- include/asmgrader/common/linux.hpp | 42 ++++++++++++++++++++++++------ 1 file changed, 34 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; } From fcaa95d0f79b91f18c4161a118e8611d1b4872ab Mon Sep 17 00:00:00 2001 From: Matthew Reese Date: Mon, 20 Oct 2025 15:51:51 -0700 Subject: [PATCH 12/12] fix: potentially fixed race condition timeout bug with waitid(2) call --- src/subprocess/tracer.cpp | 5 +++++ 1 file changed, 5 insertions(+) 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; }